Skip to content

Commit a7d2975

Browse files
authored
Add PWA App (#75)
* add pwa app * fix test * fix test
1 parent 7389005 commit a7d2975

11 files changed

Lines changed: 454 additions & 123 deletions

File tree

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,7 @@ AWS_DEFAULT_REGION=us-east-1
6161
AWS_BUCKET=
6262
AWS_USE_PATH_STYLE_ENDPOINT=false
6363

64+
# Catch-Em-All Domain Configuration
65+
CATCH_DOMAIN=catch.localhost
66+
6467
VITE_APP_NAME="${APP_NAME}"

app/Http/Controllers/AuthController.php

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,20 @@ public function show()
2121

2222
public function login()
2323
{
24+
// Determine callback URL based on current domain
25+
$currentHost = request()->getHost();
26+
$isCatchEmAll = $currentHost === config('fcea.domain');
27+
28+
if ($isCatchEmAll) {
29+
$protocol = str_contains($currentHost, 'localhost') ? 'http' : 'https';
30+
$callbackUrl = $protocol . '://' . $currentHost . '/auth/callback';
31+
} else {
32+
$callbackUrl = rtrim(config('app.url'), '/') . '/auth/callback';
33+
}
34+
2435
$url = Socialite::driver('identity')
2536
->scopes(['openid', 'profile', 'email', 'groups', 'offline', 'offline_access'])
37+
->redirectUrl($callbackUrl)
2638
->redirect();
2739

2840
// Use Interia location instead
@@ -32,9 +44,31 @@ public function login()
3244
public function loginCallback()
3345
{
3446
try {
35-
$socialLiteUser = Socialite::driver('identity')->user();
47+
// Ensure callback URL matches the one used during login redirect
48+
$currentHost = request()->getHost();
49+
$isCatchEmAll = $currentHost === config('fcea.domain');
50+
51+
if ($isCatchEmAll) {
52+
$protocol = str_contains($currentHost, 'localhost') ? 'http' : 'https';
53+
$callbackUrl = $protocol . '://' . $currentHost . '/auth/callback';
54+
} else {
55+
$callbackUrl = rtrim(config('app.url'), '/') . '/auth/callback';
56+
}
57+
58+
$socialLiteUser = Socialite::driver('identity')
59+
->redirectUrl($callbackUrl)
60+
->user();
3661
} catch (\Exception $e) {
37-
return redirect()->route('auth.login');
62+
// Redirect to login on the same domain
63+
$currentHost = request()->getHost();
64+
$isCatchEmAll = $currentHost === config('fcea.domain');
65+
66+
if ($isCatchEmAll) {
67+
$protocol = str_contains($currentHost, 'localhost') ? 'http' : 'https';
68+
return redirect($protocol . '://' . $currentHost . '/auth/login');
69+
} else {
70+
return redirect()->route('auth.login');
71+
}
3872
}
3973

4074
$attendeeListResponse = \Illuminate\Support\Facades\Http::attsrv()
@@ -45,8 +79,16 @@ public function loginCallback()
4579
$regId = $attendeeListResponse['ids'][0] ?? null;
4680

4781
if (isset($attendeeListResponse['ids'][0]) === false) {
48-
return redirect()->route('welcome')->with('message',
49-
'Please register for the Convention first before trying to obtain a fursuit badge.');
82+
$currentHost = request()->getHost();
83+
$isCatchEmAll = $currentHost === config('fcea.domain');
84+
85+
if ($isCatchEmAll) {
86+
return redirect()->route('catch-em-all.introduction')->with('message',
87+
'Please register for the Convention first before trying to play Catch-Em-All.');
88+
} else {
89+
return redirect()->route('welcome')->with('message',
90+
'Please register for the Convention first before trying to obtain a fursuit badge.');
91+
}
5092
}
5193

5294
$isNewUser = User::where('remote_id', $socialLiteUser->getId())->doesntExist();
@@ -130,24 +172,49 @@ public function loginCallback()
130172
}
131173

132174
Auth::login($user, true);
133-
if (Session::exists('catch-em-all-redirect')) {
134-
Session::forget('catch-em-all-redirect');
135175

136-
return redirect()->route('catch-em-all.catch');
137-
}
176+
// Redirect based on current domain
177+
$currentHost = request()->getHost();
178+
$isCatchEmAll = $currentHost === config('fcea.domain');
138179

139-
return redirect()->route('dashboard');
180+
if ($isCatchEmAll) {
181+
// Redirect to introduction page for new users, or home for returning users
182+
return redirect()->route('catch-em-all.introduction');
183+
} else {
184+
return redirect()->route('dashboard');
185+
}
140186
}
141187

142188
public function logout()
143189
{
144-
return Inertia::location('https://identity.eurofurence.org/oauth2/sessions/logout');
190+
$currentHost = request()->getHost();
191+
$isCatchEmAll = $currentHost === config('fcea.domain');
192+
193+
if ($isCatchEmAll) {
194+
// Include post logout redirect for Catch-Em-All
195+
$protocol = str_contains($currentHost, 'localhost') ? 'http' : 'https';
196+
$returnUrl = $protocol . '://' . $currentHost;
197+
return Inertia::location('https://identity.eurofurence.org/oauth2/sessions/logout?post_logout_redirect_uri=' . urlencode($returnUrl));
198+
} else {
199+
return Inertia::location('https://identity.eurofurence.org/oauth2/sessions/logout');
200+
}
145201
}
146202

147203
// Frontchannel Logout
148204
public function logoutCallback()
149205
{
150206
Auth::logout();
151207
Session::flush();
208+
209+
// Optional: redirect based on domain
210+
$currentHost = request()->getHost();
211+
$isCatchEmAll = $currentHost === config('fcea.domain');
212+
213+
if ($isCatchEmAll) {
214+
$protocol = str_contains($currentHost, 'localhost') ? 'http' : 'https';
215+
return redirect($protocol . '://' . $currentHost);
216+
}
217+
218+
// For main domain, just complete the logout (no redirect needed)
152219
}
153220
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Illuminate\Http\JsonResponse;
6+
use Illuminate\Http\Request;
7+
8+
class PWAController extends Controller
9+
{
10+
public function manifest(Request $request): JsonResponse
11+
{
12+
$domain = config('fcea.domain');
13+
$protocol = str_contains($domain, 'localhost') ? 'http' : 'https';
14+
$baseUrl = $protocol . '://' . $domain;
15+
16+
$manifest = [
17+
'name' => 'Catch-Em-All',
18+
'short_name' => 'CatchEm',
19+
'description' => 'Fursuiter hunting game for Eurofurence',
20+
'start_url' => $baseUrl . '/',
21+
'scope' => $baseUrl . '/',
22+
'display' => 'standalone',
23+
'orientation' => 'portrait',
24+
'theme_color' => '#1f2937',
25+
'background_color' => '#ffffff',
26+
'categories' => ['games', 'social'],
27+
'lang' => 'en',
28+
'icons' => [
29+
[
30+
'src' => '/icons/icon-72x72.png',
31+
'sizes' => '72x72',
32+
'type' => 'image/png',
33+
'purpose' => 'maskable any'
34+
],
35+
[
36+
'src' => '/icons/icon-96x96.png',
37+
'sizes' => '96x96',
38+
'type' => 'image/png',
39+
'purpose' => 'maskable any'
40+
],
41+
[
42+
'src' => '/icons/icon-128x128.png',
43+
'sizes' => '128x128',
44+
'type' => 'image/png',
45+
'purpose' => 'maskable any'
46+
],
47+
[
48+
'src' => '/icons/icon-144x144.png',
49+
'sizes' => '144x144',
50+
'type' => 'image/png',
51+
'purpose' => 'maskable any'
52+
],
53+
[
54+
'src' => '/icons/icon-152x152.png',
55+
'sizes' => '152x152',
56+
'type' => 'image/png',
57+
'purpose' => 'maskable any'
58+
],
59+
[
60+
'src' => '/icons/icon-192x192.png',
61+
'sizes' => '192x192',
62+
'type' => 'image/png',
63+
'purpose' => 'maskable any'
64+
],
65+
[
66+
'src' => '/icons/icon-384x384.png',
67+
'sizes' => '384x384',
68+
'type' => 'image/png',
69+
'purpose' => 'maskable any'
70+
],
71+
[
72+
'src' => '/icons/icon-512x512.png',
73+
'sizes' => '512x512',
74+
'type' => 'image/png',
75+
'purpose' => 'maskable any'
76+
]
77+
],
78+
'shortcuts' => [
79+
[
80+
'name' => 'Leaderboard',
81+
'short_name' => 'Leaders',
82+
'description' => 'View the leaderboard',
83+
'url' => $baseUrl . '/leaderboard',
84+
'icons' => [
85+
[
86+
'src' => '/icons/icon-96x96.png',
87+
'sizes' => '96x96'
88+
]
89+
]
90+
],
91+
[
92+
'name' => 'Collection',
93+
'short_name' => 'Collection',
94+
'description' => 'View your collection',
95+
'url' => $baseUrl . '/collection',
96+
'icons' => [
97+
[
98+
'src' => '/icons/icon-96x96.png',
99+
'sizes' => '96x96'
100+
]
101+
]
102+
]
103+
]
104+
];
105+
106+
return response()->json($manifest, 200, [
107+
'Content-Type' => 'application/manifest+json',
108+
]);
109+
}
110+
111+
public function serviceWorker(): \Illuminate\Http\Response
112+
{
113+
$serviceWorkerContent = "
114+
// Service Worker for Catch-Em-All PWA
115+
const CACHE_NAME = 'catch-em-all-v1';
116+
const urlsToCache = [
117+
'/',
118+
'/auth/login',
119+
'/leaderboard',
120+
'/collection',
121+
'/achievements'
122+
];
123+
124+
self.addEventListener('install', function(event) {
125+
event.waitUntil(
126+
caches.open(CACHE_NAME)
127+
.then(function(cache) {
128+
return cache.addAll(urlsToCache);
129+
})
130+
);
131+
});
132+
133+
self.addEventListener('fetch', function(event) {
134+
event.respondWith(
135+
caches.match(event.request)
136+
.then(function(response) {
137+
// Return cached version or fetch from network
138+
return response || fetch(event.request);
139+
}
140+
)
141+
);
142+
});
143+
144+
self.addEventListener('activate', function(event) {
145+
event.waitUntil(
146+
caches.keys().then(function(cacheNames) {
147+
return Promise.all(
148+
cacheNames.map(function(cacheName) {
149+
if (cacheName !== CACHE_NAME) {
150+
return caches.delete(cacheName);
151+
}
152+
})
153+
);
154+
})
155+
);
156+
});
157+
";
158+
159+
return response($serviceWorkerContent, 200, [
160+
'Content-Type' => 'application/javascript',
161+
'Cache-Control' => 'no-cache, no-store, must-revalidate',
162+
]);
163+
}
164+
}

app/Http/Middleware/CatchEmAllAuthMiddleware.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ class CatchEmAllAuthMiddleware extends Authenticate
1010
{
1111
protected function redirectTo(Request $request)
1212
{
13-
Session::put('catch-em-all-redirect', true);
14-
15-
return route('auth.login.redirect');
13+
// Redirect to the Catch-Em-All domain login
14+
return route('catch-em-all.auth.login');
1615
}
1716
}

bootstrap/app.php

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,47 @@
77

88
return Application::configure(basePath: dirname(__DIR__))
99
->withRouting(
10-
web: __DIR__.'/../routes/web.php',
1110
api: __DIR__.'/../routes/api.php',
1211
commands: __DIR__.'/../routes/console.php',
1312
health: '/up',
1413
then: function () {
15-
\Illuminate\Support\Facades\Route::prefix('catch-em-all/')
14+
// Parse domain from APP_URL
15+
$mainDomain = parse_url(config('app.url'), PHP_URL_HOST) ?: 'localhost';
16+
17+
// Catch-Em-All game routes (specific domain - higher priority)
18+
\Illuminate\Support\Facades\Route::domain(config('fcea.domain'))
1619
->name('catch-em-all.')
1720
->middleware([
1821
'web',
19-
'catch-auth:web',
2022
])
2123
->group(base_path('routes/catch-em-all.php'));
22-
\Illuminate\Support\Facades\Route::middleware([
23-
'pos-auth:machine',
24-
'pos-auth:machine-user',
25-
'web', \App\Http\Middleware\InactivityLogoutMiddleware::class,
26-
])
24+
25+
// Main application routes (domain-based)
26+
\Illuminate\Support\Facades\Route::domain($mainDomain)
27+
->middleware('web')
28+
->group(base_path('routes/web.php'));
29+
30+
// POS system routes
31+
\Illuminate\Support\Facades\Route::domain($mainDomain)
32+
->middleware([
33+
'pos-auth:machine',
34+
'pos-auth:machine-user',
35+
'web', \App\Http\Middleware\InactivityLogoutMiddleware::class,
36+
])
2737
->prefix('pos/')
2838
->name('pos.')
2939
->group(base_path('routes/pos.php'));
30-
\Illuminate\Support\Facades\Route::prefix('pos/auth/')
40+
41+
// POS authentication routes
42+
\Illuminate\Support\Facades\Route::domain($mainDomain)
43+
->prefix('pos/auth/')
3144
->name('pos.auth.')
3245
->middleware('web')
3346
->group(base_path('routes/pos-auth.php'));
34-
\Illuminate\Support\Facades\Route::prefix('gallery')
47+
48+
// Gallery routes
49+
\Illuminate\Support\Facades\Route::domain($mainDomain)
50+
->prefix('gallery')
3551
->name('gallery.')
3652
->middleware('web')
3753
->group(base_path('routes/gallery.php'));

config/fcea.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
<?php
22

33
return [
4+
/*
5+
|--------------------------------------------------------------------------
6+
| Catch-Em-All Domain
7+
|--------------------------------------------------------------------------
8+
|
9+
| The domain where the Catch-Em-All game is hosted. This allows the game
10+
| to be served from a separate subdomain with its own authentication flow.
11+
|
12+
*/
13+
14+
'domain' => env('CATCH_DOMAIN', 'catch.localhost'),
15+
416
/*
517
|--------------------------------------------------------------------------
618
| Fursuit Catch Code Length

0 commit comments

Comments
 (0)