import { describe, expect, it } from "@effect/vitest"; import { OAUTH_POPUP_MESSAGE_TYPE, openOAuthPopup, openOAuthSystemBrowser, reserveOAuthPopup, type OAuthPopupResult,} from "./oauth-popup"; type OAuthPopupTestWindow = { readonly screenX: number; readonly screenY: number; readonly outerWidth: number; readonly outerHeight: number; readonly location: { readonly origin: string }; readonly addEventListener: () => void; readonly removeEventListener: () => void; readonly open: ( url: string, name: string, features: string, ) => { closed: boolean; close: () => void; opener?: unknown; location: { href: string } };}; type FakePopup = { closed: boolean; close: () => void; opener?: unknown; location: { href: string };}; describe("openOAuthPopup", () => { it("does not open unsupported OAuth endpoint URLs", async () => { let openFailed = false; const teardown = openOAuthPopup({ url: "javascript:alert(1)", popupName: "oauth", channelName: "oauth-channel", onResult: () => {}, onOpenFailed: () => { openFailed = true; }, }); teardown(); await new Promise<void>((resolve) => queueMicrotask(() => resolve())); expect(openFailed).toBe(true); }); it("opens supported OAuth URLs through a reserved popup with opener available", () => { let features = ""; let opened = ""; const popup: FakePopup = { closed: false, close: () => {}, location: { href: "" } }; const previousWindow = globalThis.window; const fakeWindow: OAuthPopupTestWindow = { screenX: 0, screenY: 0, outerWidth: 1200, outerHeight: 900, location: { origin: "https://app.example" }, addEventListener: () => {}, removeEventListener: () => {}, open: (url: string, _name: string, requestedFeatures: string) => { opened = url; features = requestedFeatures; return popup; }, }; Object.defineProperty(globalThis, "window", { configurable: true, value: fakeWindow, writable: true, }); const teardown = openOAuthPopup({ url: "https://auth.example/authorize", popupName: "oauth", channelName: "oauth-channel", onResult: () => {}, }); teardown(); Object.defineProperty(globalThis, "window", { configurable: true, value: previousWindow, writable: true, }); expect(opened).toBe("about:blank"); expect(popup.opener).toBeUndefined(); expect(popup.location.href).toBe("https://auth.example/authorize"); expect(features).toContain("popup=1"); expect(features).not.toContain("noopener"); expect(features).not.toContain("noreferrer"); }); it("can reserve the popup before an async authorization start", () => { let opened = ""; const popup: FakePopup = { closed: false, close: () => {}, location: { href: "" } }; const previousWindow = globalThis.window; const fakeWindow: OAuthPopupTestWindow = { screenX: 0, screenY: 0, outerWidth: 1200, outerHeight: 900, location: { origin: "https://app.example" }, addEventListener: () => {}, removeEventListener: () => {}, open: (url: string) => { opened = url; return popup; }, }; Object.defineProperty(globalThis, "window", { configurable: true, value: fakeWindow, writable: true, }); const reservedPopup = reserveOAuthPopup({ popupName: "oauth" }); const teardown = openOAuthPopup({ url: "https://auth.example/authorize", popupName: "oauth", channelName: "oauth-channel", reservedPopup: reservedPopup ?? undefined, onResult: () => {}, }); teardown(); Object.defineProperty(globalThis, "window", { configurable: true, value: previousWindow, writable: true, }); expect(opened).toBe("about:blank"); expect(reservedPopup).not.toBeNull(); expect(popup.opener).toBeUndefined(); expect(popup.location.href).toBe("https://auth.example/authorize"); }); it("opens HTTP authorization URLs returned by local flows", () => { let opened = ""; const popup: FakePopup = { closed: false, close: () => {}, location: { href: "" } }; const previousWindow = globalThis.window; const fakeWindow: OAuthPopupTestWindow = { screenX: 0, screenY: 0, outerWidth: 1200, outerHeight: 900, location: { origin: "http://127.0.0.1:4000" }, addEventListener: () => {}, removeEventListener: () => {}, open: (url: string) => { opened = url; return popup; }, }; Object.defineProperty(globalThis, "window", { configurable: true, value: fakeWindow, writable: true, }); const teardown = openOAuthPopup({ url: "http://example.com/authorize", popupName: "oauth", channelName: "oauth-channel", onResult: () => {}, }); teardown(); Object.defineProperty(globalThis, "window", { configurable: true, value: previousWindow, writable: true, }); expect(opened).toBe("about:blank"); expect(popup.location.href).toBe("http://example.com/authorize"); }); it("can disable popup.closed polling for providers with strict opener policies", () => { let closedCalled = false; let intervalStarted = false; const popup: FakePopup = { closed: true, close: () => {}, location: { href: "" } }; const previousWindow = globalThis.window; const previousSetInterval = globalThis.setInterval; const fakeWindow: OAuthPopupTestWindow = { screenX: 0, screenY: 0, outerWidth: 1200, outerHeight: 900, location: { origin: "https://app.example" }, addEventListener: () => {}, removeEventListener: () => {}, open: () => popup, }; Object.defineProperty(globalThis, "window", { configurable: true, value: fakeWindow, writable: true, }); Object.defineProperty(globalThis, "setInterval", { configurable: true, value: () => { intervalStarted = true; return 1; }, writable: true, }); const teardown = openOAuthPopup({ url: "https://auth.example/authorize", popupName: "oauth", channelName: "oauth-channel", closedPollMs: null, onResult: () => {}, onClosed: () => { closedCalled = true; }, }); teardown(); Object.defineProperty(globalThis, "window", { configurable: true, value: previousWindow, writable: true, }); Object.defineProperty(globalThis, "setInterval", { configurable: true, value: previousSetInterval, writable: true, }); expect(intervalStarted).toBe(false); expect(closedCalled).toBe(false); });}); describe("openOAuthSystemBrowser", () => { const sessionResult = (sessionId: string): OAuthPopupResult<unknown> => ({ type: OAUTH_POPUP_MESSAGE_TYPE, ok: false, sessionId, error: "access denied", }); const waitFor = async (predicate: () => boolean): Promise<void> => { const deadline = Date.now() + 2000; while (!predicate() && Date.now() < deadline) { await new Promise<void>((resolve) => setTimeout(resolve, 5)); } expect(predicate()).toBe(true); }; const withFakeFetch = async (fake: typeof fetch, run: () => Promise<void>): Promise<void> => { const previousFetch = globalThis.fetch; globalThis.fetch = fake; // oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: always restore the global fetch try { await run(); } finally { globalThis.fetch = previousFetch; } }; it("never stacks overlapping await requests while the server holds one open", async () => { let inFlight = 0; let maxInFlight = 0; let calls = 0; let received: unknown = null; // Simulates a long-polling server: the request is held ~40ms, then // answers with the completed result. const fake: typeof fetch = async () => { calls += 1; inFlight += 1; maxInFlight = Math.max(maxInFlight, inFlight); await new Promise<void>((resolve) => setTimeout(resolve, 40)); inFlight -= 1; return new Response(JSON.stringify(sessionResult("s-hold"))); }; await withFakeFetch(fake, async () => { const teardown = openOAuthSystemBrowser({ url: "https://auth.example/authorize", sessionId: "s-hold", openExternal: async () => {}, onResult: (result) => { received = result; }, // Far shorter than the held request: the old setInterval loop would // stack many concurrent requests here. pollMs: 1, }); await waitFor(() => received !== null); teardown(); }); expect(calls).toBe(1); expect(maxInFlight).toBe(1); expect(received).toMatchObject({ sessionId: "s-hold" }); }); it("reconnects after a pending (null) answer and delivers the next result", async () => { let calls = 0; let received: unknown = null; // First request answers "still pending" (an old server, or a long-poll // deadline); the second delivers the result. const fake: typeof fetch = async () => { calls += 1; const body = calls === 1 ? null : sessionResult("s-retry"); return new Response(JSON.stringify(body)); }; await withFakeFetch(fake, async () => { const teardown = openOAuthSystemBrowser({ url: "https://auth.example/authorize", sessionId: "s-retry", openExternal: async () => {}, onResult: (result) => { received = result; }, pollMs: 1, }); await waitFor(() => received !== null); teardown(); }); expect(calls).toBe(2); expect(received).toMatchObject({ sessionId: "s-retry" }); }); it("aborts the in-flight await request on teardown", async () => { const captured: { signal: AbortSignal | null } = { signal: null }; // Held open indefinitely; on abort it answers "still pending" the way a // real aborted fetch stops mattering — the loop is already settled. const fake: typeof fetch = (_input, init) => new Promise<Response>((resolve) => { captured.signal = init?.signal ?? null; init?.signal?.addEventListener("abort", () => resolve(new Response("null"))); }); await withFakeFetch(fake, async () => { const teardown = openOAuthSystemBrowser({ url: "https://auth.example/authorize", sessionId: "s-abort", openExternal: async () => {}, onResult: () => {}, }); await waitFor(() => captured.signal !== null); teardown(); await waitFor(() => captured.signal?.aborted === true); }); expect(captured.signal?.aborted).toBe(true); });});
import { describe, expect, it } from "@effect/vitest";
import {
OAUTH_POPUP_MESSAGE_TYPE,
openOAuthPopup,
openOAuthSystemBrowser,
reserveOAuthPopup,
type OAuthPopupResult,
} from "./oauth-popup";
type OAuthPopupTestWindow = {
readonly screenX: number;
readonly screenY: number;
readonly outerWidth: number;
readonly outerHeight: number;
readonly location: { readonly origin: string };
readonly addEventListener: () => void;
readonly removeEventListener: () => void;
readonly open: (
url: string,
name: string,
features: string,
) => { closed: boolean; close: () => void; opener?: unknown; location: { href: string } };
};
type FakePopup = {
closed: boolean;
close: () => void;
opener?: unknown;
location: { href: string };
};
describe("openOAuthPopup", () => {
it("does not open unsupported OAuth endpoint URLs", async () => {
let openFailed = false;
const teardown = openOAuthPopup({
url: "javascript:alert(1)",
popupName: "oauth",
channelName: "oauth-channel",
onResult: () => {},
onOpenFailed: () => {
openFailed = true;
},
});
teardown();
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
expect(openFailed).toBe(true);
});
it("opens supported OAuth URLs through a reserved popup with opener available", () => {
let features = "";
let opened = "";
const popup: FakePopup = { closed: false, close: () => {}, location: { href: "" } };
const previousWindow = globalThis.window;
const fakeWindow: OAuthPopupTestWindow = {
screenX: 0,
screenY: 0,
outerWidth: 1200,
outerHeight: 900,
location: { origin: "https://app.example" },
addEventListener: () => {},
removeEventListener: () => {},
open: (url: string, _name: string, requestedFeatures: string) => {
opened = url;
features = requestedFeatures;
return popup;
},
};
Object.defineProperty(globalThis, "window", {
configurable: true,
value: fakeWindow,
writable: true,
});
const teardown = openOAuthPopup({
url: "https://auth.example/authorize",
popupName: "oauth",
channelName: "oauth-channel",
onResult: () => {},
});
teardown();
Object.defineProperty(globalThis, "window", {
configurable: true,
value: previousWindow,
writable: true,
});
expect(opened).toBe("about:blank");
expect(popup.opener).toBeUndefined();
expect(popup.location.href).toBe("https://auth.example/authorize");
expect(features).toContain("popup=1");
expect(features).not.toContain("noopener");
expect(features).not.toContain("noreferrer");
});
it("can reserve the popup before an async authorization start", () => {
let opened = "";
const popup: FakePopup = { closed: false, close: () => {}, location: { href: "" } };
const previousWindow = globalThis.window;
const fakeWindow: OAuthPopupTestWindow = {
screenX: 0,
screenY: 0,
outerWidth: 1200,
outerHeight: 900,
location: { origin: "https://app.example" },
addEventListener: () => {},
removeEventListener: () => {},
open: (url: string) => {
opened = url;
return popup;
},
};
Object.defineProperty(globalThis, "window", {
configurable: true,
value: fakeWindow,
writable: true,
});
const reservedPopup = reserveOAuthPopup({ popupName: "oauth" });
const teardown = openOAuthPopup({
url: "https://auth.example/authorize",
popupName: "oauth",
channelName: "oauth-channel",
reservedPopup: reservedPopup ?? undefined,
onResult: () => {},
});
teardown();
Object.defineProperty(globalThis, "window", {
configurable: true,
value: previousWindow,
writable: true,
});
expect(opened).toBe("about:blank");
expect(reservedPopup).not.toBeNull();
expect(popup.opener).toBeUndefined();
expect(popup.location.href).toBe("https://auth.example/authorize");
});
it("opens HTTP authorization URLs returned by local flows", () => {
let opened = "";
const popup: FakePopup = { closed: false, close: () => {}, location: { href: "" } };
const previousWindow = globalThis.window;
const fakeWindow: OAuthPopupTestWindow = {
screenX: 0,
screenY: 0,
outerWidth: 1200,
outerHeight: 900,
location: { origin: "http://127.0.0.1:4000" },
addEventListener: () => {},
removeEventListener: () => {},
open: (url: string) => {
opened = url;
return popup;
},
};
Object.defineProperty(globalThis, "window", {
configurable: true,
value: fakeWindow,
writable: true,
});
const teardown = openOAuthPopup({
url: "http://example.com/authorize",
popupName: "oauth",
channelName: "oauth-channel",
onResult: () => {},
});
teardown();
Object.defineProperty(globalThis, "window", {
configurable: true,
value: previousWindow,
writable: true,
});
expect(opened).toBe("about:blank");
expect(popup.location.href).toBe("http://example.com/authorize");
});
it("can disable popup.closed polling for providers with strict opener policies", () => {
let closedCalled = false;
let intervalStarted = false;
const popup: FakePopup = { closed: true, close: () => {}, location: { href: "" } };
const previousWindow = globalThis.window;
const previousSetInterval = globalThis.setInterval;
const fakeWindow: OAuthPopupTestWindow = {
screenX: 0,
screenY: 0,
outerWidth: 1200,
outerHeight: 900,
location: { origin: "https://app.example" },
addEventListener: () => {},
removeEventListener: () => {},
open: () => popup,
};
Object.defineProperty(globalThis, "window", {
configurable: true,
value: fakeWindow,
writable: true,
});
Object.defineProperty(globalThis, "setInterval", {
configurable: true,
value: () => {
intervalStarted = true;
return 1;
},
writable: true,
});
const teardown = openOAuthPopup({
url: "https://auth.example/authorize",
popupName: "oauth",
channelName: "oauth-channel",
closedPollMs: null,
onResult: () => {},
onClosed: () => {
closedCalled = true;
},
});
teardown();
Object.defineProperty(globalThis, "window", {
configurable: true,
value: previousWindow,
writable: true,
});
Object.defineProperty(globalThis, "setInterval", {
configurable: true,
value: previousSetInterval,
writable: true,
});
expect(intervalStarted).toBe(false);
expect(closedCalled).toBe(false);
});
});
describe("openOAuthSystemBrowser", () => {
const sessionResult = (sessionId: string): OAuthPopupResult<unknown> => ({
type: OAUTH_POPUP_MESSAGE_TYPE,
ok: false,
sessionId,
error: "access denied",
});
const waitFor = async (predicate: () => boolean): Promise<void> => {
const deadline = Date.now() + 2000;
while (!predicate() && Date.now() < deadline) {
await new Promise<void>((resolve) => setTimeout(resolve, 5));
}
expect(predicate()).toBe(true);
};
const withFakeFetch = async (fake: typeof fetch, run: () => Promise<void>): Promise<void> => {
const previousFetch = globalThis.fetch;
globalThis.fetch = fake;
// oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: always restore the global fetch
try {
await run();
} finally {
globalThis.fetch = previousFetch;
}
};
it("never stacks overlapping await requests while the server holds one open", async () => {
let inFlight = 0;
let maxInFlight = 0;
let calls = 0;
let received: unknown = null;
// Simulates a long-polling server: the request is held ~40ms, then
// answers with the completed result.
const fake: typeof fetch = async () => {
calls += 1;
inFlight += 1;
maxInFlight = Math.max(maxInFlight, inFlight);
await new Promise<void>((resolve) => setTimeout(resolve, 40));
inFlight -= 1;
return new Response(JSON.stringify(sessionResult("s-hold")));
};
await withFakeFetch(fake, async () => {
const teardown = openOAuthSystemBrowser({
url: "https://auth.example/authorize",
sessionId: "s-hold",
openExternal: async () => {},
onResult: (result) => {
received = result;
},
// Far shorter than the held request: the old setInterval loop would
// stack many concurrent requests here.
pollMs: 1,
});
await waitFor(() => received !== null);
teardown();
});
expect(calls).toBe(1);
expect(maxInFlight).toBe(1);
expect(received).toMatchObject({ sessionId: "s-hold" });
});
it("reconnects after a pending (null) answer and delivers the next result", async () => {
let calls = 0;
let received: unknown = null;
// First request answers "still pending" (an old server, or a long-poll
// deadline); the second delivers the result.
const fake: typeof fetch = async () => {
calls += 1;
const body = calls === 1 ? null : sessionResult("s-retry");
return new Response(JSON.stringify(body));
};
await withFakeFetch(fake, async () => {
const teardown = openOAuthSystemBrowser({
url: "https://auth.example/authorize",
sessionId: "s-retry",
openExternal: async () => {},
onResult: (result) => {
received = result;
},
pollMs: 1,
});
await waitFor(() => received !== null);
teardown();
});
expect(calls).toBe(2);
expect(received).toMatchObject({ sessionId: "s-retry" });
});
it("aborts the in-flight await request on teardown", async () => {
const captured: { signal: AbortSignal | null } = { signal: null };
// Held open indefinitely; on abort it answers "still pending" the way a
// real aborted fetch stops mattering — the loop is already settled.
const fake: typeof fetch = (_input, init) =>
new Promise<Response>((resolve) => {
captured.signal = init?.signal ?? null;
init?.signal?.addEventListener("abort", () => resolve(new Response("null")));
});
await withFakeFetch(fake, async () => {
const teardown = openOAuthSystemBrowser({
url: "https://auth.example/authorize",
sessionId: "s-abort",
openExternal: async () => {},
onResult: () => {},
});
await waitFor(() => captured.signal !== null);
teardown();
await waitFor(() => captured.signal?.aborted === true);
});
expect(captured.signal?.aborted).toBe(true);
});
});