From a890904b6c9a7e375206fe3a6a52400ddce74cb4 Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Sun, 6 Sep 2026 14:28:03 +0200 Subject: [PATCH] docs(mobile): document the full push-notification setup and its gotchas Push notifications went from "wired but not deliverable" to fully working end-to-end this session. Records the three separate one-time setup pieces (EAS project id, FCM credentials baked at prebuild time, and the FCM V1 vs. Legacy service-account-key slot mixup that produced a confusing "Unable to retrieve the FCM server key" error) so a future session doesn't have to rediscover them. Co-Authored-By: Claude Sonnet 5 --- mobile/README.md | 70 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/mobile/README.md b/mobile/README.md index 1d80929..c227225 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -17,10 +17,10 @@ comments, repost, pet profile detail (view/follow/edit/followers-following), stories (tray/creation/viewer), video playback, search, health tracking (consent gate + weight/vet/vaccine logs), milestones, and message sending/receiving are done and verified end-to-end against the live backend. -Push notifications are wired end-to-end but not yet deliverable — see "Push -notifications" below. Android is the priority platform; iOS builds need a -Mac + Apple Developer account and come later (same codebase, no separate -source tree — see "Android-first, then iOS" below). +Push notifications are fully working — see "Push notifications" below. +Android is the priority platform; iOS builds need a Mac + Apple Developer +account and come later (same codebase, no separate source tree — see +"Android-first, then iOS" below). ## Architecture @@ -206,9 +206,10 @@ separately. "Starting with Android" means: budgeted in `../mobil.md`) before an internal test track can be created. - iOS needs a Mac (for `expo run:ios` / EAS builds with credentials) and an Apple Developer account (99 €/year) — deferred until Android is validated. -- EAS Build (`eas.json`, not yet added) is the recommended path for - producing signed Android/iOS binaries without a local Android - Studio/Xcode toolchain; add it when a first real build is needed. +- EAS Build (`eas.json` — added, currently just default profiles) is the + recommended path for producing signed Android/iOS binaries without a + local Android Studio/Xcode toolchain; no EAS Build has been run yet, the + file only exists so far because `eas credentials` requires it. ## What's not built yet @@ -229,17 +230,54 @@ emulator. Still missing, roughly in the order they'd unblock the most: - No i18n — the whole app is hardcoded German text, matching the sign-in screen -### Push notifications — wired but not yet deliverable +### Push notifications `usePushNotifications` (in `app/_layout.tsx`) requests notification permission and registers the resulting Expo push token with the backend (`owner.registerPushToken`); `src/lib/notify.ts` on the web side sends a best-effort push through Expo's push API alongside every in-app -Notification it creates (follow, reaction, comment, message, ...). Minting -a real token needs an **EAS project id** (`app.json`'s -`extra.eas.projectId`, set once via `eas init` — free, no EAS Build -required, but does need an Expo account login). Until that's done, the -permission prompt shows and is granted, but `getExpoPushTokenAsync` has -nothing to attach to and the hook no-ops with a console warning -(`[push] No EAS project id configured...`) — confirmed via `adb logcat` on -2026-09-06. No Owner row has an `expoPushToken` set until this is run. +Notification it creates (follow, reaction, comment, message, ...). +Verified end-to-end on 2026-09-06: permission → real Expo push token → +registered in the DB → delivered to the Android emulator's notification +tray. + +Getting here needed three separate one-time setup pieces, each easy to +half-do and end up with a silent non-working state: + +1. **EAS project id** (`app.json`'s `extra.eas.projectId`) — via + `eas login` + `eas init --account pawfeed`. Without this, + `getExpoPushTokenAsync` has nothing to attach to and the hook no-ops + with `[push] No EAS project id configured...`. +2. **FCM credentials for Android** (`app.json`'s `googleServicesFile` → + `google-services.json`, gitignored — redownload from the Firebase + Console's `org.pawfeed.app` Android app on a fresh clone). This is + baked into the native project at `expo prebuild` time, so adding it + needs a real rebuild (`expo prebuild --platform android` + `expo run:android`), + unlike the EAS project id which the dev client picks up from a plain + JS reload. +3. **The FCM *service account key* uploaded to EAS** via + `eas credentials -p android` (needs `eas.json` to exist first, hence + that file). The credentials manager has two separate Android push + slots — **"FCM Legacy"** (a plain API key string, and Google shut the + underlying Legacy API down in 2024) and **"FCM V1"** (a Google Service + Account JSON key, what Firebase Console's "generate new private key" + actually gives you). Uploading the service-account JSON to the wrong + slot leaves EAS's send API returning + `Unable to retrieve the FCM server key for the recipient's app` even + though a real push token exists and permission is granted — that + symptom means step 3 landed in the wrong slot, not that steps 1–2 are + broken. + +Also note: `getExpoPushTokenAsync` can hang indefinitely rather than +reject if the device can't reach Google's registration backend — the hook +wraps it in a 15s timeout so a real failure surfaces instead of permanent +silence. + +**Deploy note:** `owner.registerPushToken` is a *web* backend change. +Since the mobile app's default `EXPO_PUBLIC_API_URL` is the live +`pawfeed.org` deployment, this endpoint doesn't exist there until the web +app is redeployed too (`ssh` to the NAS, `git pull`, `./docker/rolling-deploy.sh` +— see root `CLAUDE.md`'s deployment section). This is the one case where +a "mobile" feature session still needs a web deploy — [[project_mobile_deployment_target]]'s +"mobile changes need no NAS deploy" is about `mobile/` code itself, not +backend endpoints mobile happens to call.