@wenrwa/marketplace-cli 0.4.4 → 0.4.6
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.
- package/dist/__tests__/bridge-activity-parser.test.d.ts +1 -0
- package/dist/__tests__/bridge-activity-parser.test.js +0 -0
- package/dist/__tests__/bridge-activity-tap.test.d.ts +1 -0
- package/dist/__tests__/bridge-activity-tap.test.js +178 -0
- package/dist/__tests__/bridge-api-client.test.d.ts +1 -0
- package/dist/__tests__/bridge-api-client.test.js +69 -0
- package/dist/__tests__/bridge-browser-driver.test.d.ts +1 -0
- package/dist/__tests__/bridge-browser-driver.test.js +379 -0
- package/dist/__tests__/bridge-browser-frames.test.d.ts +1 -0
- package/dist/__tests__/bridge-browser-frames.test.js +166 -0
- package/dist/__tests__/bridge-browser-idle.test.d.ts +1 -0
- package/dist/__tests__/bridge-browser-idle.test.js +259 -0
- package/dist/__tests__/bridge-browser-relay.test.d.ts +1 -0
- package/dist/__tests__/bridge-browser-relay.test.js +670 -0
- package/dist/__tests__/bridge-bundle-installer.test.d.ts +1 -0
- package/dist/__tests__/bridge-bundle-installer.test.js +126 -0
- package/dist/__tests__/bridge-cdp-client.test.d.ts +1 -0
- package/dist/__tests__/bridge-cdp-client.test.js +133 -0
- package/dist/__tests__/bridge-chromium-launcher.test.d.ts +1 -0
- package/dist/__tests__/bridge-chromium-launcher.test.js +202 -0
- package/dist/__tests__/bridge-command-browser-options.test.d.ts +1 -0
- package/dist/__tests__/bridge-command-browser-options.test.js +37 -0
- package/dist/__tests__/bridge-config-mcp.test.js +4 -0
- package/dist/__tests__/bridge-context-manifest-frame.test.d.ts +1 -0
- package/dist/__tests__/bridge-context-manifest-frame.test.js +125 -0
- package/dist/__tests__/bridge-context-writer.test.d.ts +1 -0
- package/dist/__tests__/bridge-context-writer.test.js +161 -0
- package/dist/__tests__/bridge-device-registration.test.d.ts +1 -0
- package/dist/__tests__/bridge-device-registration.test.js +33 -0
- package/dist/__tests__/bridge-folder-picker-runner.test.d.ts +1 -0
- package/dist/__tests__/bridge-folder-picker-runner.test.js +37 -0
- package/dist/__tests__/bridge-folder-picker.test.d.ts +1 -0
- package/dist/__tests__/bridge-folder-picker.test.js +66 -0
- package/dist/__tests__/bridge-folder-registry.test.d.ts +1 -0
- package/dist/__tests__/bridge-folder-registry.test.js +107 -0
- package/dist/__tests__/bridge-frame-pacer.test.d.ts +1 -0
- package/dist/__tests__/bridge-frame-pacer.test.js +102 -0
- package/dist/__tests__/bridge-install-bundle-frame.test.d.ts +1 -0
- package/dist/__tests__/bridge-install-bundle-frame.test.js +107 -0
- package/dist/__tests__/bridge-local-add-core.test.d.ts +1 -0
- package/dist/__tests__/bridge-local-add-core.test.js +48 -0
- package/dist/__tests__/bridge-local-path-validation-fs.test.d.ts +1 -0
- package/dist/__tests__/bridge-local-path-validation-fs.test.js +88 -0
- package/dist/__tests__/bridge-local-path-validation.test.d.ts +1 -0
- package/dist/__tests__/bridge-local-path-validation.test.js +45 -0
- package/dist/__tests__/bridge-login-status.test.d.ts +1 -0
- package/dist/__tests__/bridge-login-status.test.js +60 -0
- package/dist/__tests__/bridge-relay-client.test.d.ts +1 -0
- package/dist/__tests__/bridge-relay-client.test.js +283 -0
- package/dist/__tests__/bridge-relay-mode.test.d.ts +1 -0
- package/dist/__tests__/bridge-relay-mode.test.js +33 -0
- package/dist/__tests__/bridge-relay-pucc14.test.d.ts +1 -0
- package/dist/__tests__/bridge-relay-pucc14.test.js +230 -0
- package/dist/__tests__/bridge-server-local-endpoints.test.d.ts +1 -0
- package/dist/__tests__/bridge-server-local-endpoints.test.js +197 -0
- package/dist/__tests__/bridge-server.test.js +22 -0
- package/dist/__tests__/bridge-workspace-multirepo.test.d.ts +1 -0
- package/dist/__tests__/bridge-workspace-multirepo.test.js +317 -0
- package/dist/__tests__/devices.test.d.ts +1 -0
- package/dist/__tests__/devices.test.js +66 -0
- package/dist/__tests__/local.test.d.ts +1 -0
- package/dist/__tests__/local.test.js +147 -0
- package/dist/bridge/activityParser.d.ts +53 -0
- package/dist/bridge/activityParser.js +202 -0
- package/dist/bridge/apiClient.d.ts +56 -0
- package/dist/bridge/apiClient.js +50 -0
- package/dist/bridge/browserDriver.d.ts +98 -0
- package/dist/bridge/browserDriver.js +265 -0
- package/dist/bridge/browserFrames.d.ts +182 -0
- package/dist/bridge/browserFrames.js +179 -0
- package/dist/bridge/browserIdle.d.ts +75 -0
- package/dist/bridge/browserIdle.js +120 -0
- package/dist/bridge/bundleInstaller.d.ts +31 -0
- package/dist/bridge/bundleInstaller.js +127 -0
- package/dist/bridge/cdpClient.d.ts +47 -0
- package/dist/bridge/cdpClient.js +102 -0
- package/dist/bridge/chromiumLauncher.d.ts +45 -0
- package/dist/bridge/chromiumLauncher.js +113 -0
- package/dist/bridge/config.d.ts +3 -0
- package/dist/bridge/contextWriter.d.ts +31 -0
- package/dist/bridge/contextWriter.js +139 -0
- package/dist/bridge/deviceRegistration.d.ts +28 -0
- package/dist/bridge/deviceRegistration.js +19 -0
- package/dist/bridge/folderPicker.d.ts +34 -0
- package/dist/bridge/folderPicker.js +36 -0
- package/dist/bridge/folderPickerRunner.d.ts +3 -0
- package/dist/bridge/folderPickerRunner.js +25 -0
- package/dist/bridge/folderRegistry.d.ts +19 -0
- package/dist/bridge/folderRegistry.js +100 -0
- package/dist/bridge/framePacer.d.ts +61 -0
- package/dist/bridge/framePacer.js +79 -0
- package/dist/bridge/localAdd.d.ts +28 -0
- package/dist/bridge/localAdd.js +67 -0
- package/dist/bridge/localPathValidation.d.ts +20 -0
- package/dist/bridge/localPathValidation.js +58 -0
- package/dist/bridge/localPathValidationFs.d.ts +2 -0
- package/dist/bridge/localPathValidationFs.js +98 -0
- package/dist/bridge/loginStatus.d.ts +38 -0
- package/dist/bridge/loginStatus.js +56 -0
- package/dist/bridge/realPty.js +6 -2
- package/dist/bridge/relayClient.d.ts +166 -0
- package/dist/bridge/relayClient.js +517 -0
- package/dist/bridge/repoCheckout.d.ts +31 -0
- package/dist/bridge/repoCheckout.js +18 -0
- package/dist/bridge/server.d.ts +33 -0
- package/dist/bridge/server.js +98 -10
- package/dist/bridge/sessionManager.d.ts +18 -0
- package/dist/bridge/sessionManager.js +15 -1
- package/dist/commands/bridge.d.ts +21 -0
- package/dist/commands/bridge.js +194 -1
- package/dist/commands/devices.d.ts +9 -0
- package/dist/commands/devices.js +53 -0
- package/dist/commands/local.d.ts +9 -0
- package/dist/commands/local.js +141 -0
- package/dist/index.js +7 -1
- package/package.json +1 -1
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* cbs-02 — relay client wiring for the cloud browser session.
|
|
5
|
+
*
|
|
6
|
+
* `browser-open` / `browser-input` (pane -> container) route to a single
|
|
7
|
+
* per-session BrowserDriver instance (one browser session per container in
|
|
8
|
+
* v1); driver frames/states forward up as `browser-frame` / `browser-state`
|
|
9
|
+
* with the sessionId stamped — following exactly the install-bundle /
|
|
10
|
+
* context-manifest non-PTY precedent (never touches sessions.open / PTY
|
|
11
|
+
* paths). Uses a FAKE driver (not the real CDP one) — this is a focused
|
|
12
|
+
* unit test of the new relayClient handler functions, same shape as
|
|
13
|
+
* bridge-install-bundle-frame.test.ts.
|
|
14
|
+
*/
|
|
15
|
+
const ws_1 = require("ws");
|
|
16
|
+
const relayClient_1 = require("../bridge/relayClient");
|
|
17
|
+
const sessionManager_1 = require("../bridge/sessionManager");
|
|
18
|
+
const browserFrames_1 = require("../bridge/browserFrames");
|
|
19
|
+
class FakePty {
|
|
20
|
+
opts;
|
|
21
|
+
constructor(opts) {
|
|
22
|
+
this.opts = opts;
|
|
23
|
+
}
|
|
24
|
+
write() { }
|
|
25
|
+
resize() { }
|
|
26
|
+
kill() { }
|
|
27
|
+
onData() { }
|
|
28
|
+
onExit() { }
|
|
29
|
+
}
|
|
30
|
+
const makeFactory = () => ({ spawn: (o) => new FakePty(o) });
|
|
31
|
+
class FakeBrowserDriver {
|
|
32
|
+
startCalls = [];
|
|
33
|
+
navigateCalls = [];
|
|
34
|
+
inputCalls = [];
|
|
35
|
+
stopCalls = 0;
|
|
36
|
+
sleepCalls = [];
|
|
37
|
+
ackFrameCalls = 0;
|
|
38
|
+
frameCb = null;
|
|
39
|
+
stateCb = null;
|
|
40
|
+
async start(url) {
|
|
41
|
+
this.startCalls.push(url);
|
|
42
|
+
}
|
|
43
|
+
async navigate(url) {
|
|
44
|
+
this.navigateCalls.push(url);
|
|
45
|
+
}
|
|
46
|
+
input(event) {
|
|
47
|
+
this.inputCalls.push(event);
|
|
48
|
+
}
|
|
49
|
+
async back() { }
|
|
50
|
+
async forward() { }
|
|
51
|
+
async reload() { }
|
|
52
|
+
onFrame(cb) {
|
|
53
|
+
this.frameCb = cb;
|
|
54
|
+
}
|
|
55
|
+
onState(cb) {
|
|
56
|
+
this.stateCb = cb;
|
|
57
|
+
}
|
|
58
|
+
ackFrame() {
|
|
59
|
+
this.ackFrameCalls += 1;
|
|
60
|
+
}
|
|
61
|
+
stop() {
|
|
62
|
+
this.stopCalls += 1;
|
|
63
|
+
}
|
|
64
|
+
sleep(reason) {
|
|
65
|
+
this.sleepCalls.push(reason);
|
|
66
|
+
this.emitState({ state: 'asleep', reason });
|
|
67
|
+
}
|
|
68
|
+
// test-only helpers to drive the driver from outside
|
|
69
|
+
emitFrame(f) {
|
|
70
|
+
this.frameCb?.(f);
|
|
71
|
+
}
|
|
72
|
+
emitState(s) {
|
|
73
|
+
this.stateCb?.(s);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
class FakeBrowserIdleTracker {
|
|
77
|
+
onIdle;
|
|
78
|
+
recordInputCalls = 0;
|
|
79
|
+
recordAckCalls = 0;
|
|
80
|
+
startCalls = 0;
|
|
81
|
+
stopCalls = 0;
|
|
82
|
+
constructor(onIdle) {
|
|
83
|
+
this.onIdle = onIdle;
|
|
84
|
+
}
|
|
85
|
+
recordInput() {
|
|
86
|
+
this.recordInputCalls += 1;
|
|
87
|
+
}
|
|
88
|
+
recordAck() {
|
|
89
|
+
this.recordAckCalls += 1;
|
|
90
|
+
}
|
|
91
|
+
start() {
|
|
92
|
+
this.startCalls += 1;
|
|
93
|
+
}
|
|
94
|
+
stop() {
|
|
95
|
+
this.stopCalls += 1;
|
|
96
|
+
}
|
|
97
|
+
// test-only helper to fire the idle condition from outside
|
|
98
|
+
fireIdle() {
|
|
99
|
+
this.onIdle();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function frameQueue(ws) {
|
|
103
|
+
const frames = [];
|
|
104
|
+
const waiters = [];
|
|
105
|
+
ws.on('message', (raw) => {
|
|
106
|
+
const f = JSON.parse(String(raw));
|
|
107
|
+
const w = waiters.shift();
|
|
108
|
+
if (w)
|
|
109
|
+
w(f);
|
|
110
|
+
else
|
|
111
|
+
frames.push(f);
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
next: () => (frames.length > 0 ? Promise.resolve(frames.shift()) : new Promise((r) => waiters.push(r))),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function startFakeRelay() {
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
const wss = new ws_1.WebSocketServer({ port: 0, host: '127.0.0.1' });
|
|
120
|
+
const queue = [];
|
|
121
|
+
const waiters = [];
|
|
122
|
+
wss.on('connection', (ws) => {
|
|
123
|
+
const w = waiters.shift();
|
|
124
|
+
if (w)
|
|
125
|
+
w(ws);
|
|
126
|
+
else
|
|
127
|
+
queue.push(ws);
|
|
128
|
+
});
|
|
129
|
+
wss.on('listening', () => {
|
|
130
|
+
const addr = wss.address();
|
|
131
|
+
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
132
|
+
resolve({
|
|
133
|
+
wss,
|
|
134
|
+
port,
|
|
135
|
+
nextConnection: () => (queue.length > 0 ? Promise.resolve(queue.shift()) : new Promise((r) => waiters.push(r))),
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function closeRelay(wss) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
for (const client of wss.clients)
|
|
143
|
+
client.terminate();
|
|
144
|
+
wss.close((err) => (err ? reject(err) : resolve()));
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
describe('relay client — browser-open/browser-input (cbs-02)', () => {
|
|
148
|
+
it('browser-open creates a driver via the factory, starts it, and forwards frames/states with sessionId stamped', async () => {
|
|
149
|
+
const relay = await startFakeRelay();
|
|
150
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
151
|
+
const drivers = {};
|
|
152
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
153
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
154
|
+
relayToken: 'tok',
|
|
155
|
+
sessions,
|
|
156
|
+
createBrowserDriver: ({ sessionId }) => {
|
|
157
|
+
const d = new FakeBrowserDriver();
|
|
158
|
+
drivers[sessionId] = d;
|
|
159
|
+
return d;
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
const ws = await relay.nextConnection();
|
|
163
|
+
const q = frameQueue(ws);
|
|
164
|
+
await q.next(); // auth
|
|
165
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
166
|
+
await new Promise((r) => setTimeout(r, 20)); // let the async start() microtask settle
|
|
167
|
+
expect(drivers['browser:c1'].startCalls).toEqual(['https://example.com']);
|
|
168
|
+
// A browser session must never open a PTY.
|
|
169
|
+
expect(sessions.list()).toEqual([]);
|
|
170
|
+
drivers['browser:c1'].emitFrame({ seq: 1, data: 'abc', w: 1280, h: 800 });
|
|
171
|
+
const frameMsg = await q.next();
|
|
172
|
+
expect(frameMsg).toEqual({ t: 'browser-frame', sessionId: 'browser:c1', seq: 1, data: 'abc', w: 1280, h: 800 });
|
|
173
|
+
expect((0, browserFrames_1.isBrowserScreenFrame)(frameMsg)).toBe(true);
|
|
174
|
+
// cbs-06: the cbs-02 interim policy (self-ack on send) is GONE — the
|
|
175
|
+
// pacer is only acked by a real browser-frame-ack from the pane (see
|
|
176
|
+
// the dedicated describe block below). Forwarding a frame must not
|
|
177
|
+
// ack it on its own.
|
|
178
|
+
expect(drivers['browser:c1'].ackFrameCalls).toBe(0);
|
|
179
|
+
drivers['browser:c1'].emitState({ state: 'open', url: 'https://example.com/', title: 'Example' });
|
|
180
|
+
const stateMsg = await q.next();
|
|
181
|
+
expect(stateMsg).toEqual({ t: 'browser-state', sessionId: 'browser:c1', state: 'open', url: 'https://example.com/', title: 'Example' });
|
|
182
|
+
expect((0, browserFrames_1.isBrowserStateFrame)(stateMsg)).toBe(true);
|
|
183
|
+
handle.close();
|
|
184
|
+
await closeRelay(relay.wss);
|
|
185
|
+
});
|
|
186
|
+
it('browser-open on an already-open session navigates the existing driver instead of starting a new one', async () => {
|
|
187
|
+
const relay = await startFakeRelay();
|
|
188
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
189
|
+
const drivers = [];
|
|
190
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
191
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
192
|
+
relayToken: 'tok',
|
|
193
|
+
sessions,
|
|
194
|
+
createBrowserDriver: () => {
|
|
195
|
+
const d = new FakeBrowserDriver();
|
|
196
|
+
drivers.push(d);
|
|
197
|
+
return d;
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
const ws = await relay.nextConnection();
|
|
201
|
+
const q = frameQueue(ws);
|
|
202
|
+
await q.next(); // auth
|
|
203
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
204
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
205
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com/other' }));
|
|
206
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
207
|
+
// Exactly one driver instance for this sessionId (single driver per container session).
|
|
208
|
+
expect(drivers).toHaveLength(1);
|
|
209
|
+
expect(drivers[0].startCalls).toEqual(['https://example.com']);
|
|
210
|
+
expect(drivers[0].navigateCalls).toEqual(['https://example.com/other']);
|
|
211
|
+
handle.close();
|
|
212
|
+
await closeRelay(relay.wss);
|
|
213
|
+
});
|
|
214
|
+
it('browser-input dispatches the event to the session-keyed driver', async () => {
|
|
215
|
+
const relay = await startFakeRelay();
|
|
216
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
217
|
+
const drivers = {};
|
|
218
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
219
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
220
|
+
relayToken: 'tok',
|
|
221
|
+
sessions,
|
|
222
|
+
createBrowserDriver: ({ sessionId }) => {
|
|
223
|
+
const d = new FakeBrowserDriver();
|
|
224
|
+
drivers[sessionId] = d;
|
|
225
|
+
return d;
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
const ws = await relay.nextConnection();
|
|
229
|
+
const q = frameQueue(ws);
|
|
230
|
+
await q.next(); // auth
|
|
231
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
232
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
233
|
+
const event = { kind: 'mouse', type: 'down', x: 10, y: 20, button: 'left' };
|
|
234
|
+
ws.send(JSON.stringify({ t: 'browser-input', sessionId: 'browser:c1', event }));
|
|
235
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
236
|
+
expect(drivers['browser:c1'].inputCalls).toEqual([event]);
|
|
237
|
+
// Never opens or touches a PTY session.
|
|
238
|
+
expect(sessions.list()).toEqual([]);
|
|
239
|
+
handle.close();
|
|
240
|
+
await closeRelay(relay.wss);
|
|
241
|
+
});
|
|
242
|
+
it('browser-input for an unknown sessionId is a safe no-op (no driver, no crash)', async () => {
|
|
243
|
+
const relay = await startFakeRelay();
|
|
244
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
245
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
246
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
247
|
+
relayToken: 'tok',
|
|
248
|
+
sessions,
|
|
249
|
+
createBrowserDriver: () => new FakeBrowserDriver(),
|
|
250
|
+
});
|
|
251
|
+
const ws = await relay.nextConnection();
|
|
252
|
+
const q = frameQueue(ws);
|
|
253
|
+
await q.next(); // auth
|
|
254
|
+
ws.send(JSON.stringify({ t: 'browser-input', sessionId: 'browser:never-opened', event: { kind: 'insertText', text: 'x' } }));
|
|
255
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
256
|
+
expect(sessions.list()).toEqual([]);
|
|
257
|
+
handle.close();
|
|
258
|
+
await closeRelay(relay.wss);
|
|
259
|
+
});
|
|
260
|
+
it('errors (never crashes) when no browser driver factory is wired', async () => {
|
|
261
|
+
const relay = await startFakeRelay();
|
|
262
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
263
|
+
const handle = (0, relayClient_1.startRelayClient)({ relayUrl: `ws://127.0.0.1:${relay.port}`, relayToken: 'tok', sessions });
|
|
264
|
+
const ws = await relay.nextConnection();
|
|
265
|
+
const q = frameQueue(ws);
|
|
266
|
+
await q.next(); // auth
|
|
267
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
268
|
+
expect((await q.next()).t).toBe('error');
|
|
269
|
+
handle.close();
|
|
270
|
+
await closeRelay(relay.wss);
|
|
271
|
+
});
|
|
272
|
+
// cbs-07: this is the exact failure shape a bridge on an OLD image (no
|
|
273
|
+
// Chromium binary, or any other spawn/launch failure) hits in production —
|
|
274
|
+
// chromiumLauncher's spawn never throws synchronously on ENOENT, it
|
|
275
|
+
// rejects the driver's start() promise asynchronously instead. Proves the
|
|
276
|
+
// ship slice's degrade-gracefully choice (documented in
|
|
277
|
+
// commands/bridge.ts's createBrowserDriver wiring): a rejecting start()
|
|
278
|
+
// yields one `error` frame, cleans up bookkeeping, and — the actual point
|
|
279
|
+
// of this test — never takes the relay connection or the bridge process
|
|
280
|
+
// down with it (a second frame on a fresh session still works normally).
|
|
281
|
+
it('a driver whose start() rejects (missing/failed Chromium) errors that one session but never crashes the bridge', async () => {
|
|
282
|
+
class FailingStartBrowserDriver extends FakeBrowserDriver {
|
|
283
|
+
async start() {
|
|
284
|
+
throw new Error('spawn chromium ENOENT');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const relay = await startFakeRelay();
|
|
288
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
289
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
290
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
291
|
+
relayToken: 'tok',
|
|
292
|
+
sessions,
|
|
293
|
+
createBrowserDriver: () => new FailingStartBrowserDriver(),
|
|
294
|
+
});
|
|
295
|
+
const ws = await relay.nextConnection();
|
|
296
|
+
const q = frameQueue(ws);
|
|
297
|
+
await q.next(); // auth
|
|
298
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
299
|
+
const errorFrame = await q.next();
|
|
300
|
+
expect(errorFrame.t).toBe('error');
|
|
301
|
+
expect(errorFrame.message).toMatch(/ENOENT/);
|
|
302
|
+
// The connection (and the bridge) is unaffected — an unrelated frame on
|
|
303
|
+
// a fresh session still gets a normal reply, proving nothing wedged.
|
|
304
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c2', url: 'https://example.com' }));
|
|
305
|
+
const secondFrame = await q.next();
|
|
306
|
+
expect(secondFrame.t).toBe('error');
|
|
307
|
+
handle.close();
|
|
308
|
+
await closeRelay(relay.wss);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
describe('relay client — browser-frame-ack: true end-to-end backpressure (cbs-06)', () => {
|
|
312
|
+
it('a browser-frame-ack frame from the pane calls the driver ackFrame — nothing else does', async () => {
|
|
313
|
+
const relay = await startFakeRelay();
|
|
314
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
315
|
+
const drivers = {};
|
|
316
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
317
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
318
|
+
relayToken: 'tok',
|
|
319
|
+
sessions,
|
|
320
|
+
createBrowserDriver: ({ sessionId }) => {
|
|
321
|
+
const d = new FakeBrowserDriver();
|
|
322
|
+
drivers[sessionId] = d;
|
|
323
|
+
return d;
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
const ws = await relay.nextConnection();
|
|
327
|
+
const q = frameQueue(ws);
|
|
328
|
+
await q.next(); // auth
|
|
329
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
330
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
331
|
+
drivers['browser:c1'].emitFrame({ seq: 1, data: 'abc', w: 1280, h: 800 });
|
|
332
|
+
await q.next(); // the browser-frame forward
|
|
333
|
+
expect(drivers['browser:c1'].ackFrameCalls).toBe(0);
|
|
334
|
+
ws.send(JSON.stringify({ t: 'browser-frame-ack', sessionId: 'browser:c1', seq: 1 }));
|
|
335
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
336
|
+
expect(drivers['browser:c1'].ackFrameCalls).toBe(1);
|
|
337
|
+
handle.close();
|
|
338
|
+
await closeRelay(relay.wss);
|
|
339
|
+
});
|
|
340
|
+
it('browser-frame-ack for an unknown sessionId is a safe no-op', async () => {
|
|
341
|
+
const relay = await startFakeRelay();
|
|
342
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
343
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
344
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
345
|
+
relayToken: 'tok',
|
|
346
|
+
sessions,
|
|
347
|
+
createBrowserDriver: () => new FakeBrowserDriver(),
|
|
348
|
+
});
|
|
349
|
+
const ws = await relay.nextConnection();
|
|
350
|
+
const q = frameQueue(ws);
|
|
351
|
+
await q.next(); // auth
|
|
352
|
+
ws.send(JSON.stringify({ t: 'browser-frame-ack', sessionId: 'browser:never-opened', seq: 0 }));
|
|
353
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
354
|
+
handle.close();
|
|
355
|
+
await closeRelay(relay.wss);
|
|
356
|
+
});
|
|
357
|
+
it('on socket close, every open driver is ack-flushed so the pacer never wedges waiting for a pane that is gone', async () => {
|
|
358
|
+
const relay = await startFakeRelay();
|
|
359
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
360
|
+
const drivers = {};
|
|
361
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
362
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
363
|
+
relayToken: 'tok',
|
|
364
|
+
sessions,
|
|
365
|
+
createBrowserDriver: ({ sessionId }) => {
|
|
366
|
+
const d = new FakeBrowserDriver();
|
|
367
|
+
drivers[sessionId] = d;
|
|
368
|
+
return d;
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
const ws = await relay.nextConnection();
|
|
372
|
+
const q = frameQueue(ws);
|
|
373
|
+
await q.next(); // auth
|
|
374
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
375
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
376
|
+
drivers['browser:c1'].emitFrame({ seq: 1, data: 'abc', w: 1280, h: 800 }); // in flight, never acked by a pane
|
|
377
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
378
|
+
expect(drivers['browser:c1'].ackFrameCalls).toBe(0);
|
|
379
|
+
ws.terminate(); // simulate the pane/relay connection dropping mid-stream
|
|
380
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
381
|
+
expect(drivers['browser:c1'].ackFrameCalls).toBe(1);
|
|
382
|
+
handle.close();
|
|
383
|
+
await closeRelay(relay.wss);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
describe('relay client — browser-sleep (cbs-06)', () => {
|
|
387
|
+
it('stops the driver (asleep, reason "sleep") and forwards exactly one browser-state frame', async () => {
|
|
388
|
+
const relay = await startFakeRelay();
|
|
389
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
390
|
+
const drivers = {};
|
|
391
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
392
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
393
|
+
relayToken: 'tok',
|
|
394
|
+
sessions,
|
|
395
|
+
createBrowserDriver: ({ sessionId }) => {
|
|
396
|
+
const d = new FakeBrowserDriver();
|
|
397
|
+
drivers[sessionId] = d;
|
|
398
|
+
return d;
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
const ws = await relay.nextConnection();
|
|
402
|
+
const q = frameQueue(ws);
|
|
403
|
+
await q.next(); // auth
|
|
404
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
405
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
406
|
+
ws.send(JSON.stringify({ t: 'browser-sleep', sessionId: 'browser:c1' }));
|
|
407
|
+
const stateMsg = await q.next();
|
|
408
|
+
expect(stateMsg).toEqual({ t: 'browser-state', sessionId: 'browser:c1', state: 'asleep', reason: 'sleep' });
|
|
409
|
+
expect((0, browserFrames_1.isBrowserStateFrame)(stateMsg)).toBe(true);
|
|
410
|
+
expect(drivers['browser:c1'].sleepCalls).toEqual(['sleep']);
|
|
411
|
+
expect(drivers['browser:c1'].stopCalls).toBe(0); // sleep, not stop — distinct verb
|
|
412
|
+
handle.close();
|
|
413
|
+
await closeRelay(relay.wss);
|
|
414
|
+
});
|
|
415
|
+
it('sleep-of-nothing (never-opened session) is a safe no-op — no error, no frame', async () => {
|
|
416
|
+
const relay = await startFakeRelay();
|
|
417
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
418
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
419
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
420
|
+
relayToken: 'tok',
|
|
421
|
+
sessions,
|
|
422
|
+
createBrowserDriver: () => new FakeBrowserDriver(),
|
|
423
|
+
});
|
|
424
|
+
const ws = await relay.nextConnection();
|
|
425
|
+
const q = frameQueue(ws);
|
|
426
|
+
await q.next(); // auth
|
|
427
|
+
ws.send(JSON.stringify({ t: 'browser-sleep', sessionId: 'browser:never-opened' }));
|
|
428
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
429
|
+
// Prove no stray frame followed: open a fresh session and the next frame is its own state.
|
|
430
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c2', url: 'https://example.com' }));
|
|
431
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
432
|
+
handle.close();
|
|
433
|
+
await closeRelay(relay.wss);
|
|
434
|
+
});
|
|
435
|
+
it('double browser-sleep is a safe no-op the second time (only one asleep frame total)', async () => {
|
|
436
|
+
const relay = await startFakeRelay();
|
|
437
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
438
|
+
const drivers = {};
|
|
439
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
440
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
441
|
+
relayToken: 'tok',
|
|
442
|
+
sessions,
|
|
443
|
+
createBrowserDriver: ({ sessionId }) => {
|
|
444
|
+
const d = new FakeBrowserDriver();
|
|
445
|
+
drivers[sessionId] = d;
|
|
446
|
+
return d;
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
const ws = await relay.nextConnection();
|
|
450
|
+
const q = frameQueue(ws);
|
|
451
|
+
await q.next(); // auth
|
|
452
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
453
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
454
|
+
ws.send(JSON.stringify({ t: 'browser-sleep', sessionId: 'browser:c1' }));
|
|
455
|
+
await q.next(); // asleep state
|
|
456
|
+
ws.send(JSON.stringify({ t: 'browser-sleep', sessionId: 'browser:c1' }));
|
|
457
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
458
|
+
// Only ever one driver, one sleep() call — the second browser-sleep found
|
|
459
|
+
// no driver in the map (removed on the first asleep transition).
|
|
460
|
+
expect(Object.keys(drivers)).toEqual(['browser:c1']);
|
|
461
|
+
expect(drivers['browser:c1'].sleepCalls).toEqual(['sleep']);
|
|
462
|
+
handle.close();
|
|
463
|
+
await closeRelay(relay.wss);
|
|
464
|
+
});
|
|
465
|
+
it('a stopped (asleep) driver is REPLACED on the next browser-open, not navigated', async () => {
|
|
466
|
+
const relay = await startFakeRelay();
|
|
467
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
468
|
+
const drivers = [];
|
|
469
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
470
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
471
|
+
relayToken: 'tok',
|
|
472
|
+
sessions,
|
|
473
|
+
createBrowserDriver: () => {
|
|
474
|
+
const d = new FakeBrowserDriver();
|
|
475
|
+
drivers.push(d);
|
|
476
|
+
return d;
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
const ws = await relay.nextConnection();
|
|
480
|
+
const q = frameQueue(ws);
|
|
481
|
+
await q.next(); // auth
|
|
482
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
483
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
484
|
+
ws.send(JSON.stringify({ t: 'browser-sleep', sessionId: 'browser:c1' }));
|
|
485
|
+
await q.next(); // asleep state
|
|
486
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com/resumed' }));
|
|
487
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
488
|
+
// A SECOND driver instance was created and start()ed with the resume URL
|
|
489
|
+
// — the first (asleep) driver was never asked to navigate().
|
|
490
|
+
expect(drivers).toHaveLength(2);
|
|
491
|
+
expect(drivers[0].startCalls).toEqual(['https://example.com']);
|
|
492
|
+
expect(drivers[0].navigateCalls).toEqual([]);
|
|
493
|
+
expect(drivers[1].startCalls).toEqual(['https://example.com/resumed']);
|
|
494
|
+
handle.close();
|
|
495
|
+
await closeRelay(relay.wss);
|
|
496
|
+
});
|
|
497
|
+
it('a closed (crashed) driver is also REPLACED on the next browser-open, not navigated', async () => {
|
|
498
|
+
const relay = await startFakeRelay();
|
|
499
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
500
|
+
const drivers = [];
|
|
501
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
502
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
503
|
+
relayToken: 'tok',
|
|
504
|
+
sessions,
|
|
505
|
+
createBrowserDriver: () => {
|
|
506
|
+
const d = new FakeBrowserDriver();
|
|
507
|
+
drivers.push(d);
|
|
508
|
+
return d;
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
const ws = await relay.nextConnection();
|
|
512
|
+
const q = frameQueue(ws);
|
|
513
|
+
await q.next(); // auth
|
|
514
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
515
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
516
|
+
drivers[0].emitState({ state: 'closed', reason: 'chromium-exit code=1' }); // crash, not sleep
|
|
517
|
+
await q.next(); // closed state
|
|
518
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com/again' }));
|
|
519
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
520
|
+
expect(drivers).toHaveLength(2);
|
|
521
|
+
expect(drivers[1].startCalls).toEqual(['https://example.com/again']);
|
|
522
|
+
handle.close();
|
|
523
|
+
await closeRelay(relay.wss);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
describe('relay client — browser idle timer wiring (cbs-06)', () => {
|
|
527
|
+
it('creates and starts an idle tracker alongside the driver on browser-open', async () => {
|
|
528
|
+
const relay = await startFakeRelay();
|
|
529
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
530
|
+
const trackers = {};
|
|
531
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
532
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
533
|
+
relayToken: 'tok',
|
|
534
|
+
sessions,
|
|
535
|
+
createBrowserDriver: () => new FakeBrowserDriver(),
|
|
536
|
+
createBrowserIdleTracker: ({ sessionId, onIdle }) => {
|
|
537
|
+
const t = new FakeBrowserIdleTracker(onIdle);
|
|
538
|
+
trackers[sessionId] = t;
|
|
539
|
+
return t;
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
const ws = await relay.nextConnection();
|
|
543
|
+
const q = frameQueue(ws);
|
|
544
|
+
await q.next(); // auth
|
|
545
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
546
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
547
|
+
expect(trackers['browser:c1']).toBeDefined();
|
|
548
|
+
expect(trackers['browser:c1'].startCalls).toBe(1);
|
|
549
|
+
handle.close();
|
|
550
|
+
await closeRelay(relay.wss);
|
|
551
|
+
});
|
|
552
|
+
it('browser-input and browser-frame-ack feed the idle tracker liveness signals', async () => {
|
|
553
|
+
const relay = await startFakeRelay();
|
|
554
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
555
|
+
const drivers = {};
|
|
556
|
+
const trackers = {};
|
|
557
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
558
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
559
|
+
relayToken: 'tok',
|
|
560
|
+
sessions,
|
|
561
|
+
createBrowserDriver: ({ sessionId }) => {
|
|
562
|
+
const d = new FakeBrowserDriver();
|
|
563
|
+
drivers[sessionId] = d;
|
|
564
|
+
return d;
|
|
565
|
+
},
|
|
566
|
+
createBrowserIdleTracker: ({ sessionId, onIdle }) => {
|
|
567
|
+
const t = new FakeBrowserIdleTracker(onIdle);
|
|
568
|
+
trackers[sessionId] = t;
|
|
569
|
+
return t;
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
const ws = await relay.nextConnection();
|
|
573
|
+
const q = frameQueue(ws);
|
|
574
|
+
await q.next(); // auth
|
|
575
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
576
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
577
|
+
const event = { kind: 'insertText', text: 'x' };
|
|
578
|
+
ws.send(JSON.stringify({ t: 'browser-input', sessionId: 'browser:c1', event }));
|
|
579
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
580
|
+
expect(trackers['browser:c1'].recordInputCalls).toBe(1);
|
|
581
|
+
ws.send(JSON.stringify({ t: 'browser-frame-ack', sessionId: 'browser:c1', seq: 0 }));
|
|
582
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
583
|
+
expect(trackers['browser:c1'].recordAckCalls).toBe(1);
|
|
584
|
+
handle.close();
|
|
585
|
+
await closeRelay(relay.wss);
|
|
586
|
+
});
|
|
587
|
+
it('onIdle firing closes the browser (asleep, reason idle-timeout) and stops the tracker', async () => {
|
|
588
|
+
const relay = await startFakeRelay();
|
|
589
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
590
|
+
const drivers = {};
|
|
591
|
+
const trackers = {};
|
|
592
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
593
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
594
|
+
relayToken: 'tok',
|
|
595
|
+
sessions,
|
|
596
|
+
createBrowserDriver: ({ sessionId }) => {
|
|
597
|
+
const d = new FakeBrowserDriver();
|
|
598
|
+
drivers[sessionId] = d;
|
|
599
|
+
return d;
|
|
600
|
+
},
|
|
601
|
+
createBrowserIdleTracker: ({ sessionId, onIdle }) => {
|
|
602
|
+
const t = new FakeBrowserIdleTracker(onIdle);
|
|
603
|
+
trackers[sessionId] = t;
|
|
604
|
+
return t;
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
const ws = await relay.nextConnection();
|
|
608
|
+
const q = frameQueue(ws);
|
|
609
|
+
await q.next(); // auth
|
|
610
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
611
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
612
|
+
trackers['browser:c1'].fireIdle();
|
|
613
|
+
const stateMsg = await q.next();
|
|
614
|
+
expect(stateMsg).toEqual({ t: 'browser-state', sessionId: 'browser:c1', state: 'asleep', reason: 'idle-timeout' });
|
|
615
|
+
expect((0, browserFrames_1.isBrowserStateFrame)(stateMsg)).toBe(true);
|
|
616
|
+
expect(drivers['browser:c1'].sleepCalls).toEqual(['idle-timeout']);
|
|
617
|
+
expect(trackers['browser:c1'].stopCalls).toBe(1);
|
|
618
|
+
handle.close();
|
|
619
|
+
await closeRelay(relay.wss);
|
|
620
|
+
});
|
|
621
|
+
it('the idle tracker is torn down (stopped) and a fresh one created when the session resumes', async () => {
|
|
622
|
+
const relay = await startFakeRelay();
|
|
623
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
624
|
+
const trackers = [];
|
|
625
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
626
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
627
|
+
relayToken: 'tok',
|
|
628
|
+
sessions,
|
|
629
|
+
createBrowserDriver: () => new FakeBrowserDriver(),
|
|
630
|
+
createBrowserIdleTracker: ({ onIdle }) => {
|
|
631
|
+
const t = new FakeBrowserIdleTracker(onIdle);
|
|
632
|
+
trackers.push(t);
|
|
633
|
+
return t;
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
const ws = await relay.nextConnection();
|
|
637
|
+
const q = frameQueue(ws);
|
|
638
|
+
await q.next(); // auth
|
|
639
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
640
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
641
|
+
ws.send(JSON.stringify({ t: 'browser-sleep', sessionId: 'browser:c1' }));
|
|
642
|
+
await q.next(); // asleep
|
|
643
|
+
expect(trackers[0].stopCalls).toBe(1);
|
|
644
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com/resumed' }));
|
|
645
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
646
|
+
expect(trackers).toHaveLength(2);
|
|
647
|
+
expect(trackers[1].startCalls).toBe(1);
|
|
648
|
+
handle.close();
|
|
649
|
+
await closeRelay(relay.wss);
|
|
650
|
+
});
|
|
651
|
+
it('absent createBrowserIdleTracker: browser-open still works, no idle tracking attempted', async () => {
|
|
652
|
+
const relay = await startFakeRelay();
|
|
653
|
+
const sessions = new sessionManager_1.SessionManager(makeFactory());
|
|
654
|
+
const handle = (0, relayClient_1.startRelayClient)({
|
|
655
|
+
relayUrl: `ws://127.0.0.1:${relay.port}`,
|
|
656
|
+
relayToken: 'tok',
|
|
657
|
+
sessions,
|
|
658
|
+
createBrowserDriver: () => new FakeBrowserDriver(),
|
|
659
|
+
});
|
|
660
|
+
const ws = await relay.nextConnection();
|
|
661
|
+
const q = frameQueue(ws);
|
|
662
|
+
await q.next(); // auth
|
|
663
|
+
ws.send(JSON.stringify({ t: 'browser-open', sessionId: 'browser:c1', url: 'https://example.com' }));
|
|
664
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
665
|
+
ws.send(JSON.stringify({ t: 'browser-input', sessionId: 'browser:c1', event: { kind: 'insertText', text: 'x' } }));
|
|
666
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
667
|
+
handle.close();
|
|
668
|
+
await closeRelay(relay.wss);
|
|
669
|
+
});
|
|
670
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|