@hasna/browser 0.0.2 → 0.0.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.
Files changed (41) hide show
  1. package/dist/cli/index.js +1576 -289
  2. package/dist/db/sessions.d.ts.map +1 -1
  3. package/dist/index.js +418 -157
  4. package/dist/lib/actions-ref.test.d.ts +2 -0
  5. package/dist/lib/actions-ref.test.d.ts.map +1 -0
  6. package/dist/lib/actions.d.ts +12 -0
  7. package/dist/lib/actions.d.ts.map +1 -1
  8. package/dist/lib/annotate.d.ts +18 -0
  9. package/dist/lib/annotate.d.ts.map +1 -0
  10. package/dist/lib/annotate.test.d.ts +2 -0
  11. package/dist/lib/annotate.test.d.ts.map +1 -0
  12. package/dist/lib/dialogs.d.ts +15 -0
  13. package/dist/lib/dialogs.d.ts.map +1 -0
  14. package/dist/lib/profiles.d.ts +23 -0
  15. package/dist/lib/profiles.d.ts.map +1 -0
  16. package/dist/lib/screenshot-v4.test.d.ts +2 -0
  17. package/dist/lib/screenshot-v4.test.d.ts.map +1 -0
  18. package/dist/lib/screenshot.d.ts.map +1 -1
  19. package/dist/lib/session-v3.test.d.ts +2 -0
  20. package/dist/lib/session-v3.test.d.ts.map +1 -0
  21. package/dist/lib/session.d.ts +5 -0
  22. package/dist/lib/session.d.ts.map +1 -1
  23. package/dist/lib/snapshot-diff.test.d.ts +2 -0
  24. package/dist/lib/snapshot-diff.test.d.ts.map +1 -0
  25. package/dist/lib/snapshot.d.ts +33 -0
  26. package/dist/lib/snapshot.d.ts.map +1 -0
  27. package/dist/lib/snapshot.test.d.ts +2 -0
  28. package/dist/lib/snapshot.test.d.ts.map +1 -0
  29. package/dist/lib/stealth.d.ts +5 -0
  30. package/dist/lib/stealth.d.ts.map +1 -0
  31. package/dist/lib/stealth.test.d.ts +2 -0
  32. package/dist/lib/stealth.test.d.ts.map +1 -0
  33. package/dist/lib/tabs.d.ts +18 -0
  34. package/dist/lib/tabs.d.ts.map +1 -0
  35. package/dist/mcp/index.js +1591 -312
  36. package/dist/mcp/v4.test.d.ts +2 -0
  37. package/dist/mcp/v4.test.d.ts.map +1 -0
  38. package/dist/server/index.js +340 -173
  39. package/dist/types/index.d.ts +35 -0
  40. package/dist/types/index.d.ts.map +1 -1
  41. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6827,7 +6827,14 @@ import { randomUUID as randomUUID3 } from "crypto";
6827
6827
  function createSession(data) {
6828
6828
  const db = getDatabase();
6829
6829
  const id = randomUUID3();
6830
- db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, data.name ?? null);
6830
+ let name = data.name ?? null;
6831
+ if (name) {
6832
+ const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
6833
+ if (existing) {
6834
+ name = `${name}-${id.slice(0, 6)}`;
6835
+ }
6836
+ }
6837
+ db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
6831
6838
  return getSession(id);
6832
6839
  }
6833
6840
  function getSessionByName(name) {
@@ -7451,6 +7458,261 @@ function inferUseCase(label) {
7451
7458
  };
7452
7459
  return map[label.toLowerCase()] ?? "spa_navigate" /* SPA_NAVIGATE */;
7453
7460
  }
7461
+ // src/lib/network.ts
7462
+ function enableNetworkLogging(page, sessionId) {
7463
+ const requestStart = new Map;
7464
+ const onRequest = (req) => {
7465
+ requestStart.set(req.url(), Date.now());
7466
+ };
7467
+ const onResponse = (res) => {
7468
+ const start = requestStart.get(res.url()) ?? Date.now();
7469
+ const duration = Date.now() - start;
7470
+ const req = res.request();
7471
+ try {
7472
+ logRequest({
7473
+ session_id: sessionId,
7474
+ method: req.method(),
7475
+ url: res.url(),
7476
+ status_code: res.status(),
7477
+ request_headers: JSON.stringify(req.headers()),
7478
+ response_headers: JSON.stringify(res.headers()),
7479
+ body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
7480
+ duration_ms: duration,
7481
+ resource_type: req.resourceType()
7482
+ });
7483
+ } catch {}
7484
+ };
7485
+ page.on("request", onRequest);
7486
+ page.on("response", onResponse);
7487
+ return () => {
7488
+ page.off("request", onRequest);
7489
+ page.off("response", onResponse);
7490
+ };
7491
+ }
7492
+ async function addInterceptRule(page, rule) {
7493
+ await page.route(rule.pattern, async (route) => {
7494
+ if (rule.action === "block") {
7495
+ await route.abort();
7496
+ } else if (rule.action === "modify" && rule.response) {
7497
+ await route.fulfill({
7498
+ status: rule.response.status,
7499
+ body: rule.response.body,
7500
+ headers: rule.response.headers
7501
+ });
7502
+ } else {
7503
+ await route.continue();
7504
+ }
7505
+ });
7506
+ }
7507
+ async function clearInterceptRules(page) {
7508
+ await page.unrouteAll();
7509
+ }
7510
+ function startHAR(page) {
7511
+ const entries = [];
7512
+ const requestStart = new Map;
7513
+ const onRequest = (req) => {
7514
+ requestStart.set(req.url() + req.method(), {
7515
+ time: Date.now(),
7516
+ method: req.method(),
7517
+ headers: req.headers(),
7518
+ postData: req.postData() ?? undefined
7519
+ });
7520
+ };
7521
+ const onResponse = async (res) => {
7522
+ const key = res.url() + res.request().method();
7523
+ const start = requestStart.get(key);
7524
+ if (!start)
7525
+ return;
7526
+ const duration = Date.now() - start.time;
7527
+ const entry = {
7528
+ startedDateTime: new Date(start.time).toISOString(),
7529
+ time: duration,
7530
+ request: {
7531
+ method: start.method,
7532
+ url: res.url(),
7533
+ headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
7534
+ postData: start.postData ? { text: start.postData } : undefined
7535
+ },
7536
+ response: {
7537
+ status: res.status(),
7538
+ statusText: res.statusText(),
7539
+ headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
7540
+ content: {
7541
+ size: parseInt(res.headers()["content-length"] ?? "0") || 0,
7542
+ mimeType: res.headers()["content-type"] ?? "application/octet-stream"
7543
+ }
7544
+ },
7545
+ timings: { send: 0, wait: duration, receive: 0 }
7546
+ };
7547
+ entries.push(entry);
7548
+ requestStart.delete(key);
7549
+ };
7550
+ page.on("request", onRequest);
7551
+ page.on("response", onResponse);
7552
+ return {
7553
+ entries,
7554
+ stop: () => {
7555
+ page.off("request", onRequest);
7556
+ page.off("response", onResponse);
7557
+ return {
7558
+ log: {
7559
+ version: "1.2",
7560
+ creator: { name: "@hasna/browser", version: "0.0.1" },
7561
+ entries
7562
+ }
7563
+ };
7564
+ }
7565
+ };
7566
+ }
7567
+
7568
+ // src/lib/console.ts
7569
+ function enableConsoleCapture(page, sessionId) {
7570
+ const onConsole = (msg) => {
7571
+ const levelMap = {
7572
+ log: "log",
7573
+ warn: "warn",
7574
+ error: "error",
7575
+ debug: "debug",
7576
+ info: "info",
7577
+ warning: "warn"
7578
+ };
7579
+ const level = levelMap[msg.type()] ?? "log";
7580
+ const location = msg.location();
7581
+ try {
7582
+ logConsoleMessage({
7583
+ session_id: sessionId,
7584
+ level,
7585
+ message: msg.text(),
7586
+ source: location.url || undefined,
7587
+ line_number: location.lineNumber || undefined
7588
+ });
7589
+ } catch {}
7590
+ };
7591
+ page.on("console", onConsole);
7592
+ return () => page.off("console", onConsole);
7593
+ }
7594
+ async function capturePageErrors(page, sessionId) {
7595
+ const onError = (err) => {
7596
+ try {
7597
+ logConsoleMessage({
7598
+ session_id: sessionId,
7599
+ level: "error",
7600
+ message: `[Page Error] ${err.message}`,
7601
+ source: err.stack?.split(`
7602
+ `)[1]?.trim()
7603
+ });
7604
+ } catch {}
7605
+ };
7606
+ page.on("pageerror", onError);
7607
+ return () => page.off("pageerror", onError);
7608
+ }
7609
+
7610
+ // src/lib/stealth.ts
7611
+ var REALISTIC_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
7612
+ var STEALTH_SCRIPT = `
7613
+ // \u2500\u2500 1. Remove navigator.webdriver flag \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7614
+ Object.defineProperty(navigator, 'webdriver', {
7615
+ get: () => false,
7616
+ configurable: true,
7617
+ });
7618
+
7619
+ // \u2500\u2500 2. Override navigator.plugins to show typical Chrome plugins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7620
+ Object.defineProperty(navigator, 'plugins', {
7621
+ get: () => {
7622
+ const plugins = [
7623
+ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 1 },
7624
+ { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },
7625
+ { name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },
7626
+ ];
7627
+ // Mimic PluginArray interface
7628
+ const pluginArray = Object.create(PluginArray.prototype);
7629
+ plugins.forEach((p, i) => {
7630
+ const plugin = Object.create(Plugin.prototype);
7631
+ Object.defineProperties(plugin, {
7632
+ name: { value: p.name, enumerable: true },
7633
+ filename: { value: p.filename, enumerable: true },
7634
+ description: { value: p.description, enumerable: true },
7635
+ length: { value: p.length, enumerable: true },
7636
+ });
7637
+ pluginArray[i] = plugin;
7638
+ });
7639
+ Object.defineProperty(pluginArray, 'length', { value: plugins.length });
7640
+ pluginArray.item = (i) => pluginArray[i] || null;
7641
+ pluginArray.namedItem = (name) => plugins.find(p => p.name === name) ? pluginArray[plugins.findIndex(p => p.name === name)] : null;
7642
+ pluginArray.refresh = () => {};
7643
+ return pluginArray;
7644
+ },
7645
+ configurable: true,
7646
+ });
7647
+
7648
+ // \u2500\u2500 3. Override navigator.languages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7649
+ Object.defineProperty(navigator, 'languages', {
7650
+ get: () => ['en-US', 'en'],
7651
+ configurable: true,
7652
+ });
7653
+
7654
+ // \u2500\u2500 4. Override chrome.runtime to appear like real Chrome \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7655
+ if (!window.chrome) {
7656
+ window.chrome = {};
7657
+ }
7658
+ if (!window.chrome.runtime) {
7659
+ window.chrome.runtime = {
7660
+ connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {} }; },
7661
+ sendMessage: function() {},
7662
+ onMessage: { addListener: function() {}, removeListener: function() {} },
7663
+ id: undefined,
7664
+ };
7665
+ }
7666
+ `;
7667
+ async function applyStealthPatches(page) {
7668
+ await page.context().addInitScript(STEALTH_SCRIPT);
7669
+ await page.context().setExtraHTTPHeaders({
7670
+ "User-Agent": REALISTIC_USER_AGENT
7671
+ });
7672
+ }
7673
+
7674
+ // src/lib/dialogs.ts
7675
+ var pendingDialogs = new Map;
7676
+ var AUTO_DISMISS_MS = 5000;
7677
+ function setupDialogHandler(page, sessionId) {
7678
+ const onDialog = (dialog) => {
7679
+ const info = {
7680
+ type: dialog.type(),
7681
+ message: dialog.message(),
7682
+ default_value: dialog.defaultValue(),
7683
+ timestamp: new Date().toISOString()
7684
+ };
7685
+ const autoTimer = setTimeout(() => {
7686
+ try {
7687
+ dialog.dismiss().catch(() => {});
7688
+ } catch {}
7689
+ const list = pendingDialogs.get(sessionId);
7690
+ if (list) {
7691
+ const idx = list.findIndex((p) => p.dialog === dialog);
7692
+ if (idx >= 0)
7693
+ list.splice(idx, 1);
7694
+ if (list.length === 0)
7695
+ pendingDialogs.delete(sessionId);
7696
+ }
7697
+ }, AUTO_DISMISS_MS);
7698
+ const pending = { dialog, info, autoTimer };
7699
+ if (!pendingDialogs.has(sessionId)) {
7700
+ pendingDialogs.set(sessionId, []);
7701
+ }
7702
+ pendingDialogs.get(sessionId).push(pending);
7703
+ };
7704
+ page.on("dialog", onDialog);
7705
+ return () => {
7706
+ page.off("dialog", onDialog);
7707
+ const list = pendingDialogs.get(sessionId);
7708
+ if (list) {
7709
+ for (const p of list)
7710
+ clearTimeout(p.autoTimer);
7711
+ pendingDialogs.delete(sessionId);
7712
+ }
7713
+ };
7714
+ }
7715
+
7454
7716
  // src/lib/session.ts
7455
7717
  var handles = new Map;
7456
7718
  async function createSession2(opts = {}) {
@@ -7473,17 +7735,44 @@ async function createSession2(opts = {}) {
7473
7735
  userAgent: opts.userAgent
7474
7736
  });
7475
7737
  }
7738
+ let sessionName = opts.name ?? (opts.startUrl ? (() => {
7739
+ try {
7740
+ return new URL(opts.startUrl).hostname;
7741
+ } catch {
7742
+ return;
7743
+ }
7744
+ })() : undefined);
7476
7745
  const session = createSession({
7477
7746
  engine: resolvedEngine,
7478
7747
  projectId: opts.projectId,
7479
7748
  agentId: opts.agentId,
7480
- startUrl: opts.startUrl
7749
+ startUrl: opts.startUrl,
7750
+ name: sessionName
7481
7751
  });
7482
- handles.set(session.id, { browser, page, engine: resolvedEngine });
7752
+ if (opts.stealth) {
7753
+ try {
7754
+ await applyStealthPatches(page);
7755
+ } catch {}
7756
+ }
7757
+ const cleanups = [];
7758
+ if (opts.captureNetwork !== false) {
7759
+ try {
7760
+ cleanups.push(enableNetworkLogging(page, session.id));
7761
+ } catch {}
7762
+ }
7763
+ if (opts.captureConsole !== false) {
7764
+ try {
7765
+ cleanups.push(enableConsoleCapture(page, session.id));
7766
+ } catch {}
7767
+ }
7768
+ try {
7769
+ cleanups.push(setupDialogHandler(page, session.id));
7770
+ } catch {}
7771
+ handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
7483
7772
  if (opts.startUrl) {
7484
7773
  try {
7485
7774
  await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
7486
- } catch (err) {}
7775
+ } catch {}
7487
7776
  }
7488
7777
  return { session, page };
7489
7778
  }
@@ -7491,6 +7780,12 @@ function getSessionPage(sessionId) {
7491
7780
  const handle = handles.get(sessionId);
7492
7781
  if (!handle)
7493
7782
  throw new SessionNotFoundError(sessionId);
7783
+ try {
7784
+ handle.page.url();
7785
+ } catch {
7786
+ handles.delete(sessionId);
7787
+ throw new SessionNotFoundError(sessionId);
7788
+ }
7494
7789
  return handle.page;
7495
7790
  }
7496
7791
  function getSessionBrowser(sessionId) {
@@ -7508,9 +7803,20 @@ function getSessionEngine(sessionId) {
7508
7803
  function hasActiveHandle(sessionId) {
7509
7804
  return handles.has(sessionId);
7510
7805
  }
7806
+ function setSessionPage(sessionId, page) {
7807
+ const handle = handles.get(sessionId);
7808
+ if (!handle)
7809
+ throw new SessionNotFoundError(sessionId);
7810
+ handle.page = page;
7811
+ }
7511
7812
  async function closeSession2(sessionId) {
7512
7813
  const handle = handles.get(sessionId);
7513
7814
  if (handle) {
7815
+ for (const cleanup of handle.cleanups) {
7816
+ try {
7817
+ cleanup();
7818
+ } catch {}
7819
+ }
7514
7820
  try {
7515
7821
  await handle.page.context().close();
7516
7822
  } catch {}
@@ -7538,6 +7844,23 @@ function getSessionByName2(name) {
7538
7844
  function renameSession2(id, name) {
7539
7845
  return renameSession(id, name);
7540
7846
  }
7847
+ function getTokenBudget(sessionId) {
7848
+ const handle = handles.get(sessionId);
7849
+ return handle ? handle.tokenBudget : null;
7850
+ }
7851
+ // src/lib/snapshot.ts
7852
+ var lastSnapshots = new Map;
7853
+ var sessionRefMaps = new Map;
7854
+ function getRefLocator(page, sessionId, ref) {
7855
+ const refMap = sessionRefMaps.get(sessionId);
7856
+ if (!refMap)
7857
+ throw new Error(`No snapshot taken for session ${sessionId}. Call browser_snapshot first.`);
7858
+ const entry = refMap.get(ref);
7859
+ if (!entry)
7860
+ throw new Error(`Ref ${ref} not found. Available refs: ${[...refMap.keys()].slice(0, 20).join(", ")}`);
7861
+ return page.getByRole(entry.role, { name: entry.name }).first();
7862
+ }
7863
+
7541
7864
  // src/lib/actions.ts
7542
7865
  async function click(page, selector, opts) {
7543
7866
  try {
@@ -7780,6 +8103,73 @@ function stopWatch(watchId) {
7780
8103
  activeWatches.delete(watchId);
7781
8104
  }
7782
8105
  }
8106
+ async function clickRef(page, sessionId, ref, opts) {
8107
+ try {
8108
+ const locator = getRefLocator(page, sessionId, ref);
8109
+ await locator.click({ timeout: opts?.timeout ?? 1e4 });
8110
+ } catch (err) {
8111
+ if (err instanceof Error && err.message.includes("Ref "))
8112
+ throw new ElementNotFoundError(ref);
8113
+ if (err instanceof Error && err.message.includes("No snapshot"))
8114
+ throw new BrowserError(err.message, "NO_SNAPSHOT");
8115
+ throw new BrowserError(`clickRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CLICK_REF_FAILED");
8116
+ }
8117
+ }
8118
+ async function typeRef(page, sessionId, ref, text, opts) {
8119
+ try {
8120
+ const locator = getRefLocator(page, sessionId, ref);
8121
+ if (opts?.clear)
8122
+ await locator.fill("", { timeout: opts.timeout ?? 1e4 });
8123
+ await locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
8124
+ } catch (err) {
8125
+ if (err instanceof Error && err.message.includes("Ref "))
8126
+ throw new ElementNotFoundError(ref);
8127
+ throw new BrowserError(`typeRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "TYPE_REF_FAILED");
8128
+ }
8129
+ }
8130
+ async function fillRef(page, sessionId, ref, value, timeout = 1e4) {
8131
+ try {
8132
+ const locator = getRefLocator(page, sessionId, ref);
8133
+ await locator.fill(value, { timeout });
8134
+ } catch (err) {
8135
+ if (err instanceof Error && err.message.includes("Ref "))
8136
+ throw new ElementNotFoundError(ref);
8137
+ throw new BrowserError(`fillRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "FILL_REF_FAILED");
8138
+ }
8139
+ }
8140
+ async function selectRef(page, sessionId, ref, value, timeout = 1e4) {
8141
+ try {
8142
+ const locator = getRefLocator(page, sessionId, ref);
8143
+ return await locator.selectOption(value, { timeout });
8144
+ } catch (err) {
8145
+ if (err instanceof Error && err.message.includes("Ref "))
8146
+ throw new ElementNotFoundError(ref);
8147
+ throw new BrowserError(`selectRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "SELECT_REF_FAILED");
8148
+ }
8149
+ }
8150
+ async function checkRef(page, sessionId, ref, checked, timeout = 1e4) {
8151
+ try {
8152
+ const locator = getRefLocator(page, sessionId, ref);
8153
+ if (checked)
8154
+ await locator.check({ timeout });
8155
+ else
8156
+ await locator.uncheck({ timeout });
8157
+ } catch (err) {
8158
+ if (err instanceof Error && err.message.includes("Ref "))
8159
+ throw new ElementNotFoundError(ref);
8160
+ throw new BrowserError(`checkRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CHECK_REF_FAILED");
8161
+ }
8162
+ }
8163
+ async function hoverRef(page, sessionId, ref, timeout = 1e4) {
8164
+ try {
8165
+ const locator = getRefLocator(page, sessionId, ref);
8166
+ await locator.hover({ timeout });
8167
+ } catch (err) {
8168
+ if (err instanceof Error && err.message.includes("Ref "))
8169
+ throw new ElementNotFoundError(ref);
8170
+ throw new BrowserError(`hoverRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "HOVER_REF_FAILED");
8171
+ }
8172
+ }
7783
8173
  // src/lib/extractor.ts
7784
8174
  async function getText(page, selector) {
7785
8175
  if (selector) {
@@ -7934,112 +8324,6 @@ async function getPageInfo(page) {
7934
8324
  viewport
7935
8325
  };
7936
8326
  }
7937
- // src/lib/network.ts
7938
- function enableNetworkLogging(page, sessionId) {
7939
- const requestStart = new Map;
7940
- const onRequest = (req) => {
7941
- requestStart.set(req.url(), Date.now());
7942
- };
7943
- const onResponse = (res) => {
7944
- const start = requestStart.get(res.url()) ?? Date.now();
7945
- const duration = Date.now() - start;
7946
- const req = res.request();
7947
- try {
7948
- logRequest({
7949
- session_id: sessionId,
7950
- method: req.method(),
7951
- url: res.url(),
7952
- status_code: res.status(),
7953
- request_headers: JSON.stringify(req.headers()),
7954
- response_headers: JSON.stringify(res.headers()),
7955
- body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
7956
- duration_ms: duration,
7957
- resource_type: req.resourceType()
7958
- });
7959
- } catch {}
7960
- };
7961
- page.on("request", onRequest);
7962
- page.on("response", onResponse);
7963
- return () => {
7964
- page.off("request", onRequest);
7965
- page.off("response", onResponse);
7966
- };
7967
- }
7968
- async function addInterceptRule(page, rule) {
7969
- await page.route(rule.pattern, async (route) => {
7970
- if (rule.action === "block") {
7971
- await route.abort();
7972
- } else if (rule.action === "modify" && rule.response) {
7973
- await route.fulfill({
7974
- status: rule.response.status,
7975
- body: rule.response.body,
7976
- headers: rule.response.headers
7977
- });
7978
- } else {
7979
- await route.continue();
7980
- }
7981
- });
7982
- }
7983
- async function clearInterceptRules(page) {
7984
- await page.unrouteAll();
7985
- }
7986
- function startHAR(page) {
7987
- const entries = [];
7988
- const requestStart = new Map;
7989
- const onRequest = (req) => {
7990
- requestStart.set(req.url() + req.method(), {
7991
- time: Date.now(),
7992
- method: req.method(),
7993
- headers: req.headers(),
7994
- postData: req.postData() ?? undefined
7995
- });
7996
- };
7997
- const onResponse = async (res) => {
7998
- const key = res.url() + res.request().method();
7999
- const start = requestStart.get(key);
8000
- if (!start)
8001
- return;
8002
- const duration = Date.now() - start.time;
8003
- const entry = {
8004
- startedDateTime: new Date(start.time).toISOString(),
8005
- time: duration,
8006
- request: {
8007
- method: start.method,
8008
- url: res.url(),
8009
- headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
8010
- postData: start.postData ? { text: start.postData } : undefined
8011
- },
8012
- response: {
8013
- status: res.status(),
8014
- statusText: res.statusText(),
8015
- headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
8016
- content: {
8017
- size: parseInt(res.headers()["content-length"] ?? "0") || 0,
8018
- mimeType: res.headers()["content-type"] ?? "application/octet-stream"
8019
- }
8020
- },
8021
- timings: { send: 0, wait: duration, receive: 0 }
8022
- };
8023
- entries.push(entry);
8024
- requestStart.delete(key);
8025
- };
8026
- page.on("request", onRequest);
8027
- page.on("response", onResponse);
8028
- return {
8029
- entries,
8030
- stop: () => {
8031
- page.off("request", onRequest);
8032
- page.off("response", onResponse);
8033
- return {
8034
- log: {
8035
- version: "1.2",
8036
- creator: { name: "@hasna/browser", version: "0.0.1" },
8037
- entries
8038
- }
8039
- };
8040
- }
8041
- };
8042
- }
8043
8327
  // src/lib/performance.ts
8044
8328
  async function getPerformanceMetrics(page) {
8045
8329
  const navTiming = await page.evaluate(() => {
@@ -8127,47 +8411,6 @@ async function startCoverage(page) {
8127
8411
  }
8128
8412
  };
8129
8413
  }
8130
- // src/lib/console.ts
8131
- function enableConsoleCapture(page, sessionId) {
8132
- const onConsole = (msg) => {
8133
- const levelMap = {
8134
- log: "log",
8135
- warn: "warn",
8136
- error: "error",
8137
- debug: "debug",
8138
- info: "info",
8139
- warning: "warn"
8140
- };
8141
- const level = levelMap[msg.type()] ?? "log";
8142
- const location = msg.location();
8143
- try {
8144
- logConsoleMessage({
8145
- session_id: sessionId,
8146
- level,
8147
- message: msg.text(),
8148
- source: location.url || undefined,
8149
- line_number: location.lineNumber || undefined
8150
- });
8151
- } catch {}
8152
- };
8153
- page.on("console", onConsole);
8154
- return () => page.off("console", onConsole);
8155
- }
8156
- async function capturePageErrors(page, sessionId) {
8157
- const onError = (err) => {
8158
- try {
8159
- logConsoleMessage({
8160
- session_id: sessionId,
8161
- level: "error",
8162
- message: `[Page Error] ${err.message}`,
8163
- source: err.stack?.split(`
8164
- `)[1]?.trim()
8165
- });
8166
- } catch {}
8167
- };
8168
- page.on("pageerror", onError);
8169
- return () => page.off("pageerror", onError);
8170
- }
8171
8414
  // src/lib/screenshot.ts
8172
8415
  var import_sharp = __toESM(require_lib(), 1);
8173
8416
  import { join as join2 } from "path";
@@ -8267,11 +8510,20 @@ async function takeScreenshot(page, opts) {
8267
8510
  }
8268
8511
  const originalSizeBytes = rawBuffer.length;
8269
8512
  let finalBuffer;
8270
- if (compress && format !== "png") {
8271
- finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
8272
- } else if (compress && format === "png") {
8273
- finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
8274
- } else {
8513
+ let compressed = true;
8514
+ let fallback = false;
8515
+ try {
8516
+ if (compress && format !== "png") {
8517
+ finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
8518
+ } else if (compress && format === "png") {
8519
+ finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
8520
+ } else {
8521
+ finalBuffer = rawBuffer;
8522
+ compressed = false;
8523
+ }
8524
+ } catch (sharpErr) {
8525
+ fallback = true;
8526
+ compressed = false;
8275
8527
  finalBuffer = rawBuffer;
8276
8528
  }
8277
8529
  const compressedSizeBytes = finalBuffer.length;
@@ -8299,7 +8551,8 @@ async function takeScreenshot(page, opts) {
8299
8551
  compressed_size_bytes: compressedSizeBytes,
8300
8552
  compression_ratio: compressionRatio,
8301
8553
  thumbnail_path: thumbnailPath,
8302
- thumbnail_base64: thumbnailBase64
8554
+ thumbnail_base64: thumbnailBase64,
8555
+ ...fallback ? { fallback: true, compressed: false } : {}
8303
8556
  };
8304
8557
  if (opts?.track !== false) {
8305
8558
  try {
@@ -8673,6 +8926,7 @@ export {
8673
8926
  updateRecording,
8674
8927
  updateProject,
8675
8928
  updateAgent,
8929
+ typeRef,
8676
8930
  type,
8677
8931
  takeScreenshot,
8678
8932
  stopWatch,
@@ -8682,8 +8936,10 @@ export {
8682
8936
  startHAR,
8683
8937
  startCoverage,
8684
8938
  setSessionStorage,
8939
+ setSessionPage,
8685
8940
  setLocalStorage,
8686
8941
  setCookie,
8942
+ selectRef,
8687
8943
  selectOption,
8688
8944
  selectEngine,
8689
8945
  scrollTo,
@@ -8712,6 +8968,7 @@ export {
8712
8968
  isEngineAvailable,
8713
8969
  isAgentStale,
8714
8970
  inferUseCase,
8971
+ hoverRef,
8715
8972
  hover,
8716
8973
  heartbeat2 as heartbeat,
8717
8974
  hasActiveHandle,
@@ -8719,6 +8976,7 @@ export {
8719
8976
  goBack,
8720
8977
  getWatchChanges,
8721
8978
  getUrl,
8979
+ getTokenBudget,
8722
8980
  getTitle,
8723
8981
  getTimingEntries,
8724
8982
  getText,
@@ -8758,6 +9016,7 @@ export {
8758
9016
  getActiveAgents,
8759
9017
  generatePDF,
8760
9018
  findElements,
9019
+ fillRef,
8761
9020
  fillForm,
8762
9021
  fill,
8763
9022
  extractTable,
@@ -8795,6 +9054,7 @@ export {
8795
9054
  closeBrowser,
8796
9055
  closeAllSessions,
8797
9056
  clickText,
9057
+ clickRef,
8798
9058
  click,
8799
9059
  clearSessionStorage,
8800
9060
  clearNetworkLog,
@@ -8804,6 +9064,7 @@ export {
8804
9064
  clearConsoleLog,
8805
9065
  cleanStaleAgents,
8806
9066
  cleanOldHeartbeats,
9067
+ checkRef,
8807
9068
  checkBox,
8808
9069
  capturePageErrors,
8809
9070
  attachPageListeners,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=actions-ref.test.d.ts.map