Notifications
The iOS and Android apps send two local notifications, scheduled on the phone with @capacitor/local-notifications, no server involved: Tank full and Daily bonus ready. A third, New district, is a push through Firebase Cloud Messaging, for players who beat the boss before a district that just shipped. The web builds get none.
When one is scheduled
Section titled “When one is scheduled”When the app goes to the background, player/notifications/localNotices.ts asks planNotice (planNotice.ts, no Phaser) for at most one notice and schedules it. When the app comes back, it is cancelled, so nothing pings while the player is in the game.
- Tank full, when the tank is not full: at the moment the last point comes back (Fuel). From empty that is two and a half hours.
- Daily bonus ready, only when the tank is full and the player claimed a daily bonus today: at the next UTC midnight, when the bonuses reset. A player who never claims one is not reminded of it.
Two limits move the time, never drop the notice: nothing fires between 21:00 and 09:00 on the phone’s clock (it moves to 09:00), and never two on one local day (it moves to 09:00 the next day). Whether a notice fired is read on return: its time had passed. Fuel and the bonus day run on the server’s clock, so the plan is made in server time and shifted by the phone’s own clock when scheduled.
Asking
Section titled “Asking”The setting is off until the player says yes, since Android 13+ and iOS show their system prompt only once. The first time the tank is empty, after Raya’s fuel tip, the game asks “Get pinged when it’s full?” (ui/notifyAsk.ts). Notify me opens the system prompt, Not now leaves it off, and either goes on to the Out of fuel dialog. The question shows once.
Settings has Alerts OFF/ON in the Display card, in the apps only (menu/notificationsToggle.ts). ON asks the system again; when the player refused there before, the toggle goes back to OFF and a toast points to the phone’s settings. The state (on, asked, the pending time, the last day one fired) is in localStorage apart from the save (core/notificationSettings.ts).
New district (push)
Section titled “New district (push)”A local notification cannot know that an update brought a new district, so this one comes from Firebase Cloud Messaging (project drag-racer-rh, the same Google project as Play Games). It uses topics, so no token is stored on the race server: player/notifications/pushTopic.ts subscribes the app to finished-<district id> of the furthest boss the player beat, moving it when they beat the next, each time the app goes to the background. Alerts OFF deletes the token and with it every topic. Auto-init is off on both platforms, so a phone gets no token before Alerts is on.
On release day, after the update with the new district is out:
npm run push:district -- chinatown # prints the messagenpm run push:district -- chinatown --validate # FCM checks it, sends nothingnpm run push:district -- chinatown --send # sends it to finished-harbor--validate and --send need FCM_SERVICE_ACCOUNT: the path to a service account key of drag-racer-rh (Firebase console, Project settings, Service accounts), kept in sops. The text is in pushTopics.ts. This push is outside the one-a-day limit: it comes once per district, about every 4 weeks.
iOS delivers pushes only once an APNs key is uploaded to Firebase (Project settings, Cloud Messaging), which needs the Apple Developer Program. Until then subscribing fails quietly and is tried again on the next background. ios/App/App/App.entitlements holds aps-environment and AppDelegate.swift hands the APNs token to the plugin.
Android
Section titled “Android”Notices are scheduled inexact (isExactNotification: false), so the app does not need the exact-alarm permission and the manifest removes the one the plugin declares. The status bar icon is a white bolt, res/drawable/ic_stat_neon.xml, set in capacitor.config.ts for local notices and in the manifest for pushes. android/app/google-services.json turns on the Google services plugin.