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.
This commit is contained in:
407
doc/local-ios-testing-ngrok.md
Normal file
407
doc/local-ios-testing-ngrok.md
Normal file
@@ -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 <PORT>`.
|
||||
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**.
|
||||
Reference in New Issue
Block a user