diff --git a/src/lib/integrations/__tests__/callback-urls.test.ts b/src/lib/integrations/__tests__/callback-urls.test.ts index 27d25c2e3..3214448c3 100644 --- a/src/lib/integrations/__tests__/callback-urls.test.ts +++ b/src/lib/integrations/__tests__/callback-urls.test.ts @@ -110,4 +110,16 @@ describe("getIntegrationCallbackUrls", () => { expect(urls.withings).toBe("http://health.example/api/withings/callback"); expect(urls.whoop).toBe("http://health.example/api/whoop/callback"); }); + + it("reports null rather than 'undefined/api/…' when the app URL is unset", () => { + // A resolver that splices the unset variable into its template would + // otherwise hand the card "undefined/api/polar/callback" to show. + const urls = getIntegrationCallbackUrls(); + for (const provider of INTEGRATION_CALLBACK_PROVIDERS) { + expect(urls[provider] ?? "", provider).not.toMatch(/^undefined/); + if (urls[provider] !== null) { + expect(urls[provider], provider).toMatch(/^https?:\/\//); + } + } + }); }); diff --git a/src/lib/integrations/callback-urls.ts b/src/lib/integrations/callback-urls.ts index d9e7036bf..5f6b00d39 100644 --- a/src/lib/integrations/callback-urls.ts +++ b/src/lib/integrations/callback-urls.ts @@ -59,7 +59,12 @@ const RESOLVERS: Record string> = { function resolve(provider: IntegrationCallbackProvider): string | null { try { - return RESOLVERS[provider](); + const url = RESOLVERS[provider](); + // Some resolvers splice an unset NEXT_PUBLIC_APP_URL into the template + // and hand back "undefined/api/…/callback". That is not a URL anyone can + // paste into a vendor form; report it as not configured so the card + // shows the path instead. + return /^https?:\/\//i.test(url) ? url : null; } catch { return null; }