Open History to see the last commit
1import { previewBridge } from "~/components/preview/previewBridge";
2
3import { browserDefaultTabState, resolveBrowserDefaults } from "./browserDefaults";
4import { stopBrowserRecording } from "./browserRecording";
5
6interface DesktopTabLease {
7 references: number;
8 closeTimer: number | null;
9 ready: Promise<void>;
10}
11
12const leases = new Map<string, DesktopTabLease>();
13const pendingTabOperations = new Map<string, Promise<void>>();
14
15const enqueueDesktopTabOperation = (
16 tabId: string,
17 operation: () => Promise<void> | void,
18): Promise<void> => {
19 const previous = pendingTabOperations.get(tabId);
20 const pending = previous
21 ? previous.catch(() => undefined).then(operation)
22 : Promise.resolve(operation());
23 pendingTabOperations.set(tabId, pending);
24 void pending
25 .finally(() => {
26 if (pendingTabOperations.get(tabId) === pending) {
27 pendingTabOperations.delete(tabId);
28 }
29 })
30 .catch(() => undefined);
31 return pending;
32};
33
34/** Runs after tab creation and earlier calls. */
35export function withDesktopTab(tabId: string, operation: () => Promise<void>): void {
36 void enqueueDesktopTabOperation(tabId, operation).catch(() => undefined);
37}
38
39export interface AcquiredDesktopTab {
40 readonly ready: Promise<void>;
41 readonly release: () => void;
42}
43
44export function acquireDesktopTab(
45 tabId: string,
46 serverTab?: { readonly threadId: string; readonly tabId: string },
47): AcquiredDesktopTab {
48 const current =
49 leases.get(tabId) ??
50 ({
51 references: 0,
52 closeTimer: null,
53 // Include defaults at creation to avoid painting a frame at 100%/system.
54 ready: enqueueDesktopTabOperation(tabId, async () =>
55 previewBridge?.createTab(tabId, {
56 ...browserDefaultTabState(await resolveBrowserDefaults()),
57 ...(serverTab === undefined ? {} : { serverTab }),
58 }),
59 ),
60 } satisfies DesktopTabLease);
61 if (current.closeTimer !== null) window.clearTimeout(current.closeTimer);
62 current.references += 1;
63 current.closeTimer = null;
64 leases.set(tabId, current);
65
66 return {
67 ready: current.ready,
68 release: () => {
69 const lease = leases.get(tabId);
70 if (!lease) return;
71 lease.references = Math.max(0, lease.references - 1);
72 if (lease.references > 0) return;
73 lease.closeTimer = window.setTimeout(() => {
74 const latest = leases.get(tabId);
75 if (!latest || latest.references > 0) return;
76 leases.delete(tabId);
77 void enqueueDesktopTabOperation(tabId, async () => {
78 await stopBrowserRecording(tabId).catch(() => null);
79 await previewBridge?.closeTab(tabId);
80 }).catch(() => undefined);
81 }, 0);
82 },
83 };
84}