From 9613a0759ce339664768bb5e8fd42d694c61e7af Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 24 Apr 2026 23:34:27 +0100 Subject: [PATCH] refactor(google-meet): tidy browser create control --- extensions/google-meet/index.test.ts | 84 +++++++++---------- .../google-meet/src/transports/chrome.ts | 48 +++++++---- 2 files changed, 73 insertions(+), 59 deletions(-) diff --git a/extensions/google-meet/index.test.ts b/extensions/google-meet/index.test.ts index 6f2bb3478a4..2a5899ac91b 100644 --- a/extensions/google-meet/index.test.ts +++ b/extensions/google-meet/index.test.ts @@ -74,6 +74,39 @@ function captureStdout() { }; } +async function runCreateMeetBrowserScript(params: { buttonText: string }) { + const location = { + href: "https://meet.google.com/new", + hostname: "meet.google.com", + }; + const button = { + disabled: false, + innerText: params.buttonText, + textContent: params.buttonText, + getAttribute: (name: string) => (name === "aria-label" ? params.buttonText : null), + click: vi.fn(() => { + location.href = "https://meet.google.com/abc-defg-hij"; + }), + }; + const document = { + title: "Meet", + body: { + innerText: "Do you want people to hear you in the meeting?", + textContent: "Do you want people to hear you in the meeting?", + }, + querySelectorAll: (selector: string) => (selector === "button" ? [button] : []), + }; + vi.stubGlobal("document", document); + vi.stubGlobal("location", location); + const fn = (0, eval)(`(${CREATE_MEET_FROM_BROWSER_SCRIPT})`) as () => Promise<{ + meetingUri?: string; + manualActionReason?: string; + notes?: string[]; + retryAfterMs?: number; + }>; + return { button, result: await fn() }; +} + type TestBridgeProcess = { stdin?: { write(chunk: unknown): unknown } | null; stdout?: { on(event: "data", listener: (chunk: unknown) => void): unknown } | null; @@ -853,50 +886,15 @@ describe("google-meet plugin", () => { ])( "uses browser automation for Meet's %s choice during browser creation", async (buttonText, note) => { - const location = { - href: "https://meet.google.com/new", - hostname: "meet.google.com", - }; - const button = { - disabled: false, - innerText: buttonText, - textContent: buttonText, - getAttribute: (name: string) => (name === "aria-label" ? buttonText : null), - click: vi.fn(() => { - location.href = "https://meet.google.com/abc-defg-hij"; - }), - }; - const document = { - title: "Meet", - body: { - innerText: "Do you want people to hear you in the meeting?", - textContent: "Do you want people to hear you in the meeting?", - }, - querySelectorAll: (selector: string) => (selector === "button" ? [button] : []), - }; - vi.stubGlobal("document", document); - vi.stubGlobal("location", location); - vi.useFakeTimers(); + const { button, result } = await runCreateMeetBrowserScript({ buttonText }); - try { - const fn = (0, eval)(`(${CREATE_MEET_FROM_BROWSER_SCRIPT})`) as () => Promise<{ - meetingUri?: string; - manualActionReason?: string; - notes?: string[]; - retryAfterMs?: number; - }>; - const result = await fn(); - - expect(result).toMatchObject({ - retryAfterMs: 1000, - notes: [note], - }); - expect(button.click).toHaveBeenCalledTimes(1); - expect(result.meetingUri).toBeUndefined(); - expect(result.manualActionReason).toBeUndefined(); - } finally { - vi.useRealTimers(); - } + expect(result).toMatchObject({ + retryAfterMs: 1000, + notes: [note], + }); + expect(button.click).toHaveBeenCalledTimes(1); + expect(result.meetingUri).toBeUndefined(); + expect(result.manualActionReason).toBeUndefined(); }, ); diff --git a/extensions/google-meet/src/transports/chrome.ts b/extensions/google-meet/src/transports/chrome.ts index 6f9f5b72e93..4bd028593ea 100644 --- a/extensions/google-meet/src/transports/chrome.ts +++ b/extensions/google-meet/src/transports/chrome.ts @@ -231,6 +231,20 @@ type BrowserTab = { url?: string; }; +type BrowserCreateStepResult = { + meetingUri?: string; + browserUrl?: string; + browserTitle?: string; + manualAction?: string; + manualActionReason?: GoogleMeetChromeHealth["manualActionReason"]; + notes?: string[]; + retryAfterMs?: number; +}; + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + function formatBrowserAutomationError(error: unknown): string { if (error instanceof Error) { return error.message; @@ -242,6 +256,12 @@ function formatBrowserAutomationError(error: unknown): string { } } +function isBrowserNavigationInterruption(error: unknown): boolean { + return /execution context was destroyed|navigation|target closed/i.test( + formatBrowserAutomationError(error), + ); +} + export type GoogleMeetBrowserCreateResult = { meetingUri: string; nodeId: string; @@ -304,15 +324,13 @@ function readBrowserTab(result: unknown): BrowserTab | undefined { return result && typeof result === "object" ? (result as BrowserTab) : undefined; } -function readBrowserCreateResult(result: unknown): { - meetingUri?: string; - browserUrl?: string; - browserTitle?: string; - manualAction?: string; - manualActionReason?: GoogleMeetChromeHealth["manualActionReason"]; - notes?: string[]; - retryAfterMs?: number; -} { +function readStringArray(value: unknown): string[] | undefined { + return Array.isArray(value) + ? value.filter((entry): entry is string => typeof entry === "string") + : undefined; +} + +function readBrowserCreateResult(result: unknown): BrowserCreateStepResult { const record = result && typeof result === "object" ? (result as Record) : {}; const nested = record.result && typeof record.result === "object" @@ -327,9 +345,7 @@ function readBrowserCreateResult(result: unknown): { typeof nested.manualActionReason === "string" ? (nested.manualActionReason as GoogleMeetChromeHealth["manualActionReason"]) : undefined, - notes: Array.isArray(nested.notes) - ? nested.notes.filter((note): note is string => typeof note === "string") - : undefined, + notes: readStringArray(nested.notes), retryAfterMs: typeof nested.retryAfterMs === "number" && Number.isFinite(nested.retryAfterMs) ? nested.retryAfterMs @@ -454,7 +470,7 @@ export async function createMeetWithBrowserProxyOnNode(params: { throw new Error("Browser fallback opened Google Meet but did not return a targetId."); } const notes = new Set(); - let lastResult: ReturnType | undefined; + let lastResult: BrowserCreateStepResult | undefined; let lastError: unknown; const deadline = Date.now() + timeoutMs; while (Date.now() <= deadline) { @@ -493,13 +509,13 @@ export async function createMeetWithBrowserProxyOnNode(params: { } throw new Error(result.manualAction); } - await new Promise((resolve) => setTimeout(resolve, result.retryAfterMs ?? 500)); + await sleep(result.retryAfterMs ?? 500); } catch (error) { lastError = error; - if (!/execution context was destroyed|navigation|target closed/i.test(String(error))) { + if (!isBrowserNavigationInterruption(error)) { throw error; } - await new Promise((resolve) => setTimeout(resolve, 1_000)); + await sleep(1_000); } } throw new Error(