wb-browser-runtime 0.10.0 → 0.11.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.
- package/bin/wb-browser-runtime.js +73 -7
- package/package.json +1 -1
|
@@ -56,7 +56,7 @@ if (recording.enabled) {
|
|
|
56
56
|
|
|
57
57
|
const sessions = new SessionManager();
|
|
58
58
|
|
|
59
|
-
async function ensureSession(name, { profile } = {}) {
|
|
59
|
+
async function ensureSession(name, { profile, restoreSession } = {}) {
|
|
60
60
|
return sessions.ensure(name, async () => {
|
|
61
61
|
// Vendors charge for the session the moment allocate() returns; if
|
|
62
62
|
// anything after this point throws (getLiveUrl, CDP connect, newContext,
|
|
@@ -69,11 +69,22 @@ async function ensureSession(name, { profile } = {}) {
|
|
|
69
69
|
// against a cold vendor region, but the live-URL fetch and
|
|
70
70
|
// newContext/newPage can each stall independently.
|
|
71
71
|
const t0 = Date.now();
|
|
72
|
-
const
|
|
72
|
+
const restored =
|
|
73
|
+
restoreSession &&
|
|
74
|
+
restoreSession.vendor === provider.name &&
|
|
75
|
+
restoreSession.cdpUrl;
|
|
76
|
+
const allocated = restored
|
|
77
|
+
? {
|
|
78
|
+
sid: restoreSession.sid,
|
|
79
|
+
cdpUrl: restoreSession.cdpUrl,
|
|
80
|
+
_liveUrl: restoreSession.liveUrl ?? null,
|
|
81
|
+
_restored: true,
|
|
82
|
+
}
|
|
83
|
+
: await provider.allocate({ profile, sessionName: name });
|
|
73
84
|
const tAllocated = Date.now();
|
|
74
85
|
let browser = null;
|
|
75
86
|
try {
|
|
76
|
-
const liveUrl = await provider.getLiveUrl(allocated);
|
|
87
|
+
const liveUrl = allocated._liveUrl ?? (await provider.getLiveUrl(allocated));
|
|
77
88
|
browser = await chromium.connectOverCDP(allocated.cdpUrl);
|
|
78
89
|
const tConnected = Date.now();
|
|
79
90
|
const context = browser.contexts()[0] ?? (await browser.newContext());
|
|
@@ -82,6 +93,8 @@ async function ensureSession(name, { profile } = {}) {
|
|
|
82
93
|
|
|
83
94
|
const info = {
|
|
84
95
|
sid: allocated.sid,
|
|
96
|
+
cdpUrl: allocated.cdpUrl,
|
|
97
|
+
vendor: provider.name,
|
|
85
98
|
browser,
|
|
86
99
|
context,
|
|
87
100
|
page,
|
|
@@ -95,6 +108,7 @@ async function ensureSession(name, { profile } = {}) {
|
|
|
95
108
|
session_id: allocated.sid,
|
|
96
109
|
live_url: liveUrl,
|
|
97
110
|
vendor: provider.name,
|
|
111
|
+
restored: Boolean(restored),
|
|
98
112
|
started_at: new Date().toISOString(),
|
|
99
113
|
timings: {
|
|
100
114
|
allocate_ms: tAllocated - t0,
|
|
@@ -107,12 +121,12 @@ async function ensureSession(name, { profile } = {}) {
|
|
|
107
121
|
await recording.start(info, name);
|
|
108
122
|
return info;
|
|
109
123
|
} catch (e) {
|
|
110
|
-
if (browser) {
|
|
124
|
+
if (browser && !allocated._restored) {
|
|
111
125
|
try {
|
|
112
126
|
await browser.close();
|
|
113
127
|
} catch {}
|
|
114
128
|
}
|
|
115
|
-
await provider.release(allocated.sid);
|
|
129
|
+
if (!allocated._restored) await provider.release(allocated.sid);
|
|
116
130
|
throw e;
|
|
117
131
|
}
|
|
118
132
|
});
|
|
@@ -252,10 +266,14 @@ async function handleSlice(msg) {
|
|
|
252
266
|
const verbs = Array.isArray(msg.verbs) ? msg.verbs : [];
|
|
253
267
|
const sessionName = msg.session || "default";
|
|
254
268
|
const restore = msg.restore || null;
|
|
269
|
+
const restoreSession = restore?.state?.session || null;
|
|
255
270
|
|
|
256
271
|
let session;
|
|
257
272
|
try {
|
|
258
|
-
session = await ensureSession(sessionName, {
|
|
273
|
+
session = await ensureSession(sessionName, {
|
|
274
|
+
profile: msg.profile,
|
|
275
|
+
restoreSession,
|
|
276
|
+
});
|
|
259
277
|
} catch (e) {
|
|
260
278
|
send({
|
|
261
279
|
type: "slice.failed",
|
|
@@ -311,7 +329,17 @@ async function handleSlice(msg) {
|
|
|
311
329
|
// descriptor and handed back on resume. The verb can stash
|
|
312
330
|
// whatever it needs here; we always ensure verb_index is set
|
|
313
331
|
// so the dispatcher can compute startAt on re-entry.
|
|
314
|
-
sidecar_state: {
|
|
332
|
+
sidecar_state: {
|
|
333
|
+
...(pauseMeta.sidecar_state || {}),
|
|
334
|
+
verb_index: i,
|
|
335
|
+
session: {
|
|
336
|
+
vendor: session.vendor,
|
|
337
|
+
name: sessionName,
|
|
338
|
+
sid: session.sid,
|
|
339
|
+
cdpUrl: session.cdpUrl,
|
|
340
|
+
liveUrl: session.liveUrl,
|
|
341
|
+
},
|
|
342
|
+
},
|
|
315
343
|
});
|
|
316
344
|
return;
|
|
317
345
|
}
|
|
@@ -391,6 +419,32 @@ async function shutdown() {
|
|
|
391
419
|
process.exit(0);
|
|
392
420
|
}
|
|
393
421
|
|
|
422
|
+
async function suspend() {
|
|
423
|
+
if (shuttingDown) return;
|
|
424
|
+
shuttingDown = true;
|
|
425
|
+
// Flush recordings while CDP is still connected, but intentionally leave
|
|
426
|
+
// browser contexts and vendor sessions open. The operator needs the live
|
|
427
|
+
// inspector after wb exits 42, and wb resume reconnects using the persisted
|
|
428
|
+
// cdpUrl/liveUrl in sidecar_state.
|
|
429
|
+
for (const [name, info] of sessions) {
|
|
430
|
+
try {
|
|
431
|
+
await recording.flush(info, name);
|
|
432
|
+
} catch (e) {
|
|
433
|
+
log(`[suspend] flush recording ${name}: ${e.message}`);
|
|
434
|
+
try {
|
|
435
|
+
send({
|
|
436
|
+
type: "slice.recording.failed",
|
|
437
|
+
session: name,
|
|
438
|
+
run_id: recording.runId,
|
|
439
|
+
reason: `suspend_finalize_error: ${e.message}`,
|
|
440
|
+
});
|
|
441
|
+
} catch {}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
log("[suspend] leaving browser session alive for external resume");
|
|
445
|
+
process.exit(0);
|
|
446
|
+
}
|
|
447
|
+
|
|
394
448
|
// --- Main loop --------------------------------------------------------------
|
|
395
449
|
|
|
396
450
|
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
@@ -426,6 +480,15 @@ async function drainAndShutdown() {
|
|
|
426
480
|
await shutdown();
|
|
427
481
|
}
|
|
428
482
|
|
|
483
|
+
async function drainAndSuspend() {
|
|
484
|
+
try {
|
|
485
|
+
await sessions.drainAll();
|
|
486
|
+
} catch (e) {
|
|
487
|
+
log(`[suspend] drain failed: ${e.message}`);
|
|
488
|
+
}
|
|
489
|
+
await suspend();
|
|
490
|
+
}
|
|
491
|
+
|
|
429
492
|
rl.on("line", (line) => {
|
|
430
493
|
const trimmed = line.trim();
|
|
431
494
|
if (!trimmed) return;
|
|
@@ -453,6 +516,9 @@ rl.on("line", (line) => {
|
|
|
453
516
|
case "shutdown":
|
|
454
517
|
drainAndShutdown();
|
|
455
518
|
break;
|
|
519
|
+
case "suspend":
|
|
520
|
+
drainAndSuspend();
|
|
521
|
+
break;
|
|
456
522
|
default:
|
|
457
523
|
log(`[warn] unknown message type: ${msg.type}`);
|
|
458
524
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wb-browser-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Browser sidecar runtime for wb — Playwright over CDP (Browserbase, browser-use) via the wb-sidecar/1 line-framed JSON protocol.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"wb-browser-runtime": "bin/wb-browser-runtime.js"
|