From 8864a2049ba618c620fbd591011e6d951cfbce9c Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Mon, 18 May 2026 21:22:53 +0800 Subject: [PATCH] docs(notifications): add local iOS ngrok testing guide for wakeup service Document Mac backend + ngrok + physical iPhone setup, debug panel overrides, Firebase/APNs checklist, curl examples, and troubleshooting for WAKEUP_PING flows. --- doc/local-ios-testing-ngrok.md | 407 +++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 doc/local-ios-testing-ngrok.md diff --git a/doc/local-ios-testing-ngrok.md b/doc/local-ios-testing-ngrok.md new file mode 100644 index 00000000..d3cd70fd --- /dev/null +++ b/doc/local-ios-testing-ngrok.md @@ -0,0 +1,407 @@ +# Local iOS Testing with ngrok (notification-wakeup-service) + +**Last updated:** 2026-05-18 +**Audience:** Developers on **crowd-funder-for-time-pwa**, **daily-notification-plugin**, and **notification-wakeup-service** +**Goal:** Exercise silent push wake (`WAKEUP_PING`), FCM token registration, and notification refresh against a Mac-hosted backend reachable from a physical iPhone. + +--- + +## Architecture overview + +End-to-end flow when testing New Activity / silent wake on a physical iPhone: + +```text +┌─────────────────────┐ HTTPS ┌──────────────────────┐ +│ Mac (localhost) │ ◄───────────── │ ngrok edge │ +│ notification- │ tunnel │ (public HTTPS URL) │ +│ wakeup-service │ └──────────┬───────────┘ +└──────────┬──────────┘ │ + │ │ fetch + │ POST /notifications/refresh │ POST /notifications/register + │ ▼ + │ ┌──────────────────────┐ + │ │ crowd-funder-for- │ + │ │ time-pwa (Capacitor │ + │ │ iOS on iPhone) │ + │ └──────────┬───────────┘ + │ │ + │ FCM data message (WAKEUP_PING) │ daily-notification-plugin + ▼ ▼ (local schedule replace) +┌─────────────────────┐ ┌──────────────────────┐ +│ Firebase Cloud │ ──APNs──────► │ iPhone (physical) │ +│ Messaging │ silent push │ app.timesafari │ +└─────────────────────┘ └──────────────────────┘ +``` + +### Repos and responsibilities + +| Repo | Role | +|------|------| +| **notification-wakeup-service** | HTTP API: device registration, refresh payload (`nextNotifications`), health, debug wakeup send | +| **crowd-funder-for-time-pwa** | Capacitor app: FCM token, `POST /notifications/register` & `/refresh`, handles `WAKEUP_PING` push | +| **daily-notification-plugin** | Native iOS/Android: clear + reschedule local notifications from refresh timestamps | + +### Silent wake sequence (production path) + +1. Backend (or `/debug/send-wakeup`) sends an FCM **data** message with `data.type = "WAKEUP_PING"`. +2. APNs delivers to the device (best-effort; see iOS caveats below). +3. Capacitor `pushNotificationReceived` fires → `handleCapacitorPushNotificationReceived()`. +4. App calls `POST {backend}/notifications/refresh` with `testMode` (from debug config). +5. Backend returns `nextNotifications: [{ timestamp }, ...]`. +6. App calls `applyNotificationRefreshPayload()` → plugin clears and schedules new local alarms. + +Console and debug panel lines are prefixed with **`[Notifications]`** (see `NotificationDebugEvents.ts`). + +--- + +## Prerequisites + +- Mac with Xcode, Node.js 18+, and the **notification-wakeup-service** repo cloned and runnable +- Physical iPhone (USB or wireless debugging) — **simulator is not sufficient** for reliable silent push / APNs behavior +- ngrok account (free tier is enough for dev) +- Firebase project with APNs configured for the iOS app bundle ID +- Non-production app build (Notification Debug Panel is dev-only) + +--- + +## 1. Install and configure ngrok (macOS) + +### Install + +```bash +# Homebrew +brew install ngrok/ngrok/ngrok +``` + +Or download from [https://ngrok.com/download](https://ngrok.com/download). + +### Account and auth token + +1. Sign up at [https://dashboard.ngrok.com/signup](https://dashboard.ngrok.com/signup). +2. Copy your authtoken from **Your Authtoken** in the dashboard. +3. Configure the CLI: + +```bash +ngrok config add-authtoken YOUR_AUTHTOKEN_HERE +``` + +### Start a tunnel to the wakeup service + +Assume the service listens on port **3000** (confirm in **notification-wakeup-service** `README` or `.env`): + +```bash +# Terminal A — backend (see next section) +cd /path/to/notification-wakeup-service +npm install +npm run dev # or whatever starts the HTTP server on :3000 + +# Terminal B — ngrok +ngrok http 3000 +``` + +ngrok prints a forwarding URL, for example: + +```text +Forwarding https://abc123.ngrok-free.app -> http://localhost:3000 +``` + +Use the **HTTPS** URL (not `http://127.0.0.1:3000`). The iPhone cannot reach your Mac’s localhost without the tunnel. + +> **Note:** Free ngrok URLs change every time you restart ngrok unless you use a reserved domain (paid). Update the app debug override whenever the URL changes. + +--- + +## 2. Start the backend locally + +Example (adjust to match **notification-wakeup-service**): + +```bash +cd /path/to/notification-wakeup-service +cp .env.example .env # if present +# Set Firebase service account, PORT, etc. per that repo's docs +export PORT=3000 +npm run dev +``` + +Verify locally before ngrok: + +```bash +curl -sS http://localhost:3000/health +``` + +Expected: HTTP 200 and a JSON body indicating the service is up (exact shape depends on that repo). + +--- + +## 3. Obtain and use the ngrok HTTPS URL + +1. Run `ngrok http `. +2. Copy the `https://….ngrok-free.app` host from the **Forwarding** line. +3. Do **not** add a trailing slash when saving in the app (the debug config trims it). +4. Optional: open `http://127.0.0.1:4040` (ngrok web UI) to inspect requests and responses while testing. + +Test through the tunnel from your Mac: + +```bash +export NGROK_URL="https://abc123.ngrok-free.app" +curl -sS "$NGROK_URL/health" +``` + +--- + +## 4. Build and run the iOS app (physical device) + +From **crowd-funder-for-time-pwa**: + +```bash +npm install +npm run build:ios:dev # or build:ios:test — non-production for debug panel +``` + +Open the generated Xcode workspace, select your **physical iPhone**, enable signing, and Run. + +Ensure `VITE_FIREBASE_*` variables are set for the Capacitor build you use (see `.env` / build docs). Native push registration runs at startup via `initializeNativePushAndFirebaseMessaging()` in `main.capacitor.ts`. + +--- + +## 5. Configure the Notification Debug Panel backend override + +The app normally calls `APP_SERVER` (from `VITE_APP_SERVER`). For local wakeup testing, override the notification API base URL without rebuilding. + +### Open the panel + +1. Use a **non-production** bundle (e.g. dev/test build). +2. **Account** → enable **Show All General Advanced Functions**. +3. Open **Notification Debug Panel** (route `/dev/notifications`). + +### Backend Testing section + +| Control | Purpose | +|---------|---------| +| **Notification Backend URL** | Paste ngrok HTTPS URL → **Save Backend URL** | +| **Test Mode** | Sends `testMode: true` on register/refresh (default on when unset in storage) | +| **Register Token Now** | `POST /notifications/register` with current FCM token | +| **Refresh Notifications** | `POST /notifications/refresh` (same as post-wakeup flow) | +| **Simulate WAKEUP_PING** | Calls refresh API directly (no FCM) — quick backend test | +| **Event Log** | Shared `[Notifications]` panel log (100 entries) | + +Persistence: `localStorage` keys `notificationDebug.backendBaseUrl` and `notificationDebug.testMode` (`NotificationDebugConfig.ts`). + +### Programmatic override (optional) + +From Safari Web Inspector or a dev console attached to the WebView: + +```javascript +import { + setBackendBaseUrl, + setTestMode, + getNotificationApiBaseUrl, +} from "@/services/notifications"; + +setBackendBaseUrl("https://abc123.ngrok-free.app"); +setTestMode(true); +getNotificationApiBaseUrl(); // → ngrok URL +``` + +--- + +## 6. Firebase and Xcode checklist (iOS) + +| Item | Action | +|------|--------| +| **Bundle ID** | Match Capacitor `appId` (`app.timesafari` in `capacitor.config.ts`) to Firebase iOS app and Xcode target | +| **APNs auth key** | Firebase Console → Project Settings → Cloud Messaging → upload **APNs Authentication Key** (.p8) or certificates | +| **Push Notifications** | Xcode target → **Signing & Capabilities** → **+ Capability** → **Push Notifications** | +| **Background Modes** | Enable **Remote notifications** (and any others required by your plugin docs) | +| **GoogleService-Info.plist** | Present in the iOS target if using Firebase iOS SDK paths in your build | +| **FCM token** | Confirm **Register Token Now** succeeds in the debug panel and ngrok shows `POST /notifications/register` | + +Silent/data pushes used for wake typically use a **content-available** style payload; confirm **notification-wakeup-service** and Firebase message format match what `handleCapacitorPushNotificationReceived` expects (`data.type === "WAKEUP_PING"`). + +--- + +## 7. iOS-specific testing notes + +### Physical device required + +- APNs silent delivery and background wake behavior are **not** representative on the iOS Simulator. +- Always validate on a plugged-in or trusted wireless device with a development provisioning profile. + +### Silent push is best-effort + +- iOS may **delay or coalesce** background pushes, especially on battery saver or under load. +- A successful `/debug/send-wakeup` from the server does not guarantee immediate app wake. + +### Force-quit limitations + +- If the user **swipes the app away** from the app switcher, iOS often **will not** deliver background notifications until the user launches the app again. +- Test with the app **backgrounded** (home button / gesture), not force-quit, when validating wake. + +### Low Power Mode and Focus + +- **Low Power Mode** can reduce background execution. +- **Focus / Do Not Disturb** may affect notification presentation (separate from silent data wake, but confusing during tests). + +### Two “Simulate WAKEUP_PING” buttons + +| Button | Behavior | +|--------|----------| +| **Backend Testing → Simulate WAKEUP_PING** | Skips FCM; calls refresh API only (ngrok path test) | +| **Wakeup Ping Simulator** (lower on panel) | Runs production handler with synthetic `WAKEUP_PING` payload | + +Use the backend button to verify ngrok + refresh; use the simulator to verify handler + refresh chaining. + +--- + +## 8. Recommended debug workflow + +1. Start **notification-wakeup-service** on the Mac. +2. Start **ngrok** and copy the HTTPS URL. +3. Set URL + **Test Mode** in the Notification Debug Panel; confirm **Backend Status**. +4. Tap **Register Token Now** → confirm ngrok request and `[Notifications] Token registration success`. +5. Tap **Refresh Notifications** → confirm `Refresh completed in Nms (scheduled X)` in Event Log and ngrok `POST /notifications/refresh`. +6. From the backend, call **`/debug/send-wakeup`** (see curl below) with the registered `deviceId` / FCM token as required by that service. +7. Watch **Xcode console** for `[Notifications] pushNotificationReceived type=WAKEUP_PING` and refresh timing lines. +8. Open **ngrok inspect UI** (`http://127.0.0.1:4040`) to correlate requests. +9. Use **Pending Notification Inspector** on the panel to see locally scheduled fires after refresh. + +--- + +## 9. Sample curl commands + +Set your tunnel base URL: + +```bash +export BASE="https://abc123.ngrok-free.app" +``` + +### Health + +```bash +curl -sS -w "\nHTTP %{http_code}\n" "$BASE/health" +``` + +### Register device (mirror app payload) + +```bash +curl -sS -X POST "$BASE/notifications/register" \ + -H "Content-Type: application/json" \ + -d '{ + "deviceId": "00000000-0000-4000-8000-000000000001", + "fcmToken": "YOUR_FCM_TOKEN_FROM_DEBUG_PANEL", + "platform": "ios", + "testMode": true + }' +``` + +### Refresh (mirror app payload) + +```bash +curl -sS -X POST "$BASE/notifications/refresh" \ + -H "Content-Type: application/json" \ + -d '{ + "platform": "ios", + "testMode": true + }' +``` + +Example success body shape (actual fields may vary by service version): + +```json +{ + "shouldNotify": true, + "nextNotifications": [ + { "timestamp": 1710000000000 }, + { "timestamp": 1710003600000 } + ] +} +``` + +The app schedules those timestamps via **daily-notification-plugin** (`applyNotificationRefreshPayload` in `NativeNotificationService.ts`). + +### Send wakeup push (debug) + +Exact path and body depend on **notification-wakeup-service**; typical pattern: + +```bash +curl -sS -X POST "$BASE/debug/send-wakeup" \ + -H "Content-Type: application/json" \ + -d '{ + "deviceId": "00000000-0000-4000-8000-000000000001", + "testMode": true + }' +``` + +Confirm parameters (token vs deviceId, auth headers) in that repo’s README or OpenAPI spec. + +--- + +## 10. Troubleshooting + +### Refresh endpoint unreachable + +| Symptom | Checks | +|---------|--------| +| Network error in Event Log | ngrok running? URL saved without typo/trailing slash? | +| HTTP 404 | Tunnel port matches backend `PORT`; path is `/notifications/refresh` | +| CORS (web only) | Native Capacitor fetch usually avoids browser CORS; if testing in Safari PWA, configure CORS on the service | +| ngrok browser warning | Free tier may show an interstitial for browser clients; native `fetch` from the app is usually unaffected | + +### Token registration failures + +- Push permission granted on the device? +- Firebase `VITE_FIREBASE_*` env vars baked into the build? +- `[Notifications] Token registration failure` in Xcode — read HTTP status in ngrok inspect +- Duplicate token skip: panel may show “skipped (duplicate)”; use **Register Token Now** to force re-register + +### Silent push not waking the app + +- App **backgrounded**, not force-quit +- Physical device, correct provisioning profile +- APNs key uploaded to Firebase; bundle ID matches +- FCM message includes `data.type = "WAKEUP_PING"` (see `NativeNotificationService.ts`) +- Server actually sent to the **same** FCM token shown in the debug panel +- Wait 30–120s — delivery is not instant +- Try **Simulate WAKEUP_PING** (refresh API) to isolate app/plugin from FCM/APNs + +### Notifications duplicating + +- Multiple refresh calls (flood test, repeated wakeups) each **replace** schedule via clear + schedule — check Event Log for repeated refreshes +- Separate issue: Daily Reminder vs New Activity both scheduling — see `doc/notification-new-activity-lay-of-the-land.md` + +### Stale ngrok URL + +- After restarting ngrok, update **Notification Backend URL** in the panel and tap **Save** +- Or clear override (empty field + Save) only if you intend to hit `APP_SERVER` again + +### Plugin / JWT errors after refresh + +- Refresh calls `configureNativeFetcherIfReady()` before scheduling — ensure an **active DID** and endorser API settings exist in the app DB +- See `doc/notification-from-api-call.md` and `nativeFetcherConfig.ts` + +--- + +## 11. Key source files (crowd-funder-for-time-pwa) + +| File | Purpose | +|------|---------| +| `src/services/notifications/NotificationDebugConfig.ts` | Backend URL + testMode override | +| `src/services/notifications/NotificationDebugEvents.ts` | Panel event log + `logNotification()` | +| `src/services/notifications/notificationLog.ts` | Structured log helpers | +| `src/services/notifications/NotificationService.ts` | `POST /notifications/register` | +| `src/services/notifications/NativeNotificationService.ts` | Refresh, `WAKEUP_PING`, schedule replace | +| `src/services/notifications/firebaseMessagingClient.ts` | Capacitor push listeners | +| `src/components/dev/NotificationDebugPanel.vue` | Dev UI | +| `src/main.capacitor.ts` | Native push init at startup | + +--- + +## 12. Related docs + +- [Notification Debug Panel (README)](../README.md#notification-debug-panel-dev-builds) +- [notification-system-overview.md](./notification-system-overview.md) +- [notification-from-api-call.md](./notification-from-api-call.md) +- [notification-new-activity-lay-of-the-land.md](./notification-new-activity-lay-of-the-land.md) +- [BUILDING.md](../BUILDING.md) — iOS build commands + +For plugin-native behavior (exact alarm, iOS pending inspector), see **daily-notification-plugin** documentation. For FCM payload format and `/debug/send-wakeup` contract, see **notification-wakeup-service**.