From 2afe748292c947c11ceeebb8db449eb4e2bd3f38 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Thu, 3 Sep 2026 21:12:05 +0800 Subject: [PATCH] Send the ngrok skip-browser-warning header only when the Notification Debug Panel backend override is set. WebView fetch to free ngrok was blocked by the interstitial (no CORS ACAO). Keep production notify-api requests unchanged. --- .../NotificationDebugConfig.test.ts | 50 +++++++++++++++++++ .../notifications/NotificationDebugConfig.ts | 17 +++++++ src/services/notifications/index.ts | 3 ++ .../notifications/notificationApiAuth.ts | 11 +++- 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/services/notifications/NotificationDebugConfig.test.ts diff --git a/src/services/notifications/NotificationDebugConfig.test.ts b/src/services/notifications/NotificationDebugConfig.test.ts new file mode 100644 index 00000000..591f2dcb --- /dev/null +++ b/src/services/notifications/NotificationDebugConfig.test.ts @@ -0,0 +1,50 @@ +jest.mock("@/constants/app", () => ({ + DEFAULT_NOTIFY_API_SERVER: "https://notify-api.timesafari.app", +})); + +import { + getNotificationDebugOverrideHeaders, + NGROK_SKIP_BROWSER_WARNING_HEADER, + NGROK_SKIP_BROWSER_WARNING_VALUE, + setBackendBaseUrl, +} from "./NotificationDebugConfig"; + +const STORAGE_KEY_BACKEND_URL = "notificationDebug.backendBaseUrl"; + +describe("getNotificationDebugOverrideHeaders", () => { + const memory = new Map(); + + beforeEach(() => { + memory.clear(); + Object.defineProperty(globalThis, "localStorage", { + configurable: true, + value: { + getItem: (key: string) => memory.get(key) ?? null, + setItem: (key: string, value: string) => { + memory.set(key, value); + }, + removeItem: (key: string) => { + memory.delete(key); + }, + }, + }); + }); + + it("adds the ngrok skip header when the debug backend override is set", () => { + setBackendBaseUrl("https://detail-frown-machine.ngrok-free.dev"); + + expect(getNotificationDebugOverrideHeaders()).toEqual({ + [NGROK_SKIP_BROWSER_WARNING_HEADER]: NGROK_SKIP_BROWSER_WARNING_VALUE, + }); + expect(memory.get(STORAGE_KEY_BACKEND_URL)).toBe( + "https://detail-frown-machine.ngrok-free.dev", + ); + }); + + it("omits the ngrok skip header for the default/production notification API", () => { + setBackendBaseUrl(""); + + expect(getNotificationDebugOverrideHeaders()).toEqual({}); + expect(memory.get(STORAGE_KEY_BACKEND_URL)).toBeUndefined(); + }); +}); diff --git a/src/services/notifications/NotificationDebugConfig.ts b/src/services/notifications/NotificationDebugConfig.ts index 7b1f9de7..53c0934d 100644 --- a/src/services/notifications/NotificationDebugConfig.ts +++ b/src/services/notifications/NotificationDebugConfig.ts @@ -10,6 +10,10 @@ const STORAGE_KEY_BACKEND_URL = "notificationDebug.backendBaseUrl"; const STORAGE_KEY_TEST_MODE = "notificationDebug.testMode"; const STORAGE_KEY_BYPASS_AUTH = "notificationDebug.bypassAuth"; +/** Free-ngrok interstitial bypass; only sent when the debug backend override is set. */ +export const NGROK_SKIP_BROWSER_WARNING_HEADER = "ngrok-skip-browser-warning"; +export const NGROK_SKIP_BROWSER_WARNING_VALUE = "true"; + /** Trim whitespace, drop trailing slash; empty input becomes null. */ export function normalizeNotificationBackendUrl(url: string): string | null { const trimmed = url.trim(); @@ -113,3 +117,16 @@ export function getNotificationApiBaseUrl(): string { DEFAULT_NOTIFY_API_SERVER ); } + +/** + * Extra headers for notification API calls when the Debug Panel backend URL + * override is set. Production/default hosts do not get this header. + */ +export function getNotificationDebugOverrideHeaders(): Record { + if (!getBackendBaseUrl()) { + return {}; + } + return { + [NGROK_SKIP_BROWSER_WARNING_HEADER]: NGROK_SKIP_BROWSER_WARNING_VALUE, + }; +} diff --git a/src/services/notifications/index.ts b/src/services/notifications/index.ts index 6ca4f9af..56cb39fb 100644 --- a/src/services/notifications/index.ts +++ b/src/services/notifications/index.ts @@ -17,7 +17,10 @@ export { getBackendBaseUrl, getBypassAuth, getNotificationApiBaseUrl, + getNotificationDebugOverrideHeaders, getTestMode, + NGROK_SKIP_BROWSER_WARNING_HEADER, + NGROK_SKIP_BROWSER_WARNING_VALUE, normalizeNotificationBackendUrl, setBackendBaseUrl, setBypassAuth, diff --git a/src/services/notifications/notificationApiAuth.ts b/src/services/notifications/notificationApiAuth.ts index ede6c7ef..e6739797 100644 --- a/src/services/notifications/notificationApiAuth.ts +++ b/src/services/notifications/notificationApiAuth.ts @@ -7,6 +7,7 @@ import { getHeaders } from "@/libs/endorserServer"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { logger } from "@/utils/logger"; +import { getNotificationDebugOverrideHeaders } from "./NotificationDebugConfig"; import { shouldBypassNotificationAuth } from "./notificationApiDebugMode"; import { logNotification } from "./NotificationDebugEvents"; @@ -82,7 +83,14 @@ export async function getNotificationApiHeaders( if (kind) { logDebugUnauthenticatedNotificationRequest(); } - return { ok: true, authenticated: false, headers: { ...DEBUG_HEADERS } }; + return { + ok: true, + authenticated: false, + headers: { + ...DEBUG_HEADERS, + ...getNotificationDebugOverrideHeaders(), + }, + }; } const did = await getActiveNotificationDid(); @@ -120,6 +128,7 @@ export async function getNotificationApiHeaders( headers: { "Content-Type": headers["Content-Type"], Authorization: headers.Authorization, + ...getNotificationDebugOverrideHeaders(), }, }; }