@flrande/browserctl 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/client.d.ts +34 -0
  2. package/dist/client.js +138 -0
  3. package/dist/commandRegistry.d.ts +16 -0
  4. package/dist/commandRegistry.js +21 -0
  5. package/dist/help.d.ts +4 -0
  6. package/dist/help.js +24 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +23 -0
  9. package/dist/runCli.d.ts +5 -0
  10. package/dist/runCli.js +170 -0
  11. package/package.json +32 -57
  12. package/INSTALL-CN.md +0 -92
  13. package/INSTALL.md +0 -92
  14. package/LICENSE +0 -21
  15. package/README-CN.md +0 -69
  16. package/README.md +0 -69
  17. package/apps/browserctl/src/commands/a11y-snapshot.ts +0 -20
  18. package/apps/browserctl/src/commands/act.test.ts +0 -71
  19. package/apps/browserctl/src/commands/act.ts +0 -64
  20. package/apps/browserctl/src/commands/command-wrappers.test.ts +0 -688
  21. package/apps/browserctl/src/commands/common.test.ts +0 -87
  22. package/apps/browserctl/src/commands/common.ts +0 -191
  23. package/apps/browserctl/src/commands/console-list.test.ts +0 -102
  24. package/apps/browserctl/src/commands/console-list.ts +0 -108
  25. package/apps/browserctl/src/commands/cookie-clear.ts +0 -18
  26. package/apps/browserctl/src/commands/cookie-get.ts +0 -18
  27. package/apps/browserctl/src/commands/cookie-set.ts +0 -22
  28. package/apps/browserctl/src/commands/dialog-arm.ts +0 -20
  29. package/apps/browserctl/src/commands/dom-query-all.ts +0 -18
  30. package/apps/browserctl/src/commands/dom-query.ts +0 -18
  31. package/apps/browserctl/src/commands/download-trigger.ts +0 -22
  32. package/apps/browserctl/src/commands/download-wait.test.ts +0 -67
  33. package/apps/browserctl/src/commands/download-wait.ts +0 -27
  34. package/apps/browserctl/src/commands/element-screenshot.ts +0 -20
  35. package/apps/browserctl/src/commands/frame-list.ts +0 -16
  36. package/apps/browserctl/src/commands/frame-snapshot.ts +0 -18
  37. package/apps/browserctl/src/commands/har-export.test.ts +0 -112
  38. package/apps/browserctl/src/commands/har-export.ts +0 -120
  39. package/apps/browserctl/src/commands/memory-delete.ts +0 -20
  40. package/apps/browserctl/src/commands/memory-inspect.ts +0 -20
  41. package/apps/browserctl/src/commands/memory-list.ts +0 -90
  42. package/apps/browserctl/src/commands/memory-mode-set.ts +0 -29
  43. package/apps/browserctl/src/commands/memory-purge.ts +0 -16
  44. package/apps/browserctl/src/commands/memory-resolve.ts +0 -56
  45. package/apps/browserctl/src/commands/memory-status.ts +0 -16
  46. package/apps/browserctl/src/commands/memory-ttl-set.ts +0 -28
  47. package/apps/browserctl/src/commands/memory-upsert.ts +0 -142
  48. package/apps/browserctl/src/commands/network-list.test.ts +0 -110
  49. package/apps/browserctl/src/commands/network-list.ts +0 -112
  50. package/apps/browserctl/src/commands/network-wait-for.test.ts +0 -90
  51. package/apps/browserctl/src/commands/network-wait-for.ts +0 -100
  52. package/apps/browserctl/src/commands/profile-list.ts +0 -16
  53. package/apps/browserctl/src/commands/profile-use.ts +0 -18
  54. package/apps/browserctl/src/commands/response-body.ts +0 -24
  55. package/apps/browserctl/src/commands/screenshot.ts +0 -16
  56. package/apps/browserctl/src/commands/session-drop.test.ts +0 -36
  57. package/apps/browserctl/src/commands/session-drop.ts +0 -16
  58. package/apps/browserctl/src/commands/session-list.test.ts +0 -81
  59. package/apps/browserctl/src/commands/session-list.ts +0 -70
  60. package/apps/browserctl/src/commands/snapshot.ts +0 -16
  61. package/apps/browserctl/src/commands/status.ts +0 -10
  62. package/apps/browserctl/src/commands/storage-get.ts +0 -20
  63. package/apps/browserctl/src/commands/storage-set.ts +0 -22
  64. package/apps/browserctl/src/commands/tab-close.ts +0 -20
  65. package/apps/browserctl/src/commands/tab-focus.ts +0 -20
  66. package/apps/browserctl/src/commands/tab-open.ts +0 -19
  67. package/apps/browserctl/src/commands/tabs.ts +0 -13
  68. package/apps/browserctl/src/commands/trace-get.test.ts +0 -61
  69. package/apps/browserctl/src/commands/trace-get.ts +0 -62
  70. package/apps/browserctl/src/commands/upload-arm.ts +0 -26
  71. package/apps/browserctl/src/commands/wait-element.test.ts +0 -80
  72. package/apps/browserctl/src/commands/wait-element.ts +0 -76
  73. package/apps/browserctl/src/commands/wait-text.test.ts +0 -110
  74. package/apps/browserctl/src/commands/wait-text.ts +0 -93
  75. package/apps/browserctl/src/commands/wait-url.test.ts +0 -80
  76. package/apps/browserctl/src/commands/wait-url.ts +0 -76
  77. package/apps/browserctl/src/daemon-client.test.ts +0 -253
  78. package/apps/browserctl/src/daemon-client.ts +0 -632
  79. package/apps/browserctl/src/e2e.test.ts +0 -103
  80. package/apps/browserctl/src/main.dispatch.test.ts +0 -461
  81. package/apps/browserctl/src/main.test.ts +0 -334
  82. package/apps/browserctl/src/main.ts +0 -957
  83. package/apps/browserctl/src/smoke.e2e.test.ts +0 -97
  84. package/apps/browserctl/src/test-port.ts +0 -26
  85. package/apps/browserd/src/bootstrap.ts +0 -432
  86. package/apps/browserd/src/chrome-relay-extension-bridge.test.ts +0 -250
  87. package/apps/browserd/src/chrome-relay-extension-bridge.ts +0 -506
  88. package/apps/browserd/src/container.ts +0 -3088
  89. package/apps/browserd/src/main.test.ts +0 -1436
  90. package/apps/browserd/src/main.ts +0 -7
  91. package/apps/browserd/src/test-port.ts +0 -26
  92. package/apps/browserd/src/tool-matrix.test.ts +0 -887
  93. package/bin/browserctl.cjs +0 -21
  94. package/bin/browserd.cjs +0 -21
  95. package/extensions/chrome-relay/README-CN.md +0 -39
  96. package/extensions/chrome-relay/README.md +0 -39
  97. package/extensions/chrome-relay/background.js +0 -1687
  98. package/extensions/chrome-relay/manifest.json +0 -15
  99. package/extensions/chrome-relay/popup.html +0 -369
  100. package/extensions/chrome-relay/popup.js +0 -972
  101. package/packages/core/src/bootstrap.test.ts +0 -10
  102. package/packages/core/src/driver-registry.test.ts +0 -45
  103. package/packages/core/src/driver-registry.ts +0 -22
  104. package/packages/core/src/driver.ts +0 -47
  105. package/packages/core/src/index.ts +0 -6
  106. package/packages/core/src/navigation-memory.test.ts +0 -259
  107. package/packages/core/src/navigation-memory.ts +0 -360
  108. package/packages/core/src/ref-cache.test.ts +0 -61
  109. package/packages/core/src/ref-cache.ts +0 -28
  110. package/packages/core/src/session-store.test.ts +0 -82
  111. package/packages/core/src/session-store.ts +0 -138
  112. package/packages/core/src/types.ts +0 -9
  113. package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +0 -744
  114. package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +0 -2429
  115. package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.test.ts +0 -264
  116. package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.ts +0 -521
  117. package/packages/driver-chrome-relay/src/index.ts +0 -26
  118. package/packages/driver-managed/src/index.ts +0 -22
  119. package/packages/driver-managed/src/managed-driver.test.ts +0 -183
  120. package/packages/driver-managed/src/managed-driver.ts +0 -341
  121. package/packages/driver-managed/src/managed-local-driver.test.ts +0 -608
  122. package/packages/driver-managed/src/managed-local-driver.ts +0 -2243
  123. package/packages/driver-remote-cdp/src/index.ts +0 -19
  124. package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +0 -727
  125. package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +0 -2264
  126. package/packages/protocol/src/envelope.test.ts +0 -25
  127. package/packages/protocol/src/envelope.ts +0 -31
  128. package/packages/protocol/src/errors.test.ts +0 -17
  129. package/packages/protocol/src/errors.ts +0 -11
  130. package/packages/protocol/src/index.ts +0 -3
  131. package/packages/protocol/src/tools.ts +0 -3
  132. package/packages/transport-mcp-stdio/src/index.ts +0 -3
  133. package/packages/transport-mcp-stdio/src/sdk-server.ts +0 -139
  134. package/packages/transport-mcp-stdio/src/server.test.ts +0 -281
  135. package/packages/transport-mcp-stdio/src/server.ts +0 -183
  136. package/packages/transport-mcp-stdio/src/tool-map.ts +0 -84
@@ -1,506 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { createServer, type Server as HttpServer } from "node:http";
3
-
4
- import { WebSocket, WebSocketServer } from "ws";
5
-
6
- const DEFAULT_REQUEST_TIMEOUT_MS = 5_000;
7
- const BRIDGE_PATH = "/bridge";
8
- const JSON_VERSION_PATH = "/json/version";
9
- const STATUS_PATH = "/browserctl/relay/status";
10
-
11
- type PendingRequest = {
12
- resolve: (value: unknown) => void;
13
- reject: (error: unknown) => void;
14
- timer: NodeJS.Timeout;
15
- };
16
-
17
- export type ChromeRelayExtensionBridgeConsoleEvent = {
18
- kind: "console";
19
- tabId: number;
20
- entry: {
21
- type: string;
22
- text: string;
23
- location?: {
24
- url?: string;
25
- lineNumber?: number;
26
- columnNumber?: number;
27
- };
28
- };
29
- };
30
-
31
- export type ChromeRelayExtensionBridgeNetworkEvent = {
32
- kind: "response";
33
- tabId: number;
34
- response: {
35
- requestId: string;
36
- url: string;
37
- status?: number;
38
- method?: string;
39
- resourceType?: string;
40
- body?: string;
41
- encoding?: "utf8" | "base64";
42
- };
43
- };
44
-
45
- export type ChromeRelayExtensionBridgeEvent =
46
- | ChromeRelayExtensionBridgeConsoleEvent
47
- | ChromeRelayExtensionBridgeNetworkEvent;
48
-
49
- export type ChromeRelayExtensionBridgeConfig = {
50
- relayUrl: string;
51
- token?: string;
52
- requestTimeoutMs?: number;
53
- };
54
-
55
- export type ChromeRelayExtensionBridgeStatus = {
56
- connected: boolean;
57
- extensionId?: string;
58
- websocketUrl: string;
59
- relayUrl: string;
60
- };
61
-
62
- export type ChromeRelayExtensionBridge = {
63
- relayUrl: string;
64
- websocketUrl: string;
65
- isConnected(): boolean;
66
- getStatus(): ChromeRelayExtensionBridgeStatus;
67
- invoke(method: string, params?: Record<string, unknown>): Promise<unknown>;
68
- onEvent(listener: (event: ChromeRelayExtensionBridgeEvent) => void): () => void;
69
- close(): Promise<void>;
70
- };
71
-
72
- function resolveNonEmptyString(value: string | undefined): string | undefined {
73
- if (value === undefined) {
74
- return undefined;
75
- }
76
-
77
- const trimmedValue = value.trim();
78
- return trimmedValue.length === 0 ? undefined : trimmedValue;
79
- }
80
-
81
- function isObjectRecord(value: unknown): value is Record<string, unknown> {
82
- return typeof value === "object" && value !== null && !Array.isArray(value);
83
- }
84
-
85
- function readObjectStringProperty(value: unknown, key: string): string | undefined {
86
- if (!isObjectRecord(value)) {
87
- return undefined;
88
- }
89
-
90
- const propertyValue = value[key];
91
- return typeof propertyValue === "string" ? propertyValue : undefined;
92
- }
93
-
94
- function readObjectNumberProperty(value: unknown, key: string): number | undefined {
95
- if (!isObjectRecord(value)) {
96
- return undefined;
97
- }
98
-
99
- const propertyValue = value[key];
100
- return typeof propertyValue === "number" ? propertyValue : undefined;
101
- }
102
-
103
- function parseBridgeEvent(value: unknown): ChromeRelayExtensionBridgeEvent | undefined {
104
- if (!isObjectRecord(value)) {
105
- return undefined;
106
- }
107
-
108
- const kind = readObjectStringProperty(value, "kind");
109
- const tabId = readObjectNumberProperty(value, "tabId");
110
- if (tabId === undefined || !Number.isFinite(tabId)) {
111
- return undefined;
112
- }
113
-
114
- if (kind === "console") {
115
- const rawEntry = isObjectRecord(value.entry) ? value.entry : undefined;
116
- const type = readObjectStringProperty(rawEntry, "type") ?? "log";
117
- const text = readObjectStringProperty(rawEntry, "text") ?? "";
118
- const rawLocation = isObjectRecord(rawEntry?.location) ? rawEntry.location : undefined;
119
- const url = readObjectStringProperty(rawLocation, "url");
120
- const lineNumber = readObjectNumberProperty(rawLocation, "lineNumber");
121
- const columnNumber = readObjectNumberProperty(rawLocation, "columnNumber");
122
-
123
- return {
124
- kind: "console",
125
- tabId,
126
- entry: {
127
- type,
128
- text,
129
- ...((url !== undefined || lineNumber !== undefined || columnNumber !== undefined)
130
- ? {
131
- location: {
132
- ...(url !== undefined ? { url } : {}),
133
- ...(lineNumber !== undefined ? { lineNumber } : {}),
134
- ...(columnNumber !== undefined ? { columnNumber } : {})
135
- }
136
- }
137
- : {})
138
- }
139
- };
140
- }
141
-
142
- if (kind === "response") {
143
- const rawResponse = isObjectRecord(value.response) ? value.response : undefined;
144
- const requestId = readObjectStringProperty(rawResponse, "requestId");
145
- const url = readObjectStringProperty(rawResponse, "url");
146
- if (requestId === undefined || url === undefined) {
147
- return undefined;
148
- }
149
-
150
- const status = readObjectNumberProperty(rawResponse, "status");
151
- const method = readObjectStringProperty(rawResponse, "method");
152
- const resourceType = readObjectStringProperty(rawResponse, "resourceType");
153
- const body = readObjectStringProperty(rawResponse, "body");
154
- const encodingRaw = readObjectStringProperty(rawResponse, "encoding");
155
- const encoding = encodingRaw === "base64" ? "base64" : encodingRaw === "utf8" ? "utf8" : undefined;
156
-
157
- return {
158
- kind: "response",
159
- tabId,
160
- response: {
161
- requestId,
162
- url,
163
- ...(status !== undefined ? { status } : {}),
164
- ...(method !== undefined ? { method } : {}),
165
- ...(resourceType !== undefined ? { resourceType } : {}),
166
- ...(body !== undefined ? { body } : {}),
167
- ...(encoding !== undefined ? { encoding } : {})
168
- }
169
- };
170
- }
171
-
172
- return undefined;
173
- }
174
-
175
- function parseRelayHttpUrl(relayUrl: string): URL {
176
- const parsedRelayUrl = new URL(relayUrl);
177
- if (parsedRelayUrl.protocol !== "http:") {
178
- throw new Error(
179
- `Chrome relay extension bridge requires http relayUrl. Received: ${relayUrl}`
180
- );
181
- }
182
-
183
- return parsedRelayUrl;
184
- }
185
-
186
- function getRelayPort(parsedRelayUrl: URL): number {
187
- if (parsedRelayUrl.port.length > 0) {
188
- const parsedPort = Number.parseInt(parsedRelayUrl.port, 10);
189
- if (Number.isFinite(parsedPort) && parsedPort > 0) {
190
- return parsedPort;
191
- }
192
- }
193
-
194
- return 80;
195
- }
196
-
197
- function createResponsePayload(payload: unknown): string {
198
- return JSON.stringify(payload);
199
- }
200
-
201
- export function createChromeRelayExtensionBridge(
202
- config: ChromeRelayExtensionBridgeConfig
203
- ): ChromeRelayExtensionBridge {
204
- const parsedRelayUrl = parseRelayHttpUrl(config.relayUrl);
205
- const host = parsedRelayUrl.hostname;
206
- const port = getRelayPort(parsedRelayUrl);
207
- const expectedToken = resolveNonEmptyString(config.token);
208
- if (expectedToken === undefined) {
209
- throw new Error(
210
- "BROWSERD_CHROME_RELAY_EXTENSION_TOKEN is required for chrome-relay extension bridge."
211
- );
212
- }
213
-
214
- const requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
215
- const pendingRequests = new Map<string, PendingRequest>();
216
- const eventListeners = new Set<(event: ChromeRelayExtensionBridgeEvent) => void>();
217
- let extensionSocket: WebSocket | undefined;
218
- let extensionId: string | undefined;
219
- let extensionAuthenticated = false;
220
-
221
- const websocketUrlBase = `ws://${host}:${port}${BRIDGE_PATH}`;
222
- const websocketUrl = websocketUrlBase;
223
-
224
- const httpServer: HttpServer = createServer((request, response) => {
225
- const requestUrl = request.url ?? "/";
226
- const parsedPath = requestUrl.split("?")[0];
227
-
228
- if (parsedPath === JSON_VERSION_PATH) {
229
- response.statusCode = 200;
230
- response.setHeader("content-type", "application/json; charset=utf-8");
231
- response.end(
232
- createResponsePayload({
233
- Browser: "browserctl-extension-relay",
234
- webSocketDebuggerUrl: websocketUrlBase
235
- })
236
- );
237
- return;
238
- }
239
-
240
- if (parsedPath === STATUS_PATH) {
241
- response.statusCode = 200;
242
- response.setHeader("content-type", "application/json; charset=utf-8");
243
- response.end(
244
- createResponsePayload({
245
- connected:
246
- extensionSocket !== undefined && extensionSocket.readyState === WebSocket.OPEN,
247
- extensionId,
248
- relayUrl: config.relayUrl,
249
- websocketUrl: websocketUrlBase
250
- })
251
- );
252
- return;
253
- }
254
-
255
- response.statusCode = 404;
256
- response.end();
257
- });
258
-
259
- const wsServer = new WebSocketServer({
260
- noServer: true
261
- });
262
-
263
- function rejectPendingRequests(reason: string): void {
264
- for (const [requestId, pending] of pendingRequests.entries()) {
265
- pendingRequests.delete(requestId);
266
- clearTimeout(pending.timer);
267
- pending.reject(new Error(reason));
268
- }
269
- }
270
-
271
- function clearExtensionConnection(reason: string): void {
272
- extensionSocket = undefined;
273
- extensionId = undefined;
274
- extensionAuthenticated = false;
275
- rejectPendingRequests(reason);
276
- }
277
-
278
- wsServer.on("connection", (socket) => {
279
- if (extensionSocket !== undefined && extensionSocket.readyState === WebSocket.OPEN) {
280
- extensionSocket.close(1000, "Superseded by a newer extension connection.");
281
- }
282
-
283
- extensionSocket = socket;
284
- extensionId = undefined;
285
- extensionAuthenticated = false;
286
-
287
- const authDeadlineMs = Math.max(200, Math.min(requestTimeoutMs, 5_000));
288
- const authTimer = setTimeout(() => {
289
- if (socket.readyState === WebSocket.OPEN && !extensionAuthenticated) {
290
- socket.close(1008, "Missing authentication token");
291
- }
292
- }, authDeadlineMs);
293
-
294
- const clearAuthTimer = () => {
295
- clearTimeout(authTimer);
296
- };
297
-
298
- socket.on("message", (message) => {
299
- let parsedMessage: unknown;
300
- try {
301
- parsedMessage = JSON.parse(String(message));
302
- } catch {
303
- return;
304
- }
305
-
306
- if (!isObjectRecord(parsedMessage)) {
307
- return;
308
- }
309
-
310
- if (parsedMessage.type === "hello") {
311
- const providedToken = resolveNonEmptyString(parsedMessage.token as string | undefined);
312
- if (providedToken !== expectedToken) {
313
- socket.close(1008, "Invalid token");
314
- return;
315
- }
316
-
317
- clearAuthTimer();
318
- extensionAuthenticated = true;
319
- extensionId = resolveNonEmptyString(parsedMessage.extensionId as string | undefined);
320
- return;
321
- }
322
-
323
- if (!extensionAuthenticated) {
324
- return;
325
- }
326
-
327
- if (parsedMessage.type === "event") {
328
- const event = parseBridgeEvent(parsedMessage.event);
329
- if (event === undefined) {
330
- return;
331
- }
332
-
333
- for (const listener of eventListeners) {
334
- listener(event);
335
- }
336
- return;
337
- }
338
-
339
- if (parsedMessage.type !== "response") {
340
- return;
341
- }
342
-
343
- const requestId = resolveNonEmptyString(parsedMessage.id as string | undefined);
344
- if (requestId === undefined) {
345
- return;
346
- }
347
-
348
- const pending = pendingRequests.get(requestId);
349
- if (pending === undefined) {
350
- return;
351
- }
352
-
353
- pendingRequests.delete(requestId);
354
- clearTimeout(pending.timer);
355
- if (parsedMessage.ok === true) {
356
- pending.resolve(parsedMessage.result);
357
- return;
358
- }
359
-
360
- const errorMessage = resolveNonEmptyString(
361
- isObjectRecord(parsedMessage.error) ? (parsedMessage.error.message as string | undefined) : undefined
362
- );
363
- pending.reject(new Error(errorMessage ?? "Extension relay request failed."));
364
- });
365
-
366
- socket.on("close", () => {
367
- clearAuthTimer();
368
- if (extensionSocket === socket) {
369
- clearExtensionConnection("Extension websocket disconnected.");
370
- }
371
- });
372
-
373
- socket.on("error", () => {
374
- clearAuthTimer();
375
- if (extensionSocket === socket) {
376
- clearExtensionConnection("Extension websocket encountered an error.");
377
- }
378
- });
379
- });
380
-
381
- httpServer.on("upgrade", (request, socket, head) => {
382
- const requestUrl = new URL(request.url ?? BRIDGE_PATH, parsedRelayUrl);
383
- if (requestUrl.pathname !== BRIDGE_PATH) {
384
- socket.destroy();
385
- return;
386
- }
387
-
388
- wsServer.handleUpgrade(request, socket, head, (upgradedSocket) => {
389
- wsServer.emit("connection", upgradedSocket, request);
390
- });
391
- });
392
-
393
- httpServer.listen(port, host);
394
-
395
- return {
396
- relayUrl: config.relayUrl,
397
- websocketUrl,
398
- isConnected: () =>
399
- extensionSocket !== undefined &&
400
- extensionSocket.readyState === WebSocket.OPEN &&
401
- extensionAuthenticated,
402
- getStatus: () => ({
403
- connected:
404
- extensionSocket !== undefined &&
405
- extensionSocket.readyState === WebSocket.OPEN &&
406
- extensionAuthenticated,
407
- extensionId,
408
- websocketUrl: websocketUrlBase,
409
- relayUrl: config.relayUrl
410
- }),
411
- invoke: async (method, params) => {
412
- if (
413
- extensionSocket === undefined ||
414
- extensionSocket.readyState !== WebSocket.OPEN ||
415
- !extensionAuthenticated
416
- ) {
417
- throw new Error("Chrome relay extension is not connected.");
418
- }
419
-
420
- const requestId = `request:extension:${randomUUID()}`;
421
- const response = await new Promise<unknown>((resolvePromise, rejectPromise) => {
422
- const timer = setTimeout(() => {
423
- pendingRequests.delete(requestId);
424
- rejectPromise(
425
- new Error(
426
- `Timed out waiting for extension response after ${requestTimeoutMs}ms (${method}).`
427
- )
428
- );
429
- }, requestTimeoutMs);
430
-
431
- pendingRequests.set(requestId, {
432
- resolve: resolvePromise,
433
- reject: rejectPromise,
434
- timer
435
- });
436
-
437
- try {
438
- extensionSocket.send(
439
- JSON.stringify({
440
- type: "request",
441
- id: requestId,
442
- method,
443
- params
444
- }),
445
- (error) => {
446
- if (error === undefined || error === null) {
447
- return;
448
- }
449
-
450
- const pending = pendingRequests.get(requestId);
451
- if (pending === undefined) {
452
- return;
453
- }
454
-
455
- pendingRequests.delete(requestId);
456
- clearTimeout(pending.timer);
457
- rejectPromise(error);
458
- }
459
- );
460
- } catch (error) {
461
- const pending = pendingRequests.get(requestId);
462
- if (pending !== undefined) {
463
- pendingRequests.delete(requestId);
464
- clearTimeout(pending.timer);
465
- }
466
- rejectPromise(error);
467
- }
468
- });
469
-
470
- return response;
471
- },
472
- onEvent: (listener) => {
473
- eventListeners.add(listener);
474
- return () => {
475
- eventListeners.delete(listener);
476
- };
477
- },
478
- close: async () => {
479
- rejectPendingRequests("Extension bridge closed.");
480
- eventListeners.clear();
481
- if (extensionSocket !== undefined) {
482
- try {
483
- extensionSocket.close(1000, "Bridge shutdown");
484
- } catch {
485
- // Ignore websocket close errors during shutdown.
486
- }
487
- }
488
- extensionSocket = undefined;
489
- extensionId = undefined;
490
-
491
- await new Promise<void>((resolve) => {
492
- wsServer.close(() => {
493
- resolve();
494
- });
495
- });
496
-
497
- if (httpServer.listening) {
498
- await new Promise<void>((resolve) => {
499
- httpServer.close(() => {
500
- resolve();
501
- });
502
- });
503
- }
504
- }
505
- };
506
- }