@monoes/monobrowse 1.0.13 → 1.0.15

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 (111) hide show
  1. package/dist/src/__tests__/bridge-instruments.test.d.ts +2 -0
  2. package/dist/src/__tests__/bridge-instruments.test.d.ts.map +1 -0
  3. package/dist/src/__tests__/bridge-instruments.test.js +132 -0
  4. package/dist/src/__tests__/bridge-instruments.test.js.map +1 -0
  5. package/dist/src/__tests__/bridge-transport.test.d.ts +2 -0
  6. package/dist/src/__tests__/bridge-transport.test.d.ts.map +1 -0
  7. package/dist/src/__tests__/bridge-transport.test.js +287 -0
  8. package/dist/src/__tests__/bridge-transport.test.js.map +1 -0
  9. package/dist/src/__tests__/cdp-transport.test.d.ts +2 -0
  10. package/dist/src/__tests__/cdp-transport.test.d.ts.map +1 -0
  11. package/dist/src/__tests__/cdp-transport.test.js +167 -0
  12. package/dist/src/__tests__/cdp-transport.test.js.map +1 -0
  13. package/dist/src/browser/bridge.d.ts +119 -0
  14. package/dist/src/browser/bridge.d.ts.map +1 -0
  15. package/dist/src/browser/bridge.js +264 -0
  16. package/dist/src/browser/bridge.js.map +1 -0
  17. package/dist/src/browser/browser.d.ts.map +1 -1
  18. package/dist/src/browser/browser.js +11 -0
  19. package/dist/src/browser/browser.js.map +1 -1
  20. package/dist/src/browser/cdp.d.ts +11 -1
  21. package/dist/src/browser/cdp.d.ts.map +1 -1
  22. package/dist/src/browser/cdp.js +60 -56
  23. package/dist/src/browser/cdp.js.map +1 -1
  24. package/dist/src/browser/index.d.ts +2 -0
  25. package/dist/src/browser/index.d.ts.map +1 -1
  26. package/dist/src/browser/index.js +2 -0
  27. package/dist/src/browser/index.js.map +1 -1
  28. package/dist/src/browser/transport.d.ts +62 -0
  29. package/dist/src/browser/transport.d.ts.map +1 -0
  30. package/dist/src/browser/transport.js +65 -0
  31. package/dist/src/browser/transport.js.map +1 -0
  32. package/dist/src/cli/commander-adapter.d.ts +16 -0
  33. package/dist/src/cli/commander-adapter.d.ts.map +1 -0
  34. package/dist/src/cli/commander-adapter.js +68 -0
  35. package/dist/src/cli/commander-adapter.js.map +1 -0
  36. package/dist/src/cli/commands-context.d.ts +14 -0
  37. package/dist/src/cli/commands-context.d.ts.map +1 -0
  38. package/dist/src/cli/commands-context.js +351 -0
  39. package/dist/src/cli/commands-context.js.map +1 -0
  40. package/dist/src/cli/commands-data.d.ts +12 -0
  41. package/dist/src/cli/commands-data.d.ts.map +1 -0
  42. package/dist/src/cli/commands-data.js +246 -0
  43. package/dist/src/cli/commands-data.js.map +1 -0
  44. package/dist/src/cli/commands-element.d.ts +15 -0
  45. package/dist/src/cli/commands-element.d.ts.map +1 -0
  46. package/dist/src/cli/commands-element.js +262 -0
  47. package/dist/src/cli/commands-element.js.map +1 -0
  48. package/dist/src/cli/commands-files.d.ts +9 -0
  49. package/dist/src/cli/commands-files.d.ts.map +1 -0
  50. package/dist/src/cli/commands-files.js +207 -0
  51. package/dist/src/cli/commands-files.js.map +1 -0
  52. package/dist/src/cli/commands-input.d.ts +26 -0
  53. package/dist/src/cli/commands-input.d.ts.map +1 -0
  54. package/dist/src/cli/commands-input.js +399 -0
  55. package/dist/src/cli/commands-input.js.map +1 -0
  56. package/dist/src/cli/commands-navigate.d.ts +12 -0
  57. package/dist/src/cli/commands-navigate.d.ts.map +1 -0
  58. package/dist/src/cli/commands-navigate.js +389 -0
  59. package/dist/src/cli/commands-navigate.js.map +1 -0
  60. package/dist/src/cli/commands-page.d.ts +25 -0
  61. package/dist/src/cli/commands-page.d.ts.map +1 -0
  62. package/dist/src/cli/commands-page.js +377 -0
  63. package/dist/src/cli/commands-page.js.map +1 -0
  64. package/dist/src/cli/commands-report.d.ts +8 -0
  65. package/dist/src/cli/commands-report.d.ts.map +1 -0
  66. package/dist/src/cli/commands-report.js +256 -0
  67. package/dist/src/cli/commands-report.js.map +1 -0
  68. package/dist/src/cli/commands-script.d.ts +43 -0
  69. package/dist/src/cli/commands-script.d.ts.map +1 -0
  70. package/dist/src/cli/commands-script.js +168 -0
  71. package/dist/src/cli/commands-script.js.map +1 -0
  72. package/dist/src/cli/commands-session.d.ts +15 -0
  73. package/dist/src/cli/commands-session.d.ts.map +1 -0
  74. package/dist/src/cli/commands-session.js +357 -0
  75. package/dist/src/cli/commands-session.js.map +1 -0
  76. package/dist/src/cli/commands-trace.d.ts +12 -0
  77. package/dist/src/cli/commands-trace.d.ts.map +1 -0
  78. package/dist/src/cli/commands-trace.js +347 -0
  79. package/dist/src/cli/commands-trace.js.map +1 -0
  80. package/dist/src/cli/commands.d.ts +22 -42
  81. package/dist/src/cli/commands.d.ts.map +1 -1
  82. package/dist/src/cli/commands.js +42 -3648
  83. package/dist/src/cli/commands.js.map +1 -1
  84. package/dist/src/cli/session.d.ts +47 -0
  85. package/dist/src/cli/session.d.ts.map +1 -0
  86. package/dist/src/cli/session.js +343 -0
  87. package/dist/src/cli/session.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +1 -1
  90. package/src/__tests__/bridge-instruments.test.ts +180 -0
  91. package/src/__tests__/bridge-transport.test.ts +365 -0
  92. package/src/__tests__/cdp-transport.test.ts +198 -0
  93. package/src/browser/bridge.ts +354 -0
  94. package/src/browser/browser.ts +12 -0
  95. package/src/browser/cdp.ts +57 -52
  96. package/src/browser/index.ts +2 -0
  97. package/src/browser/transport.ts +111 -0
  98. package/src/cli/commander-adapter.ts +65 -0
  99. package/src/cli/commands-context.ts +367 -0
  100. package/src/cli/commands-data.ts +248 -0
  101. package/src/cli/commands-element.ts +305 -0
  102. package/src/cli/commands-files.ts +248 -0
  103. package/src/cli/commands-input.ts +434 -0
  104. package/src/cli/commands-navigate.ts +442 -0
  105. package/src/cli/commands-page.ts +420 -0
  106. package/src/cli/commands-report.ts +275 -0
  107. package/src/cli/commands-script.ts +183 -0
  108. package/src/cli/commands-session.ts +376 -0
  109. package/src/cli/commands-trace.ts +349 -0
  110. package/src/cli/commands.ts +95 -3943
  111. package/src/cli/session.ts +412 -0
@@ -1,4 +1,5 @@
1
- import { WebSocket } from 'ws';
1
+ import type { CdpTransport } from './transport.js';
2
+ import { WebSocketTransport } from './transport.js';
2
3
  import type { CdpCommand, CdpResponse, CdpTarget } from './types.js';
3
4
 
4
5
  // send() had no timeout of its own — a command whose response never arrives
@@ -11,8 +12,12 @@ import type { CdpCommand, CdpResponse, CdpTarget } from './types.js';
11
12
  // passes 0 (no timeout) or a different value.
12
13
  export const DEFAULT_CDP_SEND_TIMEOUT_MS = 30_000;
13
14
 
15
+ // The message every path uses to say the connection went away. Shared so a
16
+ // caller matching on it does not have to care which transport was under it.
17
+ const CDP_CLOSED_MESSAGE = 'CDP connection closed';
18
+
14
19
  export class CdpClient {
15
- private ws: WebSocket | null = null;
20
+ private transport: CdpTransport | null = null;
16
21
  private pendingCommands = new Map<
17
22
  number,
18
23
  {
@@ -46,56 +51,58 @@ export class CdpClient {
46
51
  this.pendingCommands.clear();
47
52
  }
48
53
 
54
+ /** Connect to a Chrome DevTools WebSocket endpoint (the original path). */
49
55
  async connect(wsUrl: string): Promise<void> {
50
- return new Promise((resolve, reject) => {
51
- this.ws = new WebSocket(wsUrl);
56
+ return this.connectTransport(new WebSocketTransport(wsUrl));
57
+ }
52
58
 
53
- this.ws.on('open', () => {
59
+ /**
60
+ * Connect over any transport — a local DevTools socket, or the extension
61
+ * bridge to the user's own logged-in Chrome (see bridge.ts). Everything
62
+ * downstream of here (sessions, events, timeouts) is identical either way,
63
+ * which is the point: the instruments never learn which one they are on.
64
+ */
65
+ connectTransport(transport: CdpTransport): Promise<void> {
66
+ this.transport = transport;
67
+ return transport
68
+ .open({
69
+ message: (data) => this.handleMessage(data),
70
+ // Post-connect failure of either kind strands every in-flight
71
+ // command, so both flush: 'close' may not fire on all platforms.
72
+ close: (err) => this.flushPending(err ?? new Error(CDP_CLOSED_MESSAGE)),
73
+ error: (err) => this.flushPending(err),
74
+ })
75
+ .then(() => {
54
76
  this.connected = true;
55
- resolve();
56
77
  });
78
+ }
57
79
 
58
- this.ws.on('error', (err) => {
59
- if (!this.connected) {
60
- reject(err);
80
+ private handleMessage(data: string): void {
81
+ try {
82
+ const msg: CdpResponse = JSON.parse(data);
83
+ if (msg.id !== undefined && this.pendingCommands.has(msg.id)) {
84
+ const handler = this.pendingCommands.get(msg.id)!;
85
+ this.pendingCommands.delete(msg.id);
86
+ if (msg.error) {
87
+ handler.reject(new Error(`CDP error ${msg.error.code}: ${msg.error.message}`));
61
88
  } else {
62
- // Post-connect: flush all pending commands — 'close' may not fire on all platforms
63
- this.flushPending(err instanceof Error ? err : new Error(String(err)));
89
+ handler.resolve(msg);
64
90
  }
65
- });
66
-
67
- this.ws.on('close', () => {
68
- this.flushPending(new Error('CDP connection closed'));
69
- });
70
-
71
- this.ws.on('message', (data) => {
72
- try {
73
- const msg: CdpResponse = JSON.parse(data.toString());
74
- if (msg.id !== undefined && this.pendingCommands.has(msg.id)) {
75
- const handler = this.pendingCommands.get(msg.id)!;
76
- this.pendingCommands.delete(msg.id);
77
- if (msg.error) {
78
- handler.reject(new Error(`CDP error ${msg.error.code}: ${msg.error.message}`));
79
- } else {
80
- handler.resolve(msg);
81
- }
82
- } else if (msg.method) {
83
- const listeners = this.eventListeners.get(msg.method);
84
- if (listeners) {
85
- for (const fn of listeners) {
86
- try {
87
- fn(msg.params ?? {}, msg.sessionId);
88
- } catch {
89
- /* isolate per-listener errors */
90
- }
91
- }
91
+ } else if (msg.method) {
92
+ const listeners = this.eventListeners.get(msg.method);
93
+ if (listeners) {
94
+ for (const fn of listeners) {
95
+ try {
96
+ fn(msg.params ?? {}, msg.sessionId);
97
+ } catch {
98
+ /* isolate per-listener errors */
92
99
  }
93
100
  }
94
- } catch {
95
- // ignore malformed messages
96
101
  }
97
- });
98
- });
102
+ }
103
+ } catch {
104
+ // ignore malformed messages
105
+ }
99
106
  }
100
107
 
101
108
  send<T = Record<string, unknown>>(
@@ -105,7 +112,7 @@ export class CdpClient {
105
112
  timeoutMs: number = DEFAULT_CDP_SEND_TIMEOUT_MS,
106
113
  ): Promise<T> {
107
114
  return new Promise((resolve, reject) => {
108
- if (!this.ws || !this.connected) {
115
+ if (!this.transport || !this.connected) {
109
116
  reject(new Error('CDP not connected'));
110
117
  return;
111
118
  }
@@ -138,12 +145,10 @@ export class CdpClient {
138
145
  },
139
146
  timer,
140
147
  });
141
- this.ws.send(JSON.stringify(cmd), (err) => {
142
- if (err) {
143
- if (timer) clearTimeout(timer);
144
- this.pendingCommands.delete(id);
145
- reject(err);
146
- }
148
+ this.transport.send(JSON.stringify(cmd)).catch((err) => {
149
+ if (timer) clearTimeout(timer);
150
+ this.pendingCommands.delete(id);
151
+ reject(err);
147
152
  });
148
153
  });
149
154
  }
@@ -179,9 +184,9 @@ export class CdpClient {
179
184
  }
180
185
 
181
186
  close(): void {
182
- this.flushPending(new Error('CDP connection closed'));
183
- this.ws?.close();
184
- this.ws = null;
187
+ this.flushPending(new Error(CDP_CLOSED_MESSAGE));
188
+ this.transport?.close();
189
+ this.transport = null;
185
190
  this.eventListeners.clear();
186
191
  }
187
192
 
@@ -1,6 +1,7 @@
1
1
  export * from '../report/index.js';
2
2
  export * from './actions.js';
3
3
  export * from './batch.js';
4
+ export * from './bridge.js';
4
5
  export * from './browser.js';
5
6
  export * from './cdp.js';
6
7
  export * from './console-log.js';
@@ -19,6 +20,7 @@ export * from './snapshot.js';
19
20
  export * from './storage.js';
20
21
  export * from './tabs.js';
21
22
  export * from './trace.js';
23
+ export * from './transport.js';
22
24
  export * from './types.js';
23
25
  export * from './vitals.js';
24
26
  export * from './wait.js';
@@ -0,0 +1,111 @@
1
+ import { WebSocket } from 'ws';
2
+
3
+ /**
4
+ * The seam between CdpClient and whatever carries its frames.
5
+ *
6
+ * CdpClient used to own a `ws` socket outright, which meant "CDP" and "a
7
+ * WebSocket to a Chrome we launched" were the same thing. They are not: the
8
+ * MonoAgent browser extension already holds the `debugger` permission and
9
+ * speaks CDP to the user's real, logged-in Chrome — it just reaches it
10
+ * through `chrome.debugger.sendCommand` over the extension bridge instead of
11
+ * a socket. Everything monobrowse instruments (console, network, HAR,
12
+ * vitals, trace, profiler, snapshot, screenshot, emulation) goes through
13
+ * CdpClient, so moving the socket behind this interface is what lets all of
14
+ * them point at that browser without being ported one at a time.
15
+ *
16
+ * A transport deals in whole, already-serialized CDP frames. Framing,
17
+ * request ids, timeouts and event dispatch stay in CdpClient, so a transport
18
+ * has nothing to get subtly wrong about the protocol.
19
+ */
20
+ export interface CdpTransportHandlers {
21
+ /** One inbound frame: a command response or an event. */
22
+ message(data: string): void;
23
+ /**
24
+ * The transport is gone and nothing more will arrive. `err` explains why
25
+ * when the transport knows; CdpClient supplies a default when it doesn't.
26
+ */
27
+ close(err?: Error): void;
28
+ /**
29
+ * A failure *after* open() resolved. A failure before it instead rejects
30
+ * open() — the caller is still waiting on that promise, and a connect
31
+ * that fails should look like a failed connect, not a live-then-broken
32
+ * connection.
33
+ */
34
+ error(err: Error): void;
35
+ }
36
+
37
+ export interface CdpTransport {
38
+ /** Establish the connection. Resolves once frames can flow. */
39
+ open(handlers: CdpTransportHandlers): Promise<void>;
40
+ /** Send one serialized CDP frame; rejects if it could not be written. */
41
+ send(frame: string): Promise<void>;
42
+ /** Tear it down. Safe to call more than once. */
43
+ close(): void;
44
+ }
45
+
46
+ /**
47
+ * The original transport: a WebSocket to a Chrome DevTools endpoint, either
48
+ * a page's `webSocketDebuggerUrl` or the browser-level one. This is the code
49
+ * that used to live inline in CdpClient.connect(), moved wholesale so the
50
+ * local path keeps behaving exactly as it did.
51
+ */
52
+ export class WebSocketTransport implements CdpTransport {
53
+ private ws: WebSocket | null = null;
54
+ private opened = false;
55
+
56
+ /**
57
+ * `headers` is only ever used by the bridge transport, whose relay
58
+ * endpoint is token-authenticated; a DevTools endpoint wants none.
59
+ */
60
+ constructor(
61
+ private readonly url: string,
62
+ private readonly headers?: Record<string, string>,
63
+ ) {}
64
+
65
+ open(handlers: CdpTransportHandlers): Promise<void> {
66
+ return new Promise((resolve, reject) => {
67
+ const ws = this.headers
68
+ ? new WebSocket(this.url, { headers: this.headers })
69
+ : new WebSocket(this.url);
70
+ this.ws = ws;
71
+
72
+ ws.on('open', () => {
73
+ this.opened = true;
74
+ resolve();
75
+ });
76
+
77
+ ws.on('error', (err) => {
78
+ const e = err instanceof Error ? err : new Error(String(err));
79
+ if (!this.opened) {
80
+ reject(e);
81
+ } else {
82
+ // Post-connect: 'close' may not fire on all platforms, so this is
83
+ // the only notice the owner gets that its commands are stranded.
84
+ handlers.error(e);
85
+ }
86
+ });
87
+
88
+ ws.on('close', () => handlers.close());
89
+
90
+ ws.on('message', (data) => handlers.message(data.toString()));
91
+ });
92
+ }
93
+
94
+ send(frame: string): Promise<void> {
95
+ return new Promise((resolve, reject) => {
96
+ if (!this.ws) {
97
+ reject(new Error('CDP transport not open'));
98
+ return;
99
+ }
100
+ this.ws.send(frame, (err) => {
101
+ if (err) reject(err);
102
+ else resolve();
103
+ });
104
+ });
105
+ }
106
+
107
+ close(): void {
108
+ this.ws?.close();
109
+ this.ws = null;
110
+ }
111
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Commander-to-internal adapter.
3
+ *
4
+ * `action` and `platform` are written against commander, while `browse`'s own
5
+ * subcommands use this package's internal Command shape. This wraps one as the
6
+ * other so both can sit in the same subcommand list.
7
+ */
8
+
9
+ import type { Command, CommandContext, CommandResult } from './types.js';
10
+
11
+ /**
12
+ * Wraps a commander.Command instance as an internal Command object so it can
13
+ * participate in the browse command's subcommands array. The adapter
14
+ * reconstructs a raw argv string from the context that was parsed by the
15
+ * internal CLI, then hands it off to commander's parseAsync.
16
+ */
17
+ export function wrapCommanderCommand(factory: () => import('commander').Command): Command {
18
+ // Lazily instantiate so we don't pay import cost at startup
19
+ let _cmd: import('commander').Command | null = null;
20
+ const getCmd = () => {
21
+ if (!_cmd) _cmd = factory();
22
+ return _cmd;
23
+ };
24
+
25
+ const cmd = getCmd();
26
+ const subcommandDefs: Command[] = (cmd.commands as import('commander').Command[]).map((sub) => ({
27
+ name: sub.name(),
28
+ description: sub.description(),
29
+ action: async (ctx: CommandContext): Promise<CommandResult> => {
30
+ // Rebuild argv: node <cmd> <sub> [positional args] [--flag [value] ...]
31
+ const argv = ['node', cmd.name(), sub.name(), ...ctx.args];
32
+ for (const [key, val] of Object.entries(ctx.flags)) {
33
+ if (key === '_') continue;
34
+ if (typeof val === 'boolean') {
35
+ if (val) argv.push(`--${key}`);
36
+ } else if (val !== undefined && val !== null) {
37
+ argv.push(`--${key}`, String(val));
38
+ }
39
+ }
40
+ // Re-use the same commander instance to keep state (e.g. option defaults)
41
+ await getCmd().parseAsync(argv, { from: 'user' });
42
+ return { success: true };
43
+ },
44
+ }));
45
+
46
+ return {
47
+ name: cmd.name(),
48
+ description: cmd.description(),
49
+ subcommands: subcommandDefs,
50
+ action: async (ctx: CommandContext): Promise<CommandResult> => {
51
+ // No subcommand provided — show commander help
52
+ const argv = ['node', cmd.name(), ...ctx.args];
53
+ for (const [key, val] of Object.entries(ctx.flags)) {
54
+ if (key === '_') continue;
55
+ if (typeof val === 'boolean') {
56
+ if (val) argv.push(`--${key}`);
57
+ } else if (val !== undefined && val !== null) {
58
+ argv.push(`--${key}`, String(val));
59
+ }
60
+ }
61
+ await getCmd().parseAsync(argv, { from: 'user' });
62
+ return { success: true };
63
+ },
64
+ };
65
+ }