@xbrowser/cli 1.9.8 → 1.10.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 (39) hide show
  1. package/dist/{anti-bot-MCVM6IFB.js → anti-bot-DR56Y63V.js} +1 -1
  2. package/dist/{browser-SJWPJFN7.js → browser-HBL72GPZ.js} +2 -2
  3. package/dist/{browser-GT4BMX6X.js → browser-T3V3JWVH.js} +1 -1
  4. package/dist/{browser-43YXUIML.js → browser-WQZ3D6AE.js} +2 -2
  5. package/dist/{cdp-driver-WAFYSIIW.js → cdp-driver-2T4P4ZE2.js} +87 -1
  6. package/dist/{cdp-driver-PUZLDSQU.js → cdp-driver-J3YQ4LJV.js} +1 -1
  7. package/dist/chunk-3OD76SUE.js +20 -0
  8. package/dist/chunk-43TEMG4U.js +9 -0
  9. package/dist/{chunk-C3LIGO4V.js → chunk-4452SPFI.js} +906 -16
  10. package/dist/{chunk-VEWTHYDG.js → chunk-5UGR6MUK.js} +906 -16
  11. package/dist/{chunk-ENCEMM7Z.js → chunk-BAHSRZIX.js} +82 -29
  12. package/dist/{chunk-7ZDWM6T2.js → chunk-BNO7OKO4.js} +5 -6
  13. package/dist/{chunk-24SY6PE5.js → chunk-CG3D3CHY.js} +1 -8
  14. package/dist/{chunk-ZJHQPMCY.js → chunk-DWDXEGVK.js} +5 -6
  15. package/dist/{chunk-DPJNCMLC.js → chunk-INTQPBYF.js} +7 -0
  16. package/dist/{human-interaction-OU7LZ6OZ.js → chunk-MWFHZUIY.js} +6 -7
  17. package/dist/{chunk-A4YHJW3Z.js → chunk-NITFVWWS.js} +168 -29
  18. package/dist/chunk-Q2DFJQGS.js +128 -0
  19. package/dist/{chunk-GM3HWQ5U.js → chunk-RRBXV7KE.js} +87 -1
  20. package/dist/{human-interaction-HJTXFVQS.js → chunk-SQHSZENE.js} +2 -2
  21. package/dist/chunk-TZPKFUBT.js +20 -0
  22. package/dist/{chunk-PBDID7SE.js → chunk-WSCP7QCJ.js} +82 -29
  23. package/dist/{chunk-YAUG3NSI.js → chunk-YMUSHPU4.js} +16 -128
  24. package/dist/cli.js +48 -31
  25. package/dist/{daemon-client-4HJENICO.js → daemon-client-GKEPT4NY.js} +15 -3
  26. package/dist/{daemon-client-CMRAWXTN.js → daemon-client-O6BYVRXV.js} +4 -1
  27. package/dist/daemon-main.js +649 -58
  28. package/dist/human-interaction-5AO42MBA.js +8 -0
  29. package/dist/{human-interaction-2DZK4MW7.js → human-interaction-C5OEGXGO.js} +1 -1
  30. package/dist/human-interaction-ISXZTAYY.js +8 -0
  31. package/dist/index.d.ts +167 -2
  32. package/dist/index.js +194 -67
  33. package/dist/recovery-EF33LKRJ.js +77 -0
  34. package/dist/recovery-J2ISVGUL.js +77 -0
  35. package/dist/recovery-ZJVVHP7N.js +76 -0
  36. package/dist/{session-recorder-K3K63Q33.js → session-recorder-H3KEYU26.js} +1 -1
  37. package/dist/{session-recorder-AG6CH6EI.js → session-recorder-QRZMKFVL.js} +1 -1
  38. package/dist/{session-replayer-V4MP6M6I.js → session-replayer-LJUC4TI7.js} +24 -7
  39. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,21 +1,7 @@
1
1
  import {
2
2
  detectAntiBot,
3
3
  formatDetectionMessage
4
- } from "./chunk-DPJNCMLC.js";
5
- import {
6
- CaptchaDetector,
7
- HumanInteractionManager,
8
- NPM_REGISTRY_URL,
9
- NPM_SCOPE,
10
- ScreencastCapturer,
11
- WebhookNotifier,
12
- getCaptchaConfig,
13
- getConfigValue,
14
- getMarketplaceUrl,
15
- loadConfig,
16
- resolveNpmPackageWithFallback,
17
- setConfigValue
18
- } from "./chunk-ZJHQPMCY.js";
4
+ } from "./chunk-INTQPBYF.js";
19
5
  import {
20
6
  forwardCommandLog,
21
7
  forwardNetworkAnalyze,
@@ -38,13 +24,32 @@ import {
38
24
  forwardSessionClose,
39
25
  forwardSessionList,
40
26
  forwardViewerCheckSelector,
27
+ version
28
+ } from "./chunk-YMUSHPU4.js";
29
+ import {
30
+ buildViewerUrl
31
+ } from "./chunk-3OD76SUE.js";
32
+ import {
33
+ CaptchaDetector,
34
+ HumanInteractionManager,
35
+ NPM_REGISTRY_URL,
36
+ NPM_SCOPE,
37
+ ScreencastCapturer,
38
+ WebhookNotifier,
39
+ getCaptchaConfig,
40
+ getConfigValue,
41
+ getMarketplaceUrl,
42
+ loadConfig,
43
+ resolveNpmPackageWithFallback,
44
+ setConfigValue
45
+ } from "./chunk-DWDXEGVK.js";
46
+ import {
41
47
  getDaemonConfig,
42
48
  getDaemonProcessStatus,
43
49
  killAllDaemonProcesses,
44
50
  startDaemonProcess,
45
- stopDaemonProcess,
46
- version
47
- } from "./chunk-YAUG3NSI.js";
51
+ stopDaemonProcess
52
+ } from "./chunk-Q2DFJQGS.js";
48
53
  import {
49
54
  generateBashScript,
50
55
  generateJSScript,
@@ -61,7 +66,7 @@ import {
61
66
  } from "./chunk-ANVL2ID2.js";
62
67
  import {
63
68
  SessionRecorder
64
- } from "./chunk-C3LIGO4V.js";
69
+ } from "./chunk-4452SPFI.js";
65
70
  import {
66
71
  addKnownIssue,
67
72
  getKnowledgePath,
@@ -86,8 +91,8 @@ import {
86
91
  resolveLaunchOpts,
87
92
  saveSessionDiskMeta,
88
93
  setActivePage
89
- } from "./chunk-PBDID7SE.js";
90
- import "./chunk-GM3HWQ5U.js";
94
+ } from "./chunk-WSCP7QCJ.js";
95
+ import "./chunk-RRBXV7KE.js";
91
96
  import "./chunk-TNEN6VQ2.js";
92
97
  import {
93
98
  errMsg
@@ -6197,7 +6202,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
6197
6202
  if (!ctx.page) {
6198
6203
  throw new Error("waitForHuman requires an active page");
6199
6204
  }
6200
- const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-2DZK4MW7.js");
6205
+ const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-C5OEGXGO.js");
6201
6206
  const wsServer2 = await getOrCreateWSServer(ctx.browserContext);
6202
6207
  const manager = new HumanInteractionManager2(wsServer2, ctx.page);
6203
6208
  return manager.waitForHuman(options);
@@ -6205,7 +6210,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
6205
6210
  }
6206
6211
  function attachDetectAntiBot(ctx) {
6207
6212
  ctx.detectAntiBot = async (page, config) => {
6208
- const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-MCVM6IFB.js");
6213
+ const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-DR56Y63V.js");
6209
6214
  return detectAntiBot2(page, config);
6210
6215
  };
6211
6216
  }
@@ -6658,18 +6663,6 @@ async function getPluginLoader() {
6658
6663
  return pluginLoader;
6659
6664
  }
6660
6665
 
6661
- // src/utils/viewer-url.ts
6662
- function buildViewerUrl(sessionName = "default") {
6663
- try {
6664
- const status = getDaemonProcessStatus();
6665
- if (!status.running) return void 0;
6666
- const port = status.port || getDaemonConfig().basePort;
6667
- return `http://localhost:${port}/preview/${encodeURIComponent(sessionName)}`;
6668
- } catch {
6669
- return void 0;
6670
- }
6671
- }
6672
-
6673
6666
  // src/plugin/login-guard.ts
6674
6667
  async function checkPluginLoginRequired(options) {
6675
6668
  const { site, command, commandName, ctx, page, sessionName } = options;
@@ -7406,7 +7399,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7406
7399
  }
7407
7400
  let targetPageOverride = null;
7408
7401
  if (_target && extraOpts?.cdpEndpoint) {
7409
- const { findTargetPage } = await import("./browser-SJWPJFN7.js");
7402
+ const { findTargetPage } = await import("./browser-HBL72GPZ.js");
7410
7403
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7411
7404
  if (!targetPageOverride) {
7412
7405
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7423,7 +7416,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7423
7416
  params = result.data;
7424
7417
  }
7425
7418
  if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
7426
- const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
7419
+ const { forwardExec } = await import("./daemon-client-O6BYVRXV.js");
7427
7420
  const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
7428
7421
  if (result) return result;
7429
7422
  }
@@ -7611,6 +7604,26 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7611
7604
  const end = Date.now();
7612
7605
  const duration = end - start;
7613
7606
  const errorMessage = errMsg(err);
7607
+ if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
7608
+ try {
7609
+ const { attemptRecovery } = await import("./recovery-ZJVVHP7N.js");
7610
+ const recovery = await attemptRecovery(
7611
+ session.page,
7612
+ sessionName,
7613
+ commandName,
7614
+ errorMessage
7615
+ );
7616
+ if (recovery.recovered) {
7617
+ const retry = await executeCommand(commandName, params, sessionName, {
7618
+ ...extraOpts,
7619
+ skipCleanup: true,
7620
+ _recoveryAttempted: true
7621
+ });
7622
+ return retry;
7623
+ }
7624
+ } catch {
7625
+ }
7626
+ }
7614
7627
  if (session) {
7615
7628
  streamCommandEvent(session.id, {
7616
7629
  sessionId: session.id,
@@ -10937,7 +10950,7 @@ async function handlePlugin(args, options, mode) {
10937
10950
  } catch {
10938
10951
  }
10939
10952
  try {
10940
- const { daemonPing } = await import("./daemon-client-CMRAWXTN.js");
10953
+ const { daemonPing } = await import("./daemon-client-O6BYVRXV.js");
10941
10954
  if (await daemonPing()) {
10942
10955
  await fetch("http://localhost:9224/rpc", {
10943
10956
  method: "POST",
@@ -11503,7 +11516,7 @@ async function handleFilter(args, _mode, options) {
11503
11516
  }
11504
11517
  }
11505
11518
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11506
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-AG6CH6EI.js");
11519
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-QRZMKFVL.js");
11507
11520
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11508
11521
  const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
11509
11522
  const { join: join14 } = await import("path");
@@ -11740,7 +11753,7 @@ async function handleRun(filePath, options) {
11740
11753
 
11741
11754
  // src/cli/viewer-routes.ts
11742
11755
  async function handleViewer(_args, options, mode, _cdpEndpoint) {
11743
- const name = options.name || process.env.XBROWSER_SESSION || "default";
11756
+ const name = options.session || process.env.XBROWSER_SESSION || "default";
11744
11757
  const selector = options.selector;
11745
11758
  const userPort = options.port ? Number(options.port) : void 0;
11746
11759
  let status = getDaemonProcessStatus();
@@ -13071,12 +13084,16 @@ async function routeCommand(argvIn, stdinCommands) {
13071
13084
  }
13072
13085
  const globalFlags = /* @__PURE__ */ new Set(["--session", "--cdp", "--json", "--yaml", "--output", "--timeout", "--help", "-h", "--version"]);
13073
13086
  let chainArgIdx = -1;
13087
+ let seenCommandWord = false;
13074
13088
  for (let i = 0; i < argv.length; i++) {
13075
13089
  if (globalFlags.has(argv[i])) continue;
13076
13090
  if (globalFlags.has(argv[i]) || i > 0 && globalFlags.has(argv[i - 1]) && !argv[i].startsWith("-")) continue;
13077
- if (argv[i].includes(" ") && /^[a-zA-Z]/.test(argv[i])) {
13078
- chainArgIdx = i;
13079
- break;
13091
+ if (!seenCommandWord) {
13092
+ if (argv[i].includes(" ") && /^[a-zA-Z]/.test(argv[i])) {
13093
+ chainArgIdx = i;
13094
+ break;
13095
+ }
13096
+ seenCommandWord = true;
13080
13097
  }
13081
13098
  }
13082
13099
  if (chainArgIdx >= 0) {
@@ -13440,9 +13457,9 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13440
13457
  }
13441
13458
  const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
13442
13459
  if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
13443
- const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
13460
+ const { forwardPluginExec } = await import("./daemon-client-O6BYVRXV.js");
13444
13461
  const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
13445
- const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
13462
+ const result = await forwardPluginExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
13446
13463
  const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
13447
13464
  if (result && result.success === false && resultData?.code === "LOGIN_REQUIRED") {
13448
13465
  process.exitCode = 1;
@@ -13480,7 +13497,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13480
13497
  const targetPage = pages[cmdTabIndex];
13481
13498
  await targetPage.bringToFront().catch(() => {
13482
13499
  });
13483
- const { setActivePage: setActivePage2 } = await import("./browser-SJWPJFN7.js");
13500
+ const { setActivePage: setActivePage2 } = await import("./browser-HBL72GPZ.js");
13484
13501
  setActivePage2(session, targetPage);
13485
13502
  }
13486
13503
  }
@@ -14083,12 +14100,51 @@ var PlaybackEngine = class _PlaybackEngine {
14083
14100
  import { EventEmitter as EventEmitter2 } from "events";
14084
14101
 
14085
14102
  // src/stream/frame-processor.ts
14086
- import sharp from "sharp";
14103
+ var _sharp = null;
14104
+ async function loadSharp() {
14105
+ if (!_sharp) {
14106
+ try {
14107
+ _sharp = (await import("sharp")).default;
14108
+ } catch (err) {
14109
+ throw new Error(
14110
+ "\u76F4\u64AD\u6D41\u5E27\u5904\u7406\u9700\u8981 sharp \u539F\u751F\u6A21\u5757\uFF0C\u5F53\u524D\u73AF\u5883\u52A0\u8F7D\u5931\u8D25\uFF1A" + (err instanceof Error ? err.message : String(err)) + "\u3002\u4E0D\u7528\u76F4\u64AD\u6D41\u529F\u80FD\u53EF\u5FFD\u7565\uFF1B\u9700\u8981\u76F4\u64AD\u8BF7\u5728\u517C\u5BB9\u73AF\u5883\u8FD0\u884C\u6216\u5B89\u88C5\u5BF9\u5E94\u5E73\u53F0\u4F9D\u8D56\u3002"
14111
+ );
14112
+ }
14113
+ }
14114
+ return _sharp;
14115
+ }
14087
14116
  var STATE_CONFIGS = {
14088
- user_interacting: { format: "jpeg", quality: 85, maxFps: 60, scale: 0.8 },
14089
- screen_moving: { format: "jpeg", quality: 85, maxFps: 8, scale: 0.8 },
14090
- static: { format: "jpeg", quality: 95, maxFps: 2, scale: 1 }
14117
+ // Full-screen: aggressive compression for speed
14118
+ // user_interacting: low quality + downscale prioritize FPS over clarity
14119
+ user_interacting: { format: "jpeg", quality: 40, maxFps: 30, scale: 0.6 },
14120
+ // screen_moving: page is animating but user is NOT operating.
14121
+ // Keep quality close to interacting to avoid visible flicker on state change.
14122
+ screen_moving: { format: "jpeg", quality: 45, maxFps: 2, scale: 0.7 },
14123
+ // static: highest quality — user is reading, bandwidth is not a concern
14124
+ static: { format: "jpeg", quality: 95, maxFps: 1, scale: 1 }
14091
14125
  };
14126
+ function adjustConfigForSize(config, frameWidth, frameHeight, fullViewportWidth, fullViewportHeight) {
14127
+ const framePixels = frameWidth * frameHeight;
14128
+ const fullPixels = fullViewportWidth * fullViewportHeight;
14129
+ const ratio = fullPixels > 0 ? framePixels / fullPixels : 1;
14130
+ if (ratio < 0.3) {
14131
+ return {
14132
+ ...config,
14133
+ quality: Math.min(config.quality + 40, 90),
14134
+ // boost quality
14135
+ scale: Math.max(config.scale, 0.9)
14136
+ // don't downscale
14137
+ };
14138
+ }
14139
+ if (ratio < 0.6) {
14140
+ return {
14141
+ ...config,
14142
+ quality: Math.min(config.quality + 20, 80),
14143
+ scale: Math.max(config.scale, 0.7)
14144
+ };
14145
+ }
14146
+ return config;
14147
+ }
14092
14148
  var StreamStateManager = class {
14093
14149
  currentState = "static";
14094
14150
  isUserInteracting = false;
@@ -14097,9 +14153,11 @@ var StreamStateManager = class {
14097
14153
  frameInterval = Infinity;
14098
14154
  onStateChange = null;
14099
14155
  staticTimer = null;
14100
- USER_INTERACTION_TIMEOUT_MS = 1e3;
14156
+ USER_INTERACTION_TIMEOUT_MS = 2e3;
14157
+ // 1s → 2s: linger in interacting longer
14101
14158
  SCREEN_MOVING_THRESHOLD_MS = 1e3;
14102
- STATIC_TIMEOUT_MS = 1500;
14159
+ STATIC_TIMEOUT_MS = 3e3;
14160
+ // 1.5s → 3s: don't rush to static
14103
14161
  setStateChangeCallback(callback) {
14104
14162
  this.onStateChange = callback;
14105
14163
  }
@@ -14206,14 +14264,28 @@ var FrameProcessor = class {
14206
14264
  if (!needsResize && !needsCrop && !needsReencode) {
14207
14265
  return buffer;
14208
14266
  }
14267
+ const sharp = await loadSharp();
14209
14268
  let processed = sharp(buffer);
14210
14269
  if (cropConfig) {
14211
- processed = processed.extract({
14212
- left: Math.round(cropConfig.x),
14213
- top: Math.round(cropConfig.y),
14214
- width: Math.round(cropConfig.width),
14215
- height: Math.round(cropConfig.height)
14216
- });
14270
+ const meta = await processed.metadata();
14271
+ const actualW = meta.width ?? viewportWidth ?? 1;
14272
+ const actualH = meta.height ?? viewportHeight ?? 1;
14273
+ const refW = viewportWidth ?? actualW;
14274
+ const refH = viewportHeight ?? actualH;
14275
+ const scaleX = actualW / refW;
14276
+ const scaleY = actualH / refH;
14277
+ const cropLeft = Math.round(cropConfig.x * scaleX);
14278
+ const cropTop = Math.round(cropConfig.y * scaleY);
14279
+ const cropW = Math.min(Math.round(cropConfig.width * scaleX), actualW - cropLeft);
14280
+ const cropH = Math.min(Math.round(cropConfig.height * scaleY), actualH - cropTop);
14281
+ if (cropW > 0 && cropH > 0 && cropLeft >= 0 && cropTop >= 0) {
14282
+ processed = processed.extract({
14283
+ left: cropLeft,
14284
+ top: cropTop,
14285
+ width: cropW,
14286
+ height: cropH
14287
+ });
14288
+ }
14217
14289
  }
14218
14290
  if (needsResize) {
14219
14291
  const newWidth = Math.round((viewportWidth ?? 0) * config.scale);
@@ -14362,11 +14434,20 @@ var StreamCoordinator = class {
14362
14434
  const crop = this.sessionCrops.get(sessionId);
14363
14435
  const cropConfig = crop ? crop.box : void 0;
14364
14436
  const effectiveViewport = crop ? { width: crop.box.width, height: crop.box.height } : frameViewport;
14365
- const processedBuffer = await this.frameProcessor.process(
14366
- frameData,
14437
+ const adjustedConfig = adjustConfigForSize(
14367
14438
  config,
14368
14439
  effectiveViewport.width,
14369
14440
  effectiveViewport.height,
14441
+ frameViewport.width,
14442
+ frameViewport.height
14443
+ );
14444
+ const processedBuffer = await this.frameProcessor.process(
14445
+ frameData,
14446
+ adjustedConfig,
14447
+ // Pass the ORIGINAL viewport (not cropped) so crop coordinates
14448
+ // can be scaled correctly from viewport space to frame pixel space.
14449
+ crop ? frameViewport.width : effectiveViewport.width,
14450
+ crop ? frameViewport.height : effectiveViewport.height,
14370
14451
  cropConfig
14371
14452
  );
14372
14453
  const header = Buffer.from(JSON.stringify({
@@ -14538,7 +14619,18 @@ var ElementMonitor = class {
14538
14619
  }
14539
14620
  return results;
14540
14621
  });
14541
- const views = elements.map((e, i) => ({
14622
+ const TOLERANCE_PX = 10;
14623
+ const deduped = [];
14624
+ const isSameSize = (a, b) => Math.abs(a.width - b.width) <= TOLERANCE_PX && Math.abs(a.height - b.height) <= TOLERANCE_PX;
14625
+ const sorted = [...elements].sort((a, b) => a.rect.width * a.rect.height - b.rect.width * b.rect.height);
14626
+ for (const el of sorted) {
14627
+ const dupe = deduped.some(
14628
+ (d) => isSameSize(d.rect, el.rect) && Math.abs(d.rect.x - el.rect.x) <= TOLERANCE_PX && Math.abs(d.rect.y - el.rect.y) <= TOLERANCE_PX
14629
+ );
14630
+ if (!dupe) deduped.push(el);
14631
+ }
14632
+ deduped.sort((a, b) => b.rect.width * b.rect.height - a.rect.width * a.rect.height);
14633
+ const views = deduped.map((e, i) => ({
14542
14634
  id: "el-" + i + "-" + (e.id || e.tag),
14543
14635
  label: e.id || e.cls || e.tag,
14544
14636
  rect: e.rect
@@ -14597,10 +14689,10 @@ var SessionManager = class extends EventEmitter {
14597
14689
  this.screencasts.set(sessionId, {
14598
14690
  capturer: new ScreencastCapturer({
14599
14691
  interval: options?.interval ?? 100,
14600
- quality: options?.quality ?? 80,
14692
+ quality: options?.quality ?? 40,
14601
14693
  type: options?.type ?? "jpeg",
14602
- width: options?.width ?? 1920,
14603
- height: options?.height ?? 1080
14694
+ width: options?.width ?? 1024,
14695
+ height: options?.height ?? 576
14604
14696
  }),
14605
14697
  page,
14606
14698
  clientCount: 0
@@ -14741,6 +14833,8 @@ var SessionManager = class extends EventEmitter {
14741
14833
  this.resetStaticSnapshotTimer(sessionId);
14742
14834
  });
14743
14835
  await sc.capturer.startCapture(sc.page, sessionId, onFrame);
14836
+ this.streamCoordinator.takeStaticSnapshot(sessionId, sc.page, sc.clientCount).catch(() => {
14837
+ });
14744
14838
  }
14745
14839
  async stopCapturer(sessionId) {
14746
14840
  const sc = this.screencasts.get(sessionId);
@@ -15191,6 +15285,15 @@ var WSServer = class extends EventEmitter2 {
15191
15285
  registerSession(sessionId, page, options) {
15192
15286
  this.sessionManager.registerSession(sessionId, page, options);
15193
15287
  }
15288
+ /** Restart screencast for a session — used by viewer reconnect button. */
15289
+ async reconnectSession(sessionId) {
15290
+ try {
15291
+ await this.sessionManager.stopCapturer(sessionId);
15292
+ await new Promise((r) => setTimeout(r, 500));
15293
+ await this.sessionManager.startCapturer(sessionId);
15294
+ } catch {
15295
+ }
15296
+ }
15194
15297
  unregisterSession(sessionId) {
15195
15298
  this.sessionManager.unregisterSession(sessionId);
15196
15299
  }
@@ -15213,7 +15316,7 @@ var WSServer = class extends EventEmitter2 {
15213
15316
  });
15214
15317
  }
15215
15318
  this.sessionManager.incrementClientCount(sessionId);
15216
- if (this.streamCoordinator.getLastFrameViewport() && this.sessionManager.isCapturerActive(sessionId)) {
15319
+ if (this.streamCoordinator.getLastFrameViewport()) {
15217
15320
  this.streamCoordinator.replayLastFrame(sessionId).catch(() => {
15218
15321
  });
15219
15322
  }
@@ -15223,7 +15326,6 @@ var WSServer = class extends EventEmitter2 {
15223
15326
  if (count === 0 && this.sessionManager.isCapturerActive(sessionId)) {
15224
15327
  this.sessionManager.stopCapturer(sessionId).then(() => {
15225
15328
  this.streamCoordinator.resetFrameRate();
15226
- this.streamCoordinator.clearLastFrame();
15227
15329
  this.emit("screencast-stopped", sessionId);
15228
15330
  }).catch(() => {
15229
15331
  this.emit("screencast-stopped", sessionId);
@@ -15399,6 +15501,31 @@ var WSServer = class extends EventEmitter2 {
15399
15501
  this.emit("human-solved", { sessionId: sessionId ?? null, clientId });
15400
15502
  return;
15401
15503
  }
15504
+ if (msg.type === "reconnect") {
15505
+ this.emit("reconnect-request", { sessionId: sessionId ?? null, clientId });
15506
+ return;
15507
+ }
15508
+ if (msg.type === "health_ping") {
15509
+ this.sendToClient(clientId, { type: "health_pong", ts: msg.ts });
15510
+ return;
15511
+ }
15512
+ if (msg.type === "snapshot_request") {
15513
+ const page2 = sessionId ? this.sessionManager.getPageForSession(sessionId) : void 0;
15514
+ if (!page2) {
15515
+ this.sendToClient(clientId, { type: "snapshot_result", data: null, error: "no page" });
15516
+ return;
15517
+ }
15518
+ const fmt = msg.format === "webp" ? "webp" : "png";
15519
+ page2.screenshot({ type: fmt, quality: fmt === "webp" ? msg.quality ?? 90 : void 0 }).then((buf) => {
15520
+ this.sendToClient(clientId, {
15521
+ type: "snapshot_result",
15522
+ data: { data: buf.toString("base64"), format: fmt }
15523
+ });
15524
+ }).catch(() => {
15525
+ this.sendToClient(clientId, { type: "snapshot_result", data: null, error: "screenshot failed" });
15526
+ });
15527
+ return;
15528
+ }
15402
15529
  const ctx = {
15403
15530
  clientId,
15404
15531
  sessionId,
@@ -16543,7 +16670,7 @@ var DataCollector = class {
16543
16670
  return results;
16544
16671
  }
16545
16672
  async createBrowserContext() {
16546
- const { launch } = await import("./cdp-driver-PUZLDSQU.js");
16673
+ const { launch } = await import("./cdp-driver-J3YQ4LJV.js");
16547
16674
  const { browser } = await launch({
16548
16675
  headless: true,
16549
16676
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
@@ -0,0 +1,77 @@
1
+ import {
2
+ buildViewerUrl
3
+ } from "./chunk-TZPKFUBT.js";
4
+ import "./chunk-CG3D3CHY.js";
5
+ import {
6
+ HumanInteractionManager
7
+ } from "./chunk-SQHSZENE.js";
8
+ import "./chunk-BNO7OKO4.js";
9
+ import "./chunk-KFQGP6VL.js";
10
+
11
+ // src/recovery.ts
12
+ function getRecoveryConfig() {
13
+ const val = process.env.XBROWSER_RECOVERY?.toLowerCase();
14
+ return {
15
+ enabled: val === "true" || val === "1" || val === "yes",
16
+ timeout: Math.max(10, parseInt(process.env.XBROWSER_RECOVERY_TIMEOUT || "120", 10))
17
+ };
18
+ }
19
+ async function attemptRecovery(page, sessionName, commandName, errorMessage, previewWS) {
20
+ const cfg = getRecoveryConfig();
21
+ if (!cfg.enabled) return { recovered: false };
22
+ if (!page) {
23
+ return { recovered: false };
24
+ }
25
+ const viewerUrl = buildViewerUrl(sessionName);
26
+ if (previewWS) {
27
+ try {
28
+ previewWS.registerSession(sessionName, page);
29
+ const manager = new HumanInteractionManager(previewWS, page);
30
+ const result = await manager.waitForHuman({
31
+ reason: `Command "${commandName}" failed: ${errorMessage}. Fix the issue and confirm via viewer.`,
32
+ timeout: cfg.timeout,
33
+ autoDetect: false
34
+ });
35
+ return { recovered: result.solved };
36
+ } catch {
37
+ }
38
+ }
39
+ console.log("");
40
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
41
+ console.log(`\u26A0\uFE0F COMMAND FAILED: ${commandName}`);
42
+ console.log(`\u26A0\uFE0F Error: ${errorMessage}`);
43
+ console.log(`\u26A0\uFE0F `);
44
+ console.log(`\u26A0\uFE0F Recovery mode activated`);
45
+ if (viewerUrl) {
46
+ console.log(`\u26A0\uFE0F Viewer: ${viewerUrl}`);
47
+ }
48
+ console.log(`\u26A0\uFE0F `);
49
+ console.log(`\u26A0\uFE0F Fix the issue, then press ENTER to retry`);
50
+ console.log(`\u26A0\uFE0F Type "abort" + ENTER to cancel`);
51
+ console.log(`\u26A0\uFE0F (${cfg.timeout}s timeout)`);
52
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
53
+ console.log("");
54
+ return new Promise((resolve) => {
55
+ const cleanup = () => {
56
+ clearTimeout(timeoutTimer);
57
+ process.stdin.removeListener("data", onData);
58
+ process.stdin.pause();
59
+ };
60
+ const timeoutTimer = setTimeout(() => {
61
+ console.log("\u23F0 Recovery timeout \u2014 aborting");
62
+ cleanup();
63
+ resolve({ recovered: false });
64
+ }, cfg.timeout * 1e3);
65
+ const onData = (buf) => {
66
+ const line = buf.toString().trim().toLowerCase();
67
+ cleanup();
68
+ resolve({ recovered: line !== "abort" && line !== "q" && line !== "exit" });
69
+ };
70
+ process.stdin.resume();
71
+ process.stdin.on("data", onData);
72
+ });
73
+ }
74
+ export {
75
+ attemptRecovery,
76
+ getRecoveryConfig
77
+ };
@@ -0,0 +1,77 @@
1
+ import {
2
+ buildViewerUrl
3
+ } from "./chunk-3OD76SUE.js";
4
+ import "./chunk-Q2DFJQGS.js";
5
+ import {
6
+ HumanInteractionManager
7
+ } from "./chunk-MWFHZUIY.js";
8
+ import "./chunk-IIM5GOD7.js";
9
+ import "./chunk-KFQGP6VL.js";
10
+
11
+ // src/recovery.ts
12
+ function getRecoveryConfig() {
13
+ const val = process.env.XBROWSER_RECOVERY?.toLowerCase();
14
+ return {
15
+ enabled: val === "true" || val === "1" || val === "yes",
16
+ timeout: Math.max(10, parseInt(process.env.XBROWSER_RECOVERY_TIMEOUT || "120", 10))
17
+ };
18
+ }
19
+ async function attemptRecovery(page, sessionName, commandName, errorMessage, previewWS) {
20
+ const cfg = getRecoveryConfig();
21
+ if (!cfg.enabled) return { recovered: false };
22
+ if (!page) {
23
+ return { recovered: false };
24
+ }
25
+ const viewerUrl = buildViewerUrl(sessionName);
26
+ if (previewWS) {
27
+ try {
28
+ previewWS.registerSession(sessionName, page);
29
+ const manager = new HumanInteractionManager(previewWS, page);
30
+ const result = await manager.waitForHuman({
31
+ reason: `Command "${commandName}" failed: ${errorMessage}. Fix the issue and confirm via viewer.`,
32
+ timeout: cfg.timeout,
33
+ autoDetect: false
34
+ });
35
+ return { recovered: result.solved };
36
+ } catch {
37
+ }
38
+ }
39
+ console.log("");
40
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
41
+ console.log(`\u26A0\uFE0F COMMAND FAILED: ${commandName}`);
42
+ console.log(`\u26A0\uFE0F Error: ${errorMessage}`);
43
+ console.log(`\u26A0\uFE0F `);
44
+ console.log(`\u26A0\uFE0F Recovery mode activated`);
45
+ if (viewerUrl) {
46
+ console.log(`\u26A0\uFE0F Viewer: ${viewerUrl}`);
47
+ }
48
+ console.log(`\u26A0\uFE0F `);
49
+ console.log(`\u26A0\uFE0F Fix the issue, then press ENTER to retry`);
50
+ console.log(`\u26A0\uFE0F Type "abort" + ENTER to cancel`);
51
+ console.log(`\u26A0\uFE0F (${cfg.timeout}s timeout)`);
52
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
53
+ console.log("");
54
+ return new Promise((resolve) => {
55
+ const cleanup = () => {
56
+ clearTimeout(timeoutTimer);
57
+ process.stdin.removeListener("data", onData);
58
+ process.stdin.pause();
59
+ };
60
+ const timeoutTimer = setTimeout(() => {
61
+ console.log("\u23F0 Recovery timeout \u2014 aborting");
62
+ cleanup();
63
+ resolve({ recovered: false });
64
+ }, cfg.timeout * 1e3);
65
+ const onData = (buf) => {
66
+ const line = buf.toString().trim().toLowerCase();
67
+ cleanup();
68
+ resolve({ recovered: line !== "abort" && line !== "q" && line !== "exit" });
69
+ };
70
+ process.stdin.resume();
71
+ process.stdin.on("data", onData);
72
+ });
73
+ }
74
+ export {
75
+ attemptRecovery,
76
+ getRecoveryConfig
77
+ };