# Notification Debug Panel **Created:** 2026-07-07 **Audience:** Developers testing notification registration, refresh, and WAKEUP_PING flows on native (iOS/Android) dev builds. The **Notification Debug Panel** is a dev-only UI for exercising the same notification orchestration paths the production app uses: FCM token registration, backend refresh, wakeup handling, and local schedule inspection. It does not duplicate scheduling logic. --- ## Access 1. Use a **non-production** build (for example `build:android:dev`, `build:ios:dev`, or `vite dev` with a non-`production` mode). 2. Open **Account** → enable **Show All General Advanced Functions**. 3. Open **Notification Debug Panel** (route `/dev/notifications`). On native platforms, grant notification permission when prompted so FCM token registration and the debug actions work. --- ## Configuration (Backend Testing) Settings persist in `localStorage` via `NotificationDebugConfig.ts`: | Key | Default | Purpose | |-----|---------|---------| | `notificationDebug.backendBaseUrl` | *(unset — use `APP_SERVER`)* | Override which notification server receives API calls | | `notificationDebug.testMode` | `true` | Sent in JSON request bodies (`testMode: true/false`) | | `notificationDebug.bypassAuth` | `false` | When `true`, omit JWT `Authorization` headers on notification API calls | All notification API requests (`/notifications/register`, `/notifications/refresh`, `/debug/send-wakeup`, etc.) obtain headers through `getNotificationApiHeaders()` in `notificationApiAuth.ts`. ### Notification Backend URL Paste a base URL (no trailing slash) and tap **Save Backend URL**. This changes **only** which server the app calls (`getNotificationApiBaseUrl()`). It does **not** disable JWT authentication. Leave empty to use the built-in default (`APP_SERVER` from `VITE_APP_SERVER`). ### Test Mode When enabled (default if never saved), register and refresh requests include `"testMode": true` in the JSON body. The backend can use this to return dev-friendly schedules or route test traffic separately from production. Test Mode is **independent of authentication**. It does not control whether `Authorization` headers are sent. ### Skip JWT Authentication (Local Development Only) When **off** (default), the app resolves the active DID and sends `Authorization: Bearer …` on notification API calls. When **on**, requests include only `Content-Type: application/json` — for local servers (localhost or ngrok) that intentionally accept unauthenticated notification requests during development. Enable this **only** for local development backends that do not require JWT. Hosted shared test servers that require normal app authentication should leave this **off**. The panel **Backend Status** section shows the active URL, `testMode`, and `bypassAuth` values. --- ## Recommended settings ### Hosted test server Example: `https://test-notify-api.timesafari.app` | Setting | Value | |---------|-------| | **Notification Backend URL** | `https://test-notify-api.timesafari.app` | | **Test Mode** | **ON** (if the server expects `testMode: true`) | | **Skip JWT Authentication** | **OFF** | Ensure the app has an **active identity (DID)** with a valid endorser session so JWT headers can be built. ### Local localhost / ngrok development Example: `https://abc123.ngrok-free.app` or `http://127.0.0.1:3000` | Setting | Value | |---------|-------| | **Notification Backend URL** | Your local or ngrok URL | | **Test Mode** | **ON** or **OFF** — match what your local **notification-wakeup-service** expects | | **Skip JWT Authentication** | **ON** only if the local server accepts unauthenticated requests; **OFF** if it validates JWT like production | --- ## Backend Testing actions | Action | What it does | |--------|----------------| | **Register Token Now** | `POST {backend}/notifications/register` with current FCM token, `deviceId`, `platform`, and `testMode`. Forces re-registration (bypasses duplicate-token skip). | | **Refresh Notifications** | `POST {backend}/notifications/refresh` — same path used after a real WAKEUP_PING. Applies returned schedule to the native plugin. | | **Simulate WAKEUP_PING (Local)** | Calls the refresh API directly (no FCM). Quick test of backend URL + auth + refresh parsing without push delivery. | | **Send Real WAKEUP_PING** | `POST {backend}/debug/send-wakeup`; server sends a real FCM data message with `data.type = "WAKEUP_PING"`. Exercises backend → FCM → Capacitor listener → refresh → reschedule. Background the app before expecting delivery. | **Current FCM Token** displays the last token from Capacitor/Firebase registration. **Event Log** shows the last 100 `[Notifications]` messages (also visible in logcat / Xcode console on native). --- ## Other panel sections | Section | Purpose | |---------|---------| | **Mock Timing Presets** | Interval for mock refresh timestamps (30 sec – 10 min). | | **Trigger Mock Refresh** | Applies synthetic future timestamps locally — no backend call. | | **Wakeup Ping Simulator** | Runs the production push handler with a synthetic `WAKEUP_PING` payload (no FCM, no backend). | | **Flood Test** | Runs 20 sequential mock refreshes (stress test). | | **Pending Notification Inspector** | Lists locally scheduled notifications (iOS; Android may show unavailable). | | **Clear Notifications** | Clears/cancels all plugin-scheduled notifications on native. | --- ## Programmatic override (optional) From a WebView dev console (`chrome://inspect` on Android, Safari Web Inspector on iOS): ```javascript import { setBackendBaseUrl, setTestMode, setBypassAuth, getNotificationApiBaseUrl, } from "@/services/notifications"; setBackendBaseUrl("https://abc123.ngrok-free.app"); setTestMode(true); setBypassAuth(true); // local dev only getNotificationApiBaseUrl(); ``` --- ## Troubleshooting ### 401 Unauthorized (`registerToken failed: unauthorized`) **Likely causes:** JWT required but **Skip JWT Authentication** is off and the session is missing or expired; or JWT sent but the server rejected it. **Checks:** 1. Panel **Backend Status** → `bypassAuth: false` for hosted servers. 2. App has an active DID and endorser login. 3. Event Log: look for `Using authenticated notification request` vs `Using debug unauthenticated notification request`. 4. For hosted test server: keep **Skip JWT Authentication** **OFF**. **Fixes:** Sign in / restore identity; refresh endorser session; for local ngrok without JWT support, enable **Skip JWT Authentication**. ### `registerToken auth unavailable` / `Waiting for auth before registration` The app deferred registration because JWT could not be built (no active DID or empty token) and **Skip JWT Authentication** is **off**. **Fixes:** Complete identity setup in the app, or enable **Skip JWT Authentication** only for an intentionally unauthenticated local backend. ### Failed to fetch / network error **Likely causes:** Backend down, wrong URL, stale ngrok tunnel, device offline, or TLS/certificate issues. **Checks:** Panel **Backend Status** URL; `curl -sS "$URL/health"` from your machine; ngrok inspect UI for incoming requests. **Fixes:** Restart backend and ngrok; **Save Backend URL** with the current HTTPS forwarding URL (no trailing slash). ### Backend unreachable / no requests in ngrok Same as above. Confirm the **Active** URL in the panel matches your running tunnel or local server port. ### Register succeeds but Send Real WAKEUP_PING does not trigger refresh **Real WAKEUP_PING success** only means the backend accepted the wakeup request and attempted FCM delivery. Missing `pushNotificationReceived` / `Refresh completed (WAKEUP_PING)` indicates an FCM delivery or background execution issue — not necessarily a bad wakeup API call. **Checks:** App backgrounded (not force-stopped); FCM token matches registration; **Simulate WAKEUP_PING (Local)** works (isolates FCM from refresh API). See platform-specific guides for extended ngrok and FCM workflows: - [local-android-testing-ngrok.md](./local-android-testing-ngrok.md) - [local-ios-testing-ngrok.md](./local-ios-testing-ngrok.md) --- ## Key source files | File | Purpose | |------|---------| | `src/components/dev/NotificationDebugPanel.vue` | Dev UI | | `src/services/notifications/NotificationDebugConfig.ts` | Backend URL, testMode, bypassAuth persistence | | `src/services/notifications/notificationApiAuth.ts` | JWT vs unauthenticated headers | | `src/services/notifications/notificationApiDebugMode.ts` | Auth bypass gate | | `src/services/notifications/NotificationDebugService.ts` | Panel action handlers | | `src/services/notifications/NotificationService.ts` | `POST /notifications/register` | | `src/services/notifications/NativeNotificationService.ts` | `POST /notifications/refresh`, WAKEUP_PING handler | --- ## Related docs - [notification-system-overview.md](./notification-system-overview.md) - [notification-from-api-call.md](./notification-from-api-call.md) - [local-android-testing-ngrok.md](./local-android-testing-ngrok.md) - [local-ios-testing-ngrok.md](./local-ios-testing-ngrok.md)