diff --git a/docs/getting-started/manual-setup.md b/docs/getting-started/manual-setup.md index c1de079..e760928 100644 --- a/docs/getting-started/manual-setup.md +++ b/docs/getting-started/manual-setup.md @@ -155,8 +155,8 @@ module, and call `runEngineApp` from `lib/main.dart`. The Dart pieces. Create `app-config.json` beside `pubspec.yaml` with `API_BASE_URL`, -`GOOGLE_WEB_CLIENT_ID`, optional `APP_HOST`, and the public -`FIREBASE_VAPID_KEY`. Read them once with `const String.fromEnvironment` in +`GOOGLE_WEB_CLIENT_ID`, the optional `APP_HOST` and `AUTH_DOMAIN`, and the +public `FIREBASE_VAPID_KEY`. Read them once with `const String.fromEnvironment` in `main.dart`, pass them into `EngineConfig`, and use `--dart-define-from-file=app-config.json` for both Android and web commands. The complete shape and validation rules are in diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 09a2e9e..0bf3c3c 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -97,6 +97,12 @@ notifications and account deletion run on; token verification uses features fail only when exercised. `wrangler dev` reads `.dev.vars` at startup, so restart `pnpm dev` afterwards. +**5. Link Google Analytics.** If the console shows _"Google Analytics not +enabled for Project"_, that is expected: a project created through the CLI +during scaffolding is not linked to an Analytics account, and only the console's +own create flow offers it. Firebase Console → Settings → **Integrations** → +Google Analytics → **Enable**. + Steps 2 and 3 are public values compiled into the app. Step 4 is not: `.dev.vars` is a real credential, is git-ignored, and must stay that way. Delete the downloaded JSON once you have copied the two fields out of it. diff --git a/docs/ship-it/branding.md b/docs/ship-it/branding.md index 73de2a0..da50f0c 100644 --- a/docs/ship-it/branding.md +++ b/docs/ship-it/branding.md @@ -163,13 +163,22 @@ mid-download does not lose the page. the first deploy. What it does *not* have is the legal half: those routes are not mounted, so the footer carries only the credit until you fill in `operator`. -The page also waits for a Flutter web build to reach `public/` before it shows -your app icon or a "Play on the web" button, because both would otherwise be -broken: the icons ship inside that bundle, and `/` would bounce straight back -to `/download`. Until then it stands the EigenInteractive mark in for the icon, -drawn in your `primaryColor`, exactly as the Flutter shell defaults to the -EigenInteractive seed and credit. Run `pnpm run build:web` and your own icon -replaces it. +The page waits for a Flutter web build to reach `public/` before it offers a +"Play on the web" button, because `/` would otherwise bounce straight back to +`/download`. + +Icons are a separate question, and the answer is the same on every page the +engine renders: the browser tab, the download page's logo, and the manifest. +**Until your game has icons of its own, all three use the EigenInteractive +mark**, served by the Worker and drawn in your `primaryColor`, exactly as the +Flutter shell defaults to the EigenInteractive seed and credit. It is a +placeholder, not a default. The moment `favicon.png` exists in `public/` the +engine links your icons everywhere and stops serving its own. + +That check looks at the icons themselves rather than at the web build, so a +game with no web build still gets its own. `pnpm run build:web` is the usual +way they arrive; an Android-only game can copy the same files out of `app/web/` +into `public/` and get the identical result. A game with no web build *and* no store URLs in `deepLink` has nothing to offer at all, and the page says `Coming soon.` rather than trailing off after the diff --git a/docs/ship-it/configure.md b/docs/ship-it/configure.md index 4294385..4c52977 100644 --- a/docs/ship-it/configure.md +++ b/docs/ship-it/configure.md @@ -73,6 +73,7 @@ const apiBaseUrl = String.fromEnvironment('API_BASE_URL'); const googleWebClientId = String.fromEnvironment('GOOGLE_WEB_CLIENT_ID'); const firebaseVapidKey = String.fromEnvironment('FIREBASE_VAPID_KEY'); const appHost = String.fromEnvironment('APP_HOST'); +const authDomain = String.fromEnvironment('AUTH_DOMAIN'); await runEngineApp( module: const RpsModule(), @@ -83,6 +84,7 @@ await runEngineApp( googleWebClientId: googleWebClientId, firebaseVapidKey: firebaseVapidKey, appHost: appHost.isEmpty ? null : appHost, + authDomain: authDomain.isEmpty ? null : authDomain, ), ), firebaseOptions: DefaultFirebaseOptions.currentPlatform, @@ -105,6 +107,7 @@ flutter build appbundle --release \ | `API_BASE_URL` | **yes** | Origin of the Worker: scheme + host only, **no path, no trailing slash**. Routes carry their own `/api/engine` prefix; the socket is this origin with `ws`/`wss`. | | `GOOGLE_WEB_CLIENT_ID` | yes | Google Sign-In. Both platforms use the *web* client, so an Android-only app still needs it. | | `APP_HOST` | optional | This game's hostname, without scheme. In the default deployment it is the host part of `API_BASE_URL`; it enables invite/replay sharing and legal links. `/download` is the native install page. | +| `AUTH_DOMAIN` | optional | Firebase Auth's own domain, without scheme, overriding the project default. Cosmetic and web-only: it is the hostname Google's account chooser names during sign-in. **Not `APP_HOST`**; it must be a Firebase Hosting domain. See [Deploy the web app](./deploy-the-web-app.md#the-hostname-players-see-when-they-sign-in). | | `FIREBASE_VAPID_KEY` | **yes for web** | Public FCM Web Push key from the same Firebase project. An empty key is a web startup configuration error. Android does not consume it. | These values are embedded in the Android binary or downloaded web bundle and @@ -124,6 +127,7 @@ empty strings. What it leaves is what no CLI can produce. | `GOOGLE_WEB_CLIENT_ID` | filled in after `firebase:configure`, when there is one to copy | Firebase Console → Authentication → Sign-in method → Google → **Web SDK configuration**. It is also the `"client_type": 3` entry of `app/android/app/google-services.json`. | | `FIREBASE_VAPID_KEY` | empty | Firebase Console → Project settings → **Cloud Messaging** → Web configuration, **Generate key pair** if the list is empty. Web Push certificates are not served by the Firebase CLI, so this is always a manual copy. | | `APP_HOST` | empty | Your hostname once you have one. Leave it empty locally; sharing links are simply off. | +| `AUTH_DOMAIN` | empty | Nothing to get, and most games never set it. Empty means sign-in uses the Firebase project's own domain, which works everywhere. | An empty `GOOGLE_WEB_CLIENT_ID` after a successful `firebase:configure` means one thing: **the Google sign-in provider was never enabled**. Firebase creates @@ -160,8 +164,13 @@ Firebase is mandatory on the client. A fresh scaffold contains a throwing `firebase_options.dart` seam so analysis works before project setup, but the app will not start until FlutterFire replaces it with real platform configuration. -1. **Create the project** at console.firebase.google.com with Analytics enabled, - or let step 2 do it: the first run offers to. +1. **Create the project**, either at console.firebase.google.com or by letting + step 2 do it: the first run offers to. The two differ in one respect. Only + the console's create flow links a Google Analytics account, so a project + created through the CLI reports *"Google Analytics not enabled for Project"* + and the engine's automatic events have nowhere to land. Link one afterwards + under Settings → Integrations → Google Analytics. Crashlytics reports either + way. 2. Install and authenticate the official tooling: ```bash diff --git a/docs/ship-it/deploy-the-web-app.md b/docs/ship-it/deploy-the-web-app.md index b57c2e4..54584a7 100644 --- a/docs/ship-it/deploy-the-web-app.md +++ b/docs/ship-it/deploy-the-web-app.md @@ -213,6 +213,49 @@ The combined deploy removes the usual ordering race for web. Android still needs version ordering: publish a compatible Play build before server behavior that requires it. +### The hostname players see when they sign in + +On the web, sign-in runs through Firebase's popup, which loads +`https:///__/auth/handler`, and Google's account chooser names that +host. By default it reads: + +> Sign in to continue to **my-project.firebaseapp.com** + +**This is fine, and most games should leave it alone.** It is cosmetic, it does +not change what sign-in does, and Android never shows it, because the native +Google flow is used there instead. + +If you do want your own name on that screen, it is two independent changes and +either works without the other: + +| To change | Where | Needs a domain | +|---|---|---| +| The product name and logo | Google Cloud Console → APIs & Services → OAuth consent screen | No | +| The hostname in the sentence | `AUTH_DOMAIN` in `app-config.json` | Yes | + +The consent-screen half is the cheaper win and needs no configuration at all. + +The hostname half needs a domain **Firebase Hosting** answers for, because that +is what serves `/__/auth/handler`. It cannot be `APP_HOST`: that name is your +Worker. The two are siblings, `auth.rps.example.com` beside `rps.example.com`. +Add the custom domain under Hosting in the Firebase console, add it under +Identity Platform → Identity providers → Project settings → Add Domain, then +set it: + +```json +{ + "APP_HOST": "rps.example.com", + "AUTH_DOMAIN": "auth.rps.example.com" +} +``` + +Leave `AUTH_DOMAIN` empty and the app uses the project's own domain, which is +the scaffold's state and needs no Firebase Hosting at all. The engine applies +the value at startup rather than expecting you to edit `firebase_options.dart`, +which is generated and would lose the edit on the next +`pnpm firebase:configure`. A value Firebase Hosting does not answer for breaks +web sign-in outright, so set it once the domain resolves, not before. + ### Deliberately splitting the origins A separate static host remains supported. Point `API_BASE_URL` at the Worker, diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 6b88f74..028e0e5 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -388,16 +388,29 @@ const config: Config = { }, { to: "/showcase", label: "Showcase", position: "left" }, { to: "/blog", label: "Changelog", position: "left" }, - // Renders as "0.3.x", the label from `versions.current` above. With - // one version live the dropdown exists to answer "which engine is this - // describing, and what Flutter release pairs with it", which is why the - // compatibility table is pinned into it rather than left in the - // sidebar alone. Once a version is cut it becomes an ordinary version - // switcher and the extra item stays useful. + // Renders as "0.3.x", the label from `versions.current` above. With one + // version live it exists to answer "which engine is this describing", + // and it becomes an ordinary switcher once a version is cut. + // + // Nothing is pinned in via `dropdownItemsAfter`, and the compatibility + // table specifically must not be. Each version entry links to *the doc + // you are currently reading* in that version, so on a versioned page the + // entry is a self-link; adding a versioned doc to the dropdown makes the + // two collide on that doc's own page, where both render with + // `dropdown__link--active` pointing at the same href. Docusaurus' own + // example pins `/versions`, a page outside `/docs` that no version owns, + // which is the case this option is for. Compatibility lives in + // `referenceSidebar` and is reachable there. + // + // `dropdownActiveClassDisabled` because each version entry links to the + // doc you are currently reading, which makes it a self-link on every + // docs page and so permanently "active". Highlighting it says nothing: + // with one version there is no other state it could be in. Docusaurus' + // own example sets this for the same reason. { type: "docsVersionDropdown", position: "right", - dropdownItemsAfter: [{ to: "/docs/reference/compatibility", label: "Compatibility" }], + dropdownActiveClassDisabled: true, }, { href: GITHUB,