Add doc/notification-debug-panel.md as the canonical panel reference. Update ngrok guides, README, and analysis doc to describe independent Backend URL, Test Mode, and Skip JWT Authentication settings, including recommended configs for hosted test servers vs local ngrok.
19 KiB
Android Local Notification Testing — Planning Analysis
Created: 2026-06-02
Source document: local-ios-testing-ngrok.md
Purpose: Plan a future Android counterpart guide by mapping what can be reused from the iOS ngrok workflow and what must be written for Android-specific push, permissions, and OS behavior.
Status: Planning only — does not replace or modify the iOS guide.
Executive summary
The iOS guide’s backend + ngrok + in-app debug panel path is platform-agnostic. Most of sections 1–3, 6, 9 (with log tooling swapped), 10 (with platform: "android"), 12, and parts of 11 can be copied or lightly edited.
Everything involving APNs, Xcode, Apple Developer, iOS capabilities, and iOS background/silent-push caveats must be replaced. Android adds direct FCM delivery (no APNs hop), google-services.json, runtime notification permissions (API 33+), Doze / battery optimization / OEM restrictions, and different force-stop / background semantics.
Existing related docs to cross-link (not duplicate):
- android-physical-device-guide.md — USB,
adb, build/run commands - notification-system-overview.md
- notification-from-api-call.md
- notification-permissions-and-rollovers.md
iOS guide structure (reference map)
| § | iOS doc heading | Reuse for Android |
|---|---|---|
| Intro | Architecture overview | Adapt — swap APNs leg for FCM→device |
| — | Prerequisites | Partial — drop Xcode/APNs; add Android SDK/device |
| 1 | Install and configure ngrok | Reuse unchanged |
| 2 | Start the backend locally | Reuse unchanged |
| 3 | Obtain and use ngrok HTTPS URL | Reuse — wording: “device” not “iPhone” |
| 4 | Generate and open iOS workspace | Rewrite — Android Studio / Capacitor sync |
| 5 | Firebase + APNs setup | Rewrite — Firebase Android only; no APNs |
| 6 | Notification Debug Panel override | Reuse unchanged |
| 7 | Firebase and Xcode checklist | Rewrite — Android manifest / Gradle checklist |
| 8 | iOS-specific testing notes | Rewrite — Android delivery caveats |
| 9 | Recommended debug workflow | Reuse — replace Xcode console with logcat |
| 10 | Sample curl commands | Reuse — change platform to android |
| 11 | Troubleshooting | Partial — keep ngrok/API rows; replace push rows |
| 12 | Key source files | Reuse unchanged |
| 13 | Related docs | Extend — link Android build/device guides |
Sections reusable unchanged (or near-unchanged)
These blocks can be carried into doc/local-android-testing-ngrok.md (proposed name) with at most global find-replace (“iPhone” → “Android device”, “Mac” tunnel audience unchanged).
notification-wakeup-service startup (iOS §1 Terminal A, §2)
- Clone notification-wakeup-service,
npm install,.envfrom.env.example export PORT=3000(or port from that repo’s README)npm run dev- Local verify:
curl -sS http://localhost:3000/health - Firebase Admin service account for the backend (
GOOGLE_APPLICATION_CREDENTIALS) — same project can serve iOS and Android apps
ngrok setup (iOS §1)
brew install ngrok/ngrok/ngrok(or download)ngrok http 3000in a second terminal- Use HTTPS forwarding URL; free tier URL rotation note
- ngrok inspect UI at
http://127.0.0.1:4040
ngrok account creation (iOS §1 “Account and auth token”)
- Sign up at dashboard.ngrok.com
ngrok config add-authtoken YOUR_AUTHTOKEN_HERE
Obtaining HTTPS URL (iOS §3)
- Copy
https://….ngrok-free.appfrom Forwarding line - No trailing slash in debug panel
- Mac-side tunnel test:
export NGROK_URL=…andcurl "$NGROK_URL/health"
Backend override configuration (iOS §6)
- Non-production build required for Notification Debug Panel
- Account → Show All General Advanced Functions →
/dev/notifications - Notification Backend URL, Save Backend URL
localStorage:notificationDebug.backendBaseUrl,notificationDebug.testMode,notificationDebug.bypassAuth- Optional programmatic override via
@/services/notifications(setBackendBaseUrl,setTestMode,setBypassAuth,getNotificationApiBaseUrl)
Debug panel usage (iOS §6 table, §8 “Two Simulate WAKEUP_PING buttons”)
| Control | Android relevance |
|---|---|
| Notification Backend URL | Same |
| Test Mode | Same (testMode in JSON body) |
| Skip JWT Authentication | Same — explicit opt-in for unauthenticated local backends (default off) |
| Register Token Now | Same (POST /notifications/register) |
| Refresh Notifications | Same |
| Simulate WAKEUP_PING (backend) | Same — isolates ngrok + refresh without FCM |
| Wakeup Ping Simulator | Same — exercises handleCapacitorPushNotificationReceived path |
Event Log [Notifications] |
Same |
| Pending Notification Inspector | Same concept; confirm Android plugin inspector behavior in daily-notification-plugin |
testMode usage (iOS §6, §10)
- Default-on when unset in storage (
NotificationDebugConfig.ts) - Sent on register and refresh payloads
- Backend/debug endpoints accept
testMode: truefor dev traffic
Refresh endpoint testing (iOS §9 steps 5, §11 “Refresh endpoint unreachable”)
- Panel Refresh Notifications → expect Event Log + ngrok
POST /notifications/refresh - Simulate WAKEUP_PING (backend button) for API-only path
- Troubleshooting table for network error, 404, wrong port, stale URL
curl examples (iOS §10)
Reuse structure; only payload deltas for Android doc:
export BASE="https://abc123.ngrok-free.app"
$BASE/health— unchanged$BASE/notifications/register— set"platform": "android"$BASE/notifications/refresh— set"platform": "android"$BASE/debug/send-wakeup— unchanged shape; confirm deviceId/token contract in notification-wakeup-service README
App still uses Capacitor.getPlatform() for platform in NotificationService.ts (ios | android).
Shared architecture concepts (intro + silent wake sequence)
Reusable narrative (edit diagram only):
- FCM data message with
data.type = "WAKEUP_PING" - Capacitor
pushNotificationReceived→handleCapacitorPushNotificationReceived() POST {backend}/notifications/refreshwithtestModenextNotifications→applyNotificationRefreshPayload()→ daily-notification-plugin clear + schedule
Repos table (notification-wakeup-service, crowd-funder-for-time-pwa, daily-notification-plugin) — unchanged.
Key source files (iOS §12)
Same files apply on Android Capacitor builds:
NotificationDebugConfig.ts,NotificationDebugEvents.ts,notificationLog.tsNotificationService.ts,NativeNotificationService.tsfirebaseMessagingClient.ts,NotificationDebugPanel.vue,main.capacitor.ts
Recommended debug workflow (iOS §9) — reuse with tooling swap
Steps 1–5, 8–9 unchanged. Replace step 7:
- iOS: Xcode console →
[Notifications] pushNotificationReceived type=WAKEUP_PING - Android:
adb logcatfiltered on app tag /[Notifications](document exact filter in Android guide)
iOS-specific sections — must rewrite for Android
Architecture diagram (intro)
iOS today: Mac → ngrok → app; FCM → APNs → iPhone.
Android doc: FCM → device directly (no APNs). Update ASCII diagram and caption (“silent push” on Android is still FCM data; delivery rules differ).
Prerequisites (intro list)
| iOS prerequisite | Android replacement |
|---|---|
| Mac with Xcode | Android Studio, JDK 17+, ANDROID_HOME, adb — see android-physical-device-guide.md |
| Physical iPhone | Physical Android device (emulator possible for some steps but not representative for Doze/OEM/battery) |
| Firebase with APNs for bundle ID | Firebase with Android app (app.timesafari package name) |
| Non-production build | Same — e.g. build:android:dev / build:android:test |
Remove: “simulator is not sufficient for reliable silent push / APNs”.
Add: emulator vs physical device guidance for FCM and background limits.
§4 — Generate and open the iOS workspace
Replace entirely with Android equivalent:
npm installnpm run build:android:devorbuild:android:test(non-production for debug panel)npx cap sync androidif needed- Open
android/in Android Studio - Run on physical device (USB debugging)
VITE_FIREBASE_*in Capacitor web buildinitializeNativePushAndFirebaseMessaging()inmain.capacitor.ts— same entry point
Do not reference .xcworkspace, signing in Xcode, or build:ios:* except as cross-link to iOS doc.
§5 — Firebase + APNs setup (first-time setup)
Keep (Android-relevant portions only):
- Firebase account / Spark plan sufficient for FCM
- Create Firebase project
- Register Android app in Firebase (package name
app.timesafarifromcapacitor.config.ts) - Download
google-services.json→android/app/(project may gitignore this file — document secure handling) - Firebase Admin service account for notification-wakeup-service — same as iOS §5 tail
Remove entirely:
- Register iOS app in Firebase (or move to “shared project” sidebar: one Firebase project, two apps)
- GoogleService-Info.plist / Xcode drag-and-drop
- Create APNs Authentication Key (.p8)
- Upload APNs key to Firebase
- Enable iOS capabilities (Push Notifications, Background Modes → Remote notifications)
Add in Android guide (see next major section):
- Gradle plugin /
google-servicesclasspath if not already in repo POST_NOTIFICATIONSpermission (API 33+)- Default notification channel / Capacitor Push Notifications Android setup
- SHA-1/SHA-256 only if using Firebase features that require it (note whether wakeup testing needs Play App Signing keys)
§5 verify checklist — iOS-only bullets
Replace:
- “Xcode without Firebase/plist errors” → Android Studio build;
google-services.jsonpresent - “iOS push permission prompt” → Android 13+ notification permission + older grant model
- “content-available style payload” → Android high-priority data message / FCM options as implemented by notification-wakeup-service (document actual payload; no APNs
content-available)
§7 — Firebase and Xcode checklist (iOS)
Replace with Android checklist, e.g.:
| Item | Action |
|---|---|
| Application ID | app.timesafari in capacitor.config.ts, android/app/build.gradle, Firebase Android app |
| google-services.json | In android/app/; not committed if gitignored — local copy per developer |
| Gradle | Google services plugin applied (verify repo’s current build.gradle) |
| Permissions | POST_NOTIFICATIONS (API 33+); manifest entries for FCM |
| FCM token | Debug panel Register Token Now + ngrok POST /notifications/register |
| No APNs | N/A on Android |
§8 — iOS-specific testing notes
Replace with Android-specific sections (draft topics below). Do not port:
- APNs silent delivery / Simulator unreliability (iOS framing)
- Force-quit via app switcher (iOS-specific policy)
- Low Power Mode (iOS) — Android has different battery saver APIs
- Focus / Do Not Disturb (iOS naming)
Port with Android wording:
- Two Simulate WAKEUP_PING buttons table — unchanged behavior
§11 — Troubleshooting (partial)
Reuse as-is:
- Refresh endpoint unreachable (ngrok, URL, 404, CORS note)
- Stale ngrok URL
- Plugin / JWT errors after refresh
Rewrite:
| iOS troubleshooting | Android replacement |
|---|---|
Push permission + VITE_FIREBASE_* + Xcode log |
Permission (runtime POST_NOTIFICATIONS), logcat, Firebase Android config |
| Silent push not waking — backgrounded not force-quit, APNs key, wait 30–120s | FCM high-priority data, force-stop (STOP from settings), Doze, battery optimization, OEM autostart, token mismatch |
| Physical device + provisioning profile | USB debugging, correct build variant, Play vs debug signing if relevant |
§13 — Related docs
Keep iOS-centric links as “see also”; add:
- android-physical-device-guide.md
BUILDING.md— Android build commands (build:android:*)- daily-notification-plugin Android docs (exact alarm, pending inspector on Android)
Android-Specific Topics Required
These sections do not exist in the iOS guide (or exist only by analogy) and must be written for the Android notification testing doc.
Firebase project setup
- Use the same Firebase project as iOS when testing the same backend, or document a dedicated
timesafari-devproject. - Add an Android app with package name
app.timesafari. - Enable Cloud Messaging (default on new projects).
- Download
google-services.jsonand install underandroid/app/. - Note:
android/.gitignoremay excludegoogle-services.json— developers copy locally; never commit secrets.
google-services.json
- Placement:
android/app/google-services.json - Sync after add:
npx cap sync android, rebuild in Android Studio - Verify build merges Firebase config (no “missing google-services” Gradle errors)
- Relationship to
VITE_FIREBASE_*for the web layer / Capacitor JS Firebase initialization
Android notification permissions
- Android 13+ (API 33):
POST_NOTIFICATIONSruntime permission — required for notification display; document interaction with data-only FCM wake (may still deliver to app code when permission denied — verify against current app behavior and document accurately). - Android 12 and below: install-time grant model; fewer runtime prompts.
- App Settings → Notifications — manual enable path for testers.
- Link notification-permissions-and-rollovers.md for product-level permission UX.
FCM token handling
- Token obtained via Capacitor Push Notifications +
firebaseMessagingClient.ts(same JS path as iOS). - Register Token Now in debug panel →
POST /notifications/registerwithplatform: "android". - Token rotation: when to re-register; duplicate skip behavior in panel.
- Ensure notification-wakeup-service stores/sends to the token shown in the panel for
/debug/send-wakeup. - Optional:
adbcannot easily read FCM token — panel is source of truth (same as iOS).
Android background delivery behavior
- FCM data messages handled in foreground/background per Capacitor plugin and
NativeNotificationService.ts. - No APNs intermediary — document expected latency vs iOS.
- High-priority FCM for wakeup testing (align with backend message options).
- App in background vs foreground vs killed — different from iOS “swipe away” story:
- Force stop (Settings → Force stop): delivery often blocked until user launches app again (stricter than iOS “backgrounded”).
- Recent apps swipe: behavior varies by OEM/Android version — document “test with Home button background, not force stop.”
pushNotificationReceived/ listener registration at startup (main.capacitor.ts).
Doze Mode
- Device idle → deferred network and job execution.
- Testing: use
adb shell dumpsys deviceidle(document safe dev-only commands) or unplugged idle wait. - Explain why
/debug/send-wakeupmay succeed on server but device wakes late. - Whitelisting app for tests (developer settings) — use cautiously; note production users won’t do this.
Battery optimization
- Settings → Apps → TimeSafari → Battery → Unrestricted vs Optimized.
- Manufacturer “battery saver” modes that restrict background network.
- Recommend Unrestricted (or equivalent) for local wakeup validation; warn that production users may remain optimized.
OEM restrictions (Samsung, Xiaomi, Oppo, etc.)
- Autostart / Background activity / Battery menus on Samsung, Xiaomi (MIUI), Oppo/ColorOS, Huawei, OnePlus, etc.
- Symptom: FCM works on Pixel but not on OEM device until autostart enabled.
- Provide a short “if wake fails on OEM, check…” checklist without exhaustive per-OEM screenshots (link community docs if needed).
- Physical device testing should include at least one stock-ish device (Pixel) and one OEM device when possible.
Proposed outline for doc/local-android-testing-ngrok.md
Suggested section order mirroring iOS doc for easy maintenance:
- Title, audience, goal (Android physical device + ngrok + wakeup service)
- Architecture overview (FCM direct to Android)
- Prerequisites (Android Studio, device, Firebase Android app, non-prod build)
- ngrok install, account, tunnel (reuse iOS §1)
- Start notification-wakeup-service (reuse iOS §2)
- ngrok HTTPS URL (reuse iOS §3)
- Build and open Android project (new, replaces iOS §4)
- Firebase setup for Android (new, replaces iOS §5 — no APNs)
- Notification Debug Panel (reuse iOS §6)
- Android configuration checklist (new, replaces iOS §7)
- Android-specific testing notes (new, replaces iOS §8)
- Recommended debug workflow (reuse iOS §9 + logcat)
- Sample curl commands (reuse iOS §10 +
platform: "android") - Troubleshooting (merge reusable + Android push rows)
- Key source files (reuse iOS §12)
- Related docs (iOS doc + Android device guide + BUILDING)
Wording and terminology substitutions
When adapting reused sections:
| iOS doc term | Android doc term |
|---|---|
| iPhone | Android phone / device |
| Xcode console | logcat / Android Studio Logcat |
build:ios:dev / test |
build:android:dev / test |
GoogleService-Info.plist |
google-services.json |
| APNs / silent push | FCM data message / high-priority data |
| Bundle ID | Application ID / package name (app.timesafari) |
| Physical iPhone required for APNs | Physical device strongly recommended for Doze/OEM/FCM realism |
platform: "ios" in curl |
platform: "android" |
Gaps to resolve while writing the Android guide
Research during authoring (code + notification-wakeup-service + daily-notification-plugin):
- Exact FCM Android message priority and payload fields for
WAKEUP_PING(parity with iOS data message). - Whether
POST_NOTIFICATIONSdenial blocks data message delivery to JS listeners on API 33+. - Gradle/Firebase plugin versions already in
android/— document exact files to touch. - Android Pending Notification Inspector parity with iOS panel section.
- Whether emulator with Google Play image is acceptable for minimal FCM smoke tests vs mandatory physical device for wakeup SLA testing.
Document maintenance
| Document | Role |
|---|---|
| local-ios-testing-ngrok.md | Canonical iOS + ngrok workflow (unchanged by this analysis) |
| This file | Reuse vs rewrite matrix and Android topic backlog |
Future local-android-testing-ngrok.md |
Operator guide for Android testers |
When backend or debug panel behavior changes, update both platform guides’ shared sections in lockstep (or extract shared “ngrok + debug panel” snippet later — out of scope unless requested).