camofox-browser 2.4.1 → 2.4.4
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/CHANGELOG.md +35 -0
- package/README.md +6 -1
- package/dist/src/routes/core.d.ts.map +1 -1
- package/dist/src/routes/core.js +128 -16
- package/dist/src/routes/core.js.map +1 -1
- package/dist/src/routes/openclaw.d.ts.map +1 -1
- package/dist/src/routes/openclaw.js +82 -9
- package/dist/src/routes/openclaw.js.map +1 -1
- package/dist/src/services/context-pool.d.ts +2 -1
- package/dist/src/services/context-pool.d.ts.map +1 -1
- package/dist/src/services/context-pool.js +46 -10
- package/dist/src/services/context-pool.js.map +1 -1
- package/dist/src/services/session.d.ts +27 -4
- package/dist/src/services/session.d.ts.map +1 -1
- package/dist/src/services/session.js +331 -75
- package/dist/src/services/session.js.map +1 -1
- package/dist/src/services/tab.d.ts +2 -1
- package/dist/src/services/tab.d.ts.map +1 -1
- package/dist/src/services/tab.js +19 -0
- package/dist/src/services/tab.js.map +1 -1
- package/dist/src/services/tracing.d.ts.map +1 -1
- package/dist/src/services/tracing.js +43 -5
- package/dist/src/services/tracing.js.map +1 -1
- package/dist/src/utils/proxy-profiles.js +4 -4
- package/dist/src/utils/proxy-profiles.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.4.4] - 2026-05-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- First tab creation now reuses the browser engine's initial untracked `about:blank` page when safe, preventing headed and virtual-display sessions from opening an extra empty window beside the requested page.
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
- Refreshed Express/body-parser/qs and CI reporter dependency locks so `npm audit --audit-level=moderate` reports zero vulnerabilities.
|
|
12
|
+
|
|
13
|
+
### Tests
|
|
14
|
+
- Added unit and real browser regression coverage for the first-tab initial blank-page reuse path.
|
|
15
|
+
|
|
16
|
+
## [2.4.3] - 2026-05-13
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Session-level `proxyProfile` and raw `proxy` settings now reach the browser context launch path, so proxy egress intent is applied instead of only being validated/stored.
|
|
20
|
+
- Session-profile contexts now use delimiter-safe runtime keys derived from `userId + sessionKey + profile signature` and profile-keyed persistent directories, preventing sibling proxy profiles for the same user from sharing one browser context or `userDataDir`.
|
|
21
|
+
- Session/user ownership checks no longer use raw `userId::sessionKey` prefix matching, so `userId` or `sessionKey` values containing `::` cannot collide with another user's sessions, tab index, or cleanup path.
|
|
22
|
+
- First-create rollback now closes staged profile-keyed contexts by user/generation and always releases the canonical mutex, so a failed proxy-profile first tab cannot wedge future retries.
|
|
23
|
+
- Rejected core/OpenClaw requests no longer persist provisional session proxy profiles or leave allocated profile-key sessions/contexts behind after runtime allocation failures.
|
|
24
|
+
- Concurrent core/OpenClaw requests for the same new session profile now wait for the profile-create attempt to commit or rollback, so a failed creator cannot delete a sibling request that already returned success.
|
|
25
|
+
- Idle lifecycle cleanup now closes and removes only the exact zero-tab profile-key session, preserving active sibling profile sessions for the same user.
|
|
26
|
+
- Display-mode toggles now prewarm the existing single profile-key context for VNC with its profile launch settings while avoiding stale default-context prelaunches before first tab create.
|
|
27
|
+
- Cookie import now rejects ambiguous user-level requests when multiple active browser contexts exist, requiring `tabId` targeting instead of importing into an arbitrary sibling context.
|
|
28
|
+
- Eviction, timeout, and shutdown cleanup now resolve encoded session/profile keys back to their raw owner user IDs for trace/download/VNC cleanup.
|
|
29
|
+
- Internal session/profile/trace ownership tokens now preserve UTF-16 code-unit identity, so malformed Unicode user/session IDs cannot collapse into replacement-character aliases or cross profile/trace ownership boundaries.
|
|
30
|
+
- Legacy UTF-8 trace artifact lookup now accepts only collision-free owner tokens, so a crafted user ID cannot use a legacy token that is also another user's UTF-16LE artifact token.
|
|
31
|
+
- Explicit session close now treats `userId` as an external owner ID only, so raw internal `u:`, `o:`, or `p:` session/profile keys cannot close another user's runtime state through `/sessions/:userId`.
|
|
32
|
+
- Default profile directory compatibility now applies only to well-formed non-internal user IDs; raw IDs that look like internal `u:`, `s:`, `p:`, or `o:` keys, or contain malformed UTF-16, remain isolated under encoded profile-key directories.
|
|
33
|
+
|
|
34
|
+
## [2.4.2] - 2026-05-13
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- `proxyProfile` now takes precedence over raw `proxy` when both are supplied for session proxy/geo resolution, matching the documented/tested contract for `/tabs` and `/tabs/open`.
|
|
38
|
+
- Refreshed runtime and dev dependency lockfile entries so full `npm audit` reports zero vulnerabilities.
|
|
39
|
+
|
|
5
40
|
## Release Audit: v2.3.0 -> v2.4.1
|
|
6
41
|
|
|
7
42
|
### What shipped in this line
|
package/README.md
CHANGED
|
@@ -393,7 +393,7 @@ This works with **Claude Code**, **Codex**, **Cursor**, **Gemini CLI**, **GitHub
|
|
|
393
393
|
| Skill | Focus | Best For |
|
|
394
394
|
|-------|-------|----------|
|
|
395
395
|
| `camofox-browser` | Full coverage (CLI + API + OpenClaw) | Complete reference |
|
|
396
|
-
| `camofox-cli` | CLI-only (50 commands) | Terminal-first workflows |
|
|
396
|
+
| `camofox-cli` | CLI-only (50+ commands) | Terminal-first workflows |
|
|
397
397
|
| `dogfood` | QA testing workflow | Systematic web app testing |
|
|
398
398
|
| `gemini-image` | Gemini image generation | AI image automation |
|
|
399
399
|
| `reddit` | Reddit automation | Reddit posting/commenting |
|
|
@@ -849,11 +849,13 @@ Then use profiles by name in API requests or CLI commands.
|
|
|
849
849
|
| `CAMOFOX_MAX_BATCH_CONCURRENCY` | `5` | Batch download concurrency cap |
|
|
850
850
|
| `CAMOFOX_MAX_BLOB_SIZE_MB` | `5` | Max blob payload size |
|
|
851
851
|
| `CAMOFOX_MAX_DOWNLOADS_PER_USER` | `500` | Per-user download record cap |
|
|
852
|
+
| `CAMOFOX_CONSOLE_BUFFER_SIZE` | `1000` | Per-tab console/error message buffer size (minimum `100`) |
|
|
852
853
|
| `HANDLER_TIMEOUT_MS` | `30000` | Handler timeout fallback |
|
|
853
854
|
| `MAX_CONCURRENT_PER_USER` | `3` | Concurrent operations per user |
|
|
854
855
|
| `CAMOFOX_VNC_BASE_PORT` | `6080` | noVNC/websockify base port |
|
|
855
856
|
| `CAMOFOX_VNC_HOST` | `localhost` | noVNC host in returned URL |
|
|
856
857
|
| `CAMOFOX_CLI_USER` | `cli-default` | Default CLI user id |
|
|
858
|
+
| `CAMOFOX_SERVER_PID_FILE` | (unset) | Optional daemon PID file path used by the CLI server manager |
|
|
857
859
|
| `CAMOFOX_IDLE_TIMEOUT_MS` | `1800000` | Stage 1 idle cleanup threshold (ms) |
|
|
858
860
|
| `CAMOFOX_IDLE_EXIT_TIMEOUT_MS` | `1800000` | Stage 2 daemon exit quiet window (ms, defaults to match Stage 1) |
|
|
859
861
|
| `CAMOFOX_PRESETS_FILE` | (unset) | Optional JSON file defining/overriding geo presets |
|
|
@@ -866,9 +868,12 @@ Then use profiles by name in API requests or CLI commands.
|
|
|
866
868
|
| `PROXY_USERNAME` | (empty) | Proxy username (server-level default) |
|
|
867
869
|
| `PROXY_PASSWORD` | (empty) | Proxy password (server-level default) |
|
|
868
870
|
| `CAMOFOX_MAX_SNAPSHOT_CHARS` | `80000` | Max characters in snapshot before truncation |
|
|
871
|
+
| `CAMOFOX_MAX_SNAPSHOT_NODES` | `2000` | Max accessibility snapshot nodes before truncation |
|
|
869
872
|
| `CAMOFOX_SNAPSHOT_TAIL_CHARS` | `5000` | Characters preserved at end of truncated snapshot |
|
|
870
873
|
| `CAMOFOX_BUILDREFS_TIMEOUT_MS` | `12000` | Timeout for building element refs |
|
|
871
874
|
| `CAMOFOX_TAB_LOCK_TIMEOUT_MS` | `30000` | Timeout for acquiring tab lock |
|
|
875
|
+
| `CAMOFOX_TRACES_DIR` | `~/.camofox/traces` | Managed Playwright trace artifact directory |
|
|
876
|
+
| `CAMOFOX_TRACE_MAX_DURATION_MS` | `300000` | Maximum trace recording duration before automatic stop |
|
|
872
877
|
| `CAMOFOX_HEALTH_PROBE_INTERVAL_MS` | `60000` | Health probe check interval |
|
|
873
878
|
| `CAMOFOX_FAILURE_THRESHOLD` | `3` | Consecutive failures before health degradation |
|
|
874
879
|
| `CAMOFOX_YT_DLP_TIMEOUT_MS` | `30000` | Timeout for yt-dlp subtitle extraction |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/routes/core.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/routes/core.ts"],"names":[],"mappings":"AAmHA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAk0DxB,eAAe,MAAM,CAAC"}
|
package/dist/src/routes/core.js
CHANGED
|
@@ -186,6 +186,16 @@ router.post('/sessions/:userId/cookies', express_1.default.json({ limit: '512kb'
|
|
|
186
186
|
message: 'Cannot import cookies without an active session. Create a tab via POST /tabs first.',
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
|
+
if (existingSessions.length > 1) {
|
|
190
|
+
(0, logging_1.log)('warn', 'cookie import rejected: ambiguous active sessions', {
|
|
191
|
+
userId: String(userId),
|
|
192
|
+
sessionCount: existingSessions.length,
|
|
193
|
+
});
|
|
194
|
+
return res.status(409).json({
|
|
195
|
+
error: 'Ambiguous active sessions',
|
|
196
|
+
message: 'Multiple active browser contexts exist for this user. Provide tabId to import cookies into a specific context.',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
189
199
|
session = existingSessions[0][1];
|
|
190
200
|
}
|
|
191
201
|
await session.context.addCookies(sanitized);
|
|
@@ -272,6 +282,11 @@ router.get('/presets', (_req, res) => {
|
|
|
272
282
|
// Create new tab
|
|
273
283
|
router.post('/tabs', async (req, res) => {
|
|
274
284
|
let createUserId;
|
|
285
|
+
let createSessionKey;
|
|
286
|
+
let createdSessionProfile = false;
|
|
287
|
+
let createdSessionProfileSignature;
|
|
288
|
+
let createdDefaultSessionProfileClaim = false;
|
|
289
|
+
let releaseSessionProfileCreate;
|
|
275
290
|
let isFirstCreator = false;
|
|
276
291
|
let stagedGeneration;
|
|
277
292
|
try {
|
|
@@ -287,10 +302,11 @@ router.post('/tabs', async (req, res) => {
|
|
|
287
302
|
return res.status(400).json({ error: 'userId and sessionKey required' });
|
|
288
303
|
}
|
|
289
304
|
createUserId = String(userId);
|
|
305
|
+
createSessionKey = String(resolvedSessionKey);
|
|
290
306
|
// Check for session profile conflict (proxy/geo drift)
|
|
307
|
+
let resolvedSessionProfile;
|
|
291
308
|
if (proxy || proxyProfile || geoMode) {
|
|
292
309
|
const { resolveSessionProfileInput, getConfiguredServerProxy, loadProxyProfiles } = await Promise.resolve().then(() => __importStar(require('../utils/proxy-profiles')));
|
|
293
|
-
const { establishSessionProfile } = await Promise.resolve().then(() => __importStar(require('../services/session')));
|
|
294
310
|
const profileInput = {
|
|
295
311
|
preset,
|
|
296
312
|
locale,
|
|
@@ -307,14 +323,10 @@ router.post('/tabs', async (req, res) => {
|
|
|
307
323
|
};
|
|
308
324
|
try {
|
|
309
325
|
const resolvedProfileBase = resolveSessionProfileInput(profileInput, deps);
|
|
310
|
-
|
|
311
|
-
establishSessionProfile(userId, resolvedSessionKey, resolvedProfile);
|
|
326
|
+
resolvedSessionProfile = { ...resolvedProfileBase, sessionKey: resolvedSessionKey };
|
|
312
327
|
}
|
|
313
328
|
catch (err) {
|
|
314
329
|
const message = err instanceof Error ? err.message : String(err);
|
|
315
|
-
if (message === 'Session profile conflict') {
|
|
316
|
-
return res.status(409).json({ error: 'Session profile conflict' });
|
|
317
|
-
}
|
|
318
330
|
return res.status(400).json({ error: message });
|
|
319
331
|
}
|
|
320
332
|
}
|
|
@@ -370,14 +382,58 @@ router.post('/tabs', async (req, res) => {
|
|
|
370
382
|
(0, logging_1.log)('error', 'canonical profile acquisition failed after retries', { userId: String(userId) });
|
|
371
383
|
return res.status(503).json({ error: 'Could not acquire canonical profile, try again' });
|
|
372
384
|
}
|
|
373
|
-
|
|
385
|
+
if (resolvedSessionProfile) {
|
|
386
|
+
while (true) {
|
|
387
|
+
await (0, session_1.waitForSessionProfileCreate)(userId, resolvedSessionKey);
|
|
388
|
+
const existingProfile = (0, session_1.getEstablishedSessionProfile)(userId, resolvedSessionKey);
|
|
389
|
+
if (existingProfile) {
|
|
390
|
+
if (existingProfile.signature !== resolvedSessionProfile.signature) {
|
|
391
|
+
return res.status(409).json({ error: 'Session profile conflict' });
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
if ((0, session_1.hasDefaultSessionProfileRuntime)(userId, resolvedSessionKey)) {
|
|
396
|
+
return res.status(409).json({ error: 'Session profile conflict' });
|
|
397
|
+
}
|
|
398
|
+
const mutex = (0, session_1.acquireSessionProfileCreateMutex)(userId, resolvedSessionKey, resolvedSessionProfile.signature);
|
|
399
|
+
if (mutex.acquired) {
|
|
400
|
+
releaseSessionProfileCreate = mutex.release;
|
|
401
|
+
try {
|
|
402
|
+
(0, session_1.establishSessionProfile)(userId, resolvedSessionKey, resolvedSessionProfile);
|
|
403
|
+
createdSessionProfile = true;
|
|
404
|
+
createdSessionProfileSignature = resolvedSessionProfile.signature;
|
|
405
|
+
}
|
|
406
|
+
catch (err) {
|
|
407
|
+
releaseSessionProfileCreate(false);
|
|
408
|
+
releaseSessionProfileCreate = undefined;
|
|
409
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
410
|
+
if (message === 'Session profile conflict') {
|
|
411
|
+
return res.status(409).json({ error: 'Session profile conflict' });
|
|
412
|
+
}
|
|
413
|
+
return res.status(400).json({ error: message });
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
await mutex.wait;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
await (0, session_1.waitForSessionProfileCreate)(userId, resolvedSessionKey);
|
|
422
|
+
if (!(0, session_1.getEstablishedSessionProfile)(userId, resolvedSessionKey)) {
|
|
423
|
+
createdDefaultSessionProfileClaim = (0, session_1.claimDefaultSessionProfileRuntime)(userId, resolvedSessionKey);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const establishedProfile = (0, session_1.getEstablishedSessionProfile)(userId, resolvedSessionKey);
|
|
427
|
+
const sessionMapKey = establishedProfile
|
|
428
|
+
? (0, session_1.getSessionMapKey)(userId, resolvedSessionKey, establishedProfile.signature)
|
|
429
|
+
: (0, session_1.getSessionMapKey)(userId, contextOverrides);
|
|
374
430
|
let tabId;
|
|
375
431
|
let pageUrl;
|
|
376
432
|
if (isFirstCreator) {
|
|
377
|
-
const staged = await (0, session_1.createStagedSession)(userId, contextOverrides);
|
|
433
|
+
const staged = await (0, session_1.createStagedSession)(userId, contextOverrides, resolvedSessionKey);
|
|
378
434
|
stagedGeneration = staged.generation;
|
|
379
435
|
const { session, generation } = staged;
|
|
380
|
-
const page = await session.context
|
|
436
|
+
const page = await (0, tab_1.acquirePageForNewTab)(session.context);
|
|
381
437
|
tabId = node_crypto_1.default.randomUUID();
|
|
382
438
|
page.__camofox_tabId = tabId;
|
|
383
439
|
const tabState = await (0, tab_1.createTabState)(page);
|
|
@@ -399,19 +455,37 @@ router.post('/tabs', async (req, res) => {
|
|
|
399
455
|
}, generation);
|
|
400
456
|
if (!committed) {
|
|
401
457
|
await (0, session_1.rollbackStagedFirstUse)(createUserId ?? userId, generation).catch(() => { });
|
|
458
|
+
if (createdSessionProfile && createdSessionProfileSignature) {
|
|
459
|
+
await (0, session_1.rollbackSessionProfileRuntime)(userId, resolvedSessionKey, createdSessionProfileSignature);
|
|
460
|
+
}
|
|
461
|
+
if (createdDefaultSessionProfileClaim) {
|
|
462
|
+
(0, session_1.clearDefaultSessionProfileClaim)(userId, resolvedSessionKey);
|
|
463
|
+
createdDefaultSessionProfileClaim = false;
|
|
464
|
+
}
|
|
465
|
+
releaseSessionProfileCreate?.(false);
|
|
466
|
+
releaseSessionProfileCreate = undefined;
|
|
402
467
|
return res.status(409).json({ error: 'Session closed during creation' });
|
|
403
468
|
}
|
|
404
469
|
(0, download_1.commitStagedDownloads)(tabId);
|
|
405
470
|
pageUrl = page.url();
|
|
406
471
|
}
|
|
407
472
|
else {
|
|
408
|
-
const session = await (0, session_1.getSession)(userId, contextOverrides);
|
|
473
|
+
const session = await (0, session_1.getSession)(userId, contextOverrides, resolvedSessionKey);
|
|
409
474
|
const totalTabs = (0, session_1.countTotalTabsForSessions)([[sessionMapKey, session]]);
|
|
410
475
|
if (totalTabs >= session_1.MAX_TABS_PER_SESSION) {
|
|
476
|
+
if (createdSessionProfile && createdSessionProfileSignature) {
|
|
477
|
+
await (0, session_1.rollbackSessionProfileRuntime)(userId, resolvedSessionKey, createdSessionProfileSignature);
|
|
478
|
+
}
|
|
479
|
+
if (createdDefaultSessionProfileClaim) {
|
|
480
|
+
(0, session_1.clearDefaultSessionProfileClaim)(userId, resolvedSessionKey);
|
|
481
|
+
createdDefaultSessionProfileClaim = false;
|
|
482
|
+
}
|
|
483
|
+
releaseSessionProfileCreate?.(false);
|
|
484
|
+
releaseSessionProfileCreate = undefined;
|
|
411
485
|
return res.status(429).json({ error: 'Maximum tabs per session reached' });
|
|
412
486
|
}
|
|
413
487
|
const group = (0, session_1.getTabGroup)(session, resolvedSessionKey);
|
|
414
|
-
const page = await session.context
|
|
488
|
+
const page = await (0, tab_1.acquirePageForNewTab)(session.context);
|
|
415
489
|
tabId = node_crypto_1.default.randomUUID();
|
|
416
490
|
page.__camofox_tabId = tabId;
|
|
417
491
|
const tabState = await (0, tab_1.createTabState)(page);
|
|
@@ -436,6 +510,9 @@ router.post('/tabs', async (req, res) => {
|
|
|
436
510
|
url: pageUrl,
|
|
437
511
|
});
|
|
438
512
|
lifecycle_controller_1.lifecycleController.recordInteractiveActivity();
|
|
513
|
+
releaseSessionProfileCreate?.(true);
|
|
514
|
+
releaseSessionProfileCreate = undefined;
|
|
515
|
+
createdSessionProfile = false;
|
|
439
516
|
return res.json({ tabId, url: pageUrl });
|
|
440
517
|
}
|
|
441
518
|
catch (err) {
|
|
@@ -447,6 +524,19 @@ router.post('/tabs', async (req, res) => {
|
|
|
447
524
|
(0, session_1.rollbackCanonicalMutex)(createUserId);
|
|
448
525
|
}
|
|
449
526
|
}
|
|
527
|
+
if (createdSessionProfile && createUserId && createSessionKey) {
|
|
528
|
+
if (createdSessionProfileSignature) {
|
|
529
|
+
await (0, session_1.rollbackSessionProfileRuntime)(createUserId, createSessionKey, createdSessionProfileSignature);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
(0, session_1.clearSessionProfile)(createUserId, createSessionKey);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (createdDefaultSessionProfileClaim && createUserId && createSessionKey) {
|
|
536
|
+
(0, session_1.clearDefaultSessionProfileClaim)(createUserId, createSessionKey);
|
|
537
|
+
}
|
|
538
|
+
releaseSessionProfileCreate?.(false);
|
|
539
|
+
releaseSessionProfileCreate = undefined;
|
|
450
540
|
const message = err instanceof Error ? err.message : String(err);
|
|
451
541
|
(0, logging_1.log)('error', 'tab create failed', { reqId: req.reqId, error: message });
|
|
452
542
|
return res.status(getRouteErrorStatus(err)).json({ error: (0, errors_1.safeError)(err) });
|
|
@@ -1079,14 +1169,28 @@ router.post('/sessions/:userId/toggle-display', async (req, res) => {
|
|
|
1079
1169
|
error: 'headless must be a boolean or "virtual"',
|
|
1080
1170
|
});
|
|
1081
1171
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1172
|
+
const existingSessions = (0, session_1.getSessionsForUser)(userId);
|
|
1173
|
+
const prewarmProfileKey = existingSessions.length === 1 ? existingSessions[0][0] : undefined;
|
|
1174
|
+
const prewarmEntry = prewarmProfileKey ? context_pool_1.contextPool.getEntry(prewarmProfileKey) : undefined;
|
|
1175
|
+
const prewarmLaunchSettings = prewarmProfileKey && !prewarmEntry ? (0, session_1.getSessionProfileLaunchSettings)(userId, prewarmProfileKey) : undefined;
|
|
1176
|
+
const prewarmOptions = prewarmEntry ? prewarmEntry.seedOptions : prewarmLaunchSettings?.contextOverrides ?? undefined;
|
|
1177
|
+
const prewarmProxy = prewarmEntry ? prewarmEntry.proxyConfig : prewarmLaunchSettings?.proxy ?? null;
|
|
1178
|
+
let tabsInvalidated = false;
|
|
1085
1179
|
let vncUrl;
|
|
1086
1180
|
if (headless === true) {
|
|
1181
|
+
if (prewarmProfileKey) {
|
|
1182
|
+
// Existing tabs become invalid after context restart/close.
|
|
1183
|
+
await (0, session_1.closeSessionsForUser)(userId, { clearProfiles: false });
|
|
1184
|
+
tabsInvalidated = true;
|
|
1185
|
+
}
|
|
1186
|
+
context_pool_1.contextPool.setHeadlessOverride(userId, headless);
|
|
1087
1187
|
await (0, vnc_1.stopVnc)(userId).catch(() => { });
|
|
1088
1188
|
}
|
|
1089
|
-
else {
|
|
1189
|
+
else if (prewarmProfileKey) {
|
|
1190
|
+
// Existing tabs become invalid after context restart.
|
|
1191
|
+
await (0, session_1.closeSessionsForUser)(userId, { clearProfiles: false });
|
|
1192
|
+
tabsInvalidated = true;
|
|
1193
|
+
await context_pool_1.contextPool.restartContext(userId, headless, prewarmProfileKey, prewarmOptions, prewarmProxy);
|
|
1090
1194
|
const displayNum = (0, context_pool_1.getDisplayForUser)(userId);
|
|
1091
1195
|
if (displayNum) {
|
|
1092
1196
|
try {
|
|
@@ -1099,13 +1203,21 @@ router.post('/sessions/:userId/toggle-display', async (req, res) => {
|
|
|
1099
1203
|
}
|
|
1100
1204
|
}
|
|
1101
1205
|
}
|
|
1206
|
+
else {
|
|
1207
|
+
context_pool_1.contextPool.setHeadlessOverride(userId, headless);
|
|
1208
|
+
}
|
|
1102
1209
|
const modeLabel = headless === false ? 'headed mode' : headless === 'virtual' ? 'virtual display mode' : 'headless mode';
|
|
1103
1210
|
return res.json({
|
|
1104
1211
|
ok: true,
|
|
1105
1212
|
headless,
|
|
1213
|
+
tabsInvalidated,
|
|
1106
1214
|
...(vncUrl
|
|
1107
1215
|
? { vncUrl, message: 'Browser visible via VNC' }
|
|
1108
|
-
: {
|
|
1216
|
+
: {
|
|
1217
|
+
message: tabsInvalidated
|
|
1218
|
+
? `Display mode updated to ${modeLabel}. Previous tabs invalidated — create new tabs.`
|
|
1219
|
+
: `Display mode override saved as ${modeLabel}. Existing tabs preserved; new contexts use the requested mode.`,
|
|
1220
|
+
}),
|
|
1109
1221
|
userId,
|
|
1110
1222
|
});
|
|
1111
1223
|
}
|