create-eigen-game 0.16.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-eigen-game",
3
- "version": "0.16.2",
3
+ "version": "0.17.0",
4
4
  "license": "MIT",
5
5
  "description": "Scaffold an EigenInteractive game Worker and Flutter app without cloning the engine repositories.",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  // Keep this version aligned with the Firebase JS SDK supported by the
2
2
  // firebase_core_web version resolved by the Flutter app.
3
- importScripts("https://www.gstatic.com/firebasejs/12.15.0/firebase-app-compat.js");
4
- importScripts("https://www.gstatic.com/firebasejs/12.15.0/firebase-messaging-compat.js");
3
+ importScripts("https://www.gstatic.com/firebasejs/12.19.0/firebase-app-compat.js");
4
+ importScripts("https://www.gstatic.com/firebasejs/12.19.0/firebase-messaging-compat.js");
5
5
  importScripts("firebase-config.js");
6
6
 
7
7
  // `firebase:configure` generates this public configuration from the Web app
@@ -1,30 +1,43 @@
1
1
  {{flutter_js}}
2
2
  {{flutter_build_config}}
3
3
 
4
- (async () => {
5
- if ("serviceWorker" in navigator) {
6
- await navigator.serviceWorker
7
- .register("firebase-messaging-sw.js", {
8
- scope: "/firebase-cloud-messaging-push-scope",
9
- })
10
- .catch((error) => {
11
- console.warn(
12
- "Firebase Messaging service worker registration failed",
13
- error,
14
- );
15
- });
16
- }
4
+ // Flutter starts at once. Neither worker below is anything this page waits on,
5
+ // so they register beside it rather than ahead of it.
6
+ _flutter.loader.load();
17
7
 
18
- if ("serviceWorker" in navigator) {
19
- // The app shell's own cache, registered at the root scope so it can answer
20
- // a cold start with no network. Separate from the messaging worker above,
21
- // which owns only its push scope.
22
- await navigator.serviceWorker
23
- .register("eigen_offline_sw.js", { scope: "/" })
24
- .catch((error) => {
25
- console.warn("Offline shell service worker registration failed", error);
26
- });
27
- }
8
+ if ("serviceWorker" in navigator) {
9
+ // The app's own worker, generated by Workbox (workbox-config.cjs): the
10
+ // precached build, which is what lets the app open with no network.
11
+ navigator.serviceWorker.register("sw.js").catch((error) => {
12
+ console.warn("App service worker registration failed", error);
13
+ });
14
+ // Firebase Messaging's worker, which owns only its push scope.
15
+ navigator.serviceWorker.register("firebase-messaging-sw.js", { scope: "/firebase-cloud-messaging-push-scope" }).catch((error) => {
16
+ console.warn("Firebase Messaging service worker registration failed", error);
17
+ });
18
+ }
28
19
 
29
- _flutter.loader.load();
30
- })();
20
+ // CanvasKit and the Firebase JS SDK load from Google's CDN, which the app's
21
+ // worker caches as they pass through it. A first visit fetches them before that
22
+ // worker controls the page, so the page copies what it loaded into the same
23
+ // cache ("eigen-cdn" in workbox-config.cjs), and drops versions of each it no
24
+ // longer loads. Without this, the first visit would not open offline.
25
+ if ("caches" in self && "PerformanceObserver" in self) {
26
+ const cdn = /^https:\/\/www\.gstatic\.com\/(flutter-canvaskit|firebasejs)\/[^/]+\//;
27
+ new PerformanceObserver(async (entries) => {
28
+ for (const { name } of entries.getEntries()) {
29
+ const loaded = cdn.exec(name);
30
+ if (!loaded) continue;
31
+ try {
32
+ const cache = await caches.open("eigen-cdn");
33
+ if (!(await cache.match(name))) await cache.add(name);
34
+ for (const request of await cache.keys()) {
35
+ const cached = cdn.exec(request.url);
36
+ if (cached && cached[1] === loaded[1] && cached[0] !== loaded[0]) await cache.delete(request);
37
+ }
38
+ } catch {
39
+ // Only a later offline start is at stake, and the next load tries again.
40
+ }
41
+ }
42
+ }).observe({ type: "resource", buffered: true });
43
+ }
@@ -109,17 +109,19 @@ jobs:
109
109
  run: flutter test
110
110
 
111
111
  # A VM widget test can pass while a dependency still imports dart:io or
112
- # calls an unsupported plugin. Compiling the real web entrypoint is
113
- # what actually proves it ships, mirroring eigen-flutter's own `example`
114
- # job, which does the same for the RPS reference app.
112
+ # calls an unsupported plugin. Building the real web app is what actually
113
+ # proves it ships, mirroring eigen-flutter's own `example` job, which does
114
+ # the same for the RPS reference app. `build:web` is the deploy's own
115
+ # build: Flutter, then the service worker Workbox generates from it, so
116
+ # the root's Node tools are installed first.
115
117
  #
116
118
  # No Android SDK step in this job, for the same reason
117
119
  # `create-eigen-game`'s own `scaffold-e2e.mjs` check skips one:
118
120
  # `flutter analyze`/`test`/`build web` don't need it, and it would
119
121
  # roughly double this job for no extra coverage. The Android build is
120
122
  # verified for real, with a real signing config, in `release.yml`.
123
+ - name: Install root dependencies
124
+ run: {{PACKAGE_MANAGER}} install
125
+
121
126
  - name: Build web
122
- working-directory: app
123
- run: >
124
- flutter build web --release
125
- --dart-define-from-file=app-config.json
127
+ run: {{PACKAGE_MANAGER}} run build:web
@@ -88,8 +88,9 @@ values in `app/app-config.json`, then deploy both halves together:
88
88
  {{PACKAGE_MANAGER}} run deploy
89
89
  ```
90
90
 
91
- That builds Flutter into `server/public/` and deploys one Worker containing the
92
- SPA and API. The app is `/`; invite and game URLs open the installed native app
91
+ That builds Flutter into `server/public/`, generates the service worker that lets
92
+ the web app open offline (`workbox-config.cjs`), and deploys one Worker
93
+ containing the SPA and API. The app is `/`; invite and game URLs open the installed native app
93
94
  or the browser SPA; the native install landing page is `/download`.
94
95
 
95
96
  Configure Android, Flutter Web, and the messaging service worker together:
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "scripts": {
5
5
  "build:android": "cd app && flutter build appbundle --release --dart-define-from-file=app-config.json",
6
- "build:web": "cd app && flutter build web --release --output ../server/public --dart-define-from-file=app-config.json",
6
+ "build:web": "cd app && flutter build web --release --output ../server/public --dart-define-from-file=app-config.json && cd .. && workbox generateSW workbox-config.cjs",
7
7
  "contract": "cd server && {{PACKAGE_MANAGER}} run contract && cd ../app && dart run eigen_codegen:generate_payloads --contract ../server/game-contract.json --output lib/game/generated/payloads.dart --fixtures-output test/fixtures",
8
8
  "contract:check": "cd server && {{PACKAGE_MANAGER}} run contract:check && cd ../app && dart run eigen_codegen:generate_payloads --contract ../server/game-contract.json --output lib/game/generated/payloads.dart --fixtures-output test/fixtures --check",
9
9
  "deploy": "{{PACKAGE_MANAGER}} run build:web && cd server && {{PACKAGE_MANAGER}} run deploy",
@@ -12,6 +12,7 @@
12
12
  "lint": "biome check ."
13
13
  },
14
14
  "devDependencies": {
15
- "@biomejs/biome": "2.5.12"
15
+ "@biomejs/biome": "2.5.12",
16
+ "workbox-cli": "7.4.1"
16
17
  }
17
18
  }
@@ -0,0 +1,45 @@
1
+ // The web app's service worker, generated by Workbox from the Flutter build
2
+ // (decision 0014). `build:web` runs it after building
3
+ // Flutter into `server/public/`.
4
+ //
5
+ // Every file the app is built from is precached when the worker installs, so
6
+ // the app opens with no network from the first visit on, and always from one
7
+ // consistent build. A deploy reaches a player as a new worker that precaches
8
+ // the new build beside the old one and takes over once no tab still runs the
9
+ // old build, or at once when the app asks it to because the engine requires the
10
+ // update.
11
+
12
+ // Paths the game Worker answers itself rather than the app shell: its
13
+ // `run_worker_first` routes in wrangler.jsonc, less `/join/*` and `/game/*`,
14
+ // which render the app. A navigation to any other path opens the app.
15
+ const workerRoutes = [/^\/api\//, /^\/avatars\//, /^\/\.well-known\//, /^\/(health|download|terms|privacy|delete-account|robots\.txt|sitemap\.xml|site\.webmanifest)$/];
16
+
17
+ module.exports = {
18
+ globDirectory: "server/public",
19
+ swDest: "server/public/sw.js",
20
+ globPatterns: ["index.html", "flutter_bootstrap.js", "main.dart.*", "manifest.json", "favicon.png", "icons/**", "assets/**"],
21
+ globIgnores: ["**/*.map", "**/*.symbols"],
22
+ // `main.dart.js` alone is several megabytes, and a game's assets may be too.
23
+ // A file over the limit would be left out with only a warning, and the app
24
+ // would then fail offline, so the limit is set well above anything expected.
25
+ maximumFileSizeToCacheInBytes: 32 * 1024 * 1024,
26
+ navigateFallback: "index.html",
27
+ navigateFallbackDenylist: workerRoutes,
28
+ runtimeCaching: [
29
+ {
30
+ // CanvasKit and the Firebase JS SDK load from Google's CDN at URLs that
31
+ // name their version, so a cached copy is never stale. The page adds what
32
+ // a first visit loaded before this worker controlled it (see
33
+ // web/flutter_bootstrap.js), under the same cache name.
34
+ urlPattern: /^https:\/\/www\.gstatic\.com\/(flutter-canvaskit|firebasejs)\//,
35
+ handler: "CacheFirst",
36
+ options: { cacheName: "eigen-cdn", cacheableResponse: { statuses: [200] } },
37
+ },
38
+ ],
39
+ skipWaiting: false,
40
+ clientsClaim: true,
41
+ cleanupOutdatedCaches: true,
42
+ inlineWorkboxRuntime: true,
43
+ sourcemap: false,
44
+ mode: "production",
45
+ };
@@ -1,11 +0,0 @@
1
- # Web assets
2
-
3
- `sqlite3.wasm` and `drift_worker.js` are drift's web runtime, taken verbatim
4
- from the [drift 2.35.0
5
- release](https://github.com/simolus3/drift/releases/tag/drift-2.35.0). They are
6
- served from the app's own origin because that is where `LocalDatabase` looks for
7
- them, and they are what gives a browser the storage offline play needs.
8
-
9
- Replace both together whenever the `drift` constraint in `eigen_flutter`'s
10
- pubspec moves, and take them from the matching release: the worker and the
11
- module are one pair.