@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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=v4.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v4.test.d.ts","sourceRoot":"","sources":["../../src/mcp/v4.test.ts"],"names":[],"mappings":""}
@@ -6881,7 +6881,14 @@ import { randomUUID } from "crypto";
6881
6881
  function createSession(data) {
6882
6882
  const db = getDatabase();
6883
6883
  const id = randomUUID();
6884
- 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);
6884
+ let name = data.name ?? null;
6885
+ if (name) {
6886
+ const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
6887
+ if (existing) {
6888
+ name = `${name}-${id.slice(0, 6)}`;
6889
+ }
6890
+ }
6891
+ 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);
6885
6892
  return getSession(id);
6886
6893
  }
6887
6894
  function getSession(id) {
@@ -7072,6 +7079,273 @@ function selectEngine(useCase, explicit) {
7072
7079
  return preferred;
7073
7080
  }
7074
7081
 
7082
+ // src/db/network-log.ts
7083
+ init_schema();
7084
+ import { randomUUID as randomUUID2 } from "crypto";
7085
+ function logRequest(data) {
7086
+ const db = getDatabase();
7087
+ const id = randomUUID2();
7088
+ db.prepare(`INSERT INTO network_log (id, session_id, method, url, status_code, request_headers,
7089
+ response_headers, request_body, body_size, duration_ms, resource_type)
7090
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, data.session_id, data.method, data.url, data.status_code ?? null, data.request_headers ?? null, data.response_headers ?? null, data.request_body ?? null, data.body_size ?? null, data.duration_ms ?? null, data.resource_type ?? null);
7091
+ return getNetworkRequest(id);
7092
+ }
7093
+ function getNetworkRequest(id) {
7094
+ const db = getDatabase();
7095
+ return db.query("SELECT * FROM network_log WHERE id = ?").get(id) ?? null;
7096
+ }
7097
+ function getNetworkLog(sessionId) {
7098
+ const db = getDatabase();
7099
+ return db.query("SELECT * FROM network_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
7100
+ }
7101
+ function clearNetworkLog(sessionId) {
7102
+ const db = getDatabase();
7103
+ db.prepare("DELETE FROM network_log WHERE session_id = ?").run(sessionId);
7104
+ }
7105
+
7106
+ // src/lib/network.ts
7107
+ function enableNetworkLogging(page, sessionId) {
7108
+ const requestStart = new Map;
7109
+ const onRequest = (req) => {
7110
+ requestStart.set(req.url(), Date.now());
7111
+ };
7112
+ const onResponse = (res) => {
7113
+ const start = requestStart.get(res.url()) ?? Date.now();
7114
+ const duration = Date.now() - start;
7115
+ const req = res.request();
7116
+ try {
7117
+ logRequest({
7118
+ session_id: sessionId,
7119
+ method: req.method(),
7120
+ url: res.url(),
7121
+ status_code: res.status(),
7122
+ request_headers: JSON.stringify(req.headers()),
7123
+ response_headers: JSON.stringify(res.headers()),
7124
+ body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
7125
+ duration_ms: duration,
7126
+ resource_type: req.resourceType()
7127
+ });
7128
+ } catch {}
7129
+ };
7130
+ page.on("request", onRequest);
7131
+ page.on("response", onResponse);
7132
+ return () => {
7133
+ page.off("request", onRequest);
7134
+ page.off("response", onResponse);
7135
+ };
7136
+ }
7137
+ function startHAR(page) {
7138
+ const entries = [];
7139
+ const requestStart = new Map;
7140
+ const onRequest = (req) => {
7141
+ requestStart.set(req.url() + req.method(), {
7142
+ time: Date.now(),
7143
+ method: req.method(),
7144
+ headers: req.headers(),
7145
+ postData: req.postData() ?? undefined
7146
+ });
7147
+ };
7148
+ const onResponse = async (res) => {
7149
+ const key = res.url() + res.request().method();
7150
+ const start = requestStart.get(key);
7151
+ if (!start)
7152
+ return;
7153
+ const duration = Date.now() - start.time;
7154
+ const entry = {
7155
+ startedDateTime: new Date(start.time).toISOString(),
7156
+ time: duration,
7157
+ request: {
7158
+ method: start.method,
7159
+ url: res.url(),
7160
+ headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
7161
+ postData: start.postData ? { text: start.postData } : undefined
7162
+ },
7163
+ response: {
7164
+ status: res.status(),
7165
+ statusText: res.statusText(),
7166
+ headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
7167
+ content: {
7168
+ size: parseInt(res.headers()["content-length"] ?? "0") || 0,
7169
+ mimeType: res.headers()["content-type"] ?? "application/octet-stream"
7170
+ }
7171
+ },
7172
+ timings: { send: 0, wait: duration, receive: 0 }
7173
+ };
7174
+ entries.push(entry);
7175
+ requestStart.delete(key);
7176
+ };
7177
+ page.on("request", onRequest);
7178
+ page.on("response", onResponse);
7179
+ return {
7180
+ entries,
7181
+ stop: () => {
7182
+ page.off("request", onRequest);
7183
+ page.off("response", onResponse);
7184
+ return {
7185
+ log: {
7186
+ version: "1.2",
7187
+ creator: { name: "@hasna/browser", version: "0.0.1" },
7188
+ entries
7189
+ }
7190
+ };
7191
+ }
7192
+ };
7193
+ }
7194
+
7195
+ // src/db/console-log.ts
7196
+ init_schema();
7197
+ import { randomUUID as randomUUID3 } from "crypto";
7198
+ function logConsoleMessage(data) {
7199
+ const db = getDatabase();
7200
+ const id = randomUUID3();
7201
+ db.prepare("INSERT INTO console_log (id, session_id, level, message, source, line_number) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.level, data.message, data.source ?? null, data.line_number ?? null);
7202
+ return getConsoleMessage(id);
7203
+ }
7204
+ function getConsoleMessage(id) {
7205
+ const db = getDatabase();
7206
+ return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
7207
+ }
7208
+ function getConsoleLog(sessionId, level) {
7209
+ const db = getDatabase();
7210
+ if (level) {
7211
+ return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
7212
+ }
7213
+ return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
7214
+ }
7215
+
7216
+ // src/lib/console.ts
7217
+ function enableConsoleCapture(page, sessionId) {
7218
+ const onConsole = (msg) => {
7219
+ const levelMap = {
7220
+ log: "log",
7221
+ warn: "warn",
7222
+ error: "error",
7223
+ debug: "debug",
7224
+ info: "info",
7225
+ warning: "warn"
7226
+ };
7227
+ const level = levelMap[msg.type()] ?? "log";
7228
+ const location = msg.location();
7229
+ try {
7230
+ logConsoleMessage({
7231
+ session_id: sessionId,
7232
+ level,
7233
+ message: msg.text(),
7234
+ source: location.url || undefined,
7235
+ line_number: location.lineNumber || undefined
7236
+ });
7237
+ } catch {}
7238
+ };
7239
+ page.on("console", onConsole);
7240
+ return () => page.off("console", onConsole);
7241
+ }
7242
+
7243
+ // src/lib/stealth.ts
7244
+ 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";
7245
+ var STEALTH_SCRIPT = `
7246
+ // \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
7247
+ Object.defineProperty(navigator, 'webdriver', {
7248
+ get: () => false,
7249
+ configurable: true,
7250
+ });
7251
+
7252
+ // \u2500\u2500 2. Override navigator.plugins to show typical Chrome plugins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7253
+ Object.defineProperty(navigator, 'plugins', {
7254
+ get: () => {
7255
+ const plugins = [
7256
+ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 1 },
7257
+ { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },
7258
+ { name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },
7259
+ ];
7260
+ // Mimic PluginArray interface
7261
+ const pluginArray = Object.create(PluginArray.prototype);
7262
+ plugins.forEach((p, i) => {
7263
+ const plugin = Object.create(Plugin.prototype);
7264
+ Object.defineProperties(plugin, {
7265
+ name: { value: p.name, enumerable: true },
7266
+ filename: { value: p.filename, enumerable: true },
7267
+ description: { value: p.description, enumerable: true },
7268
+ length: { value: p.length, enumerable: true },
7269
+ });
7270
+ pluginArray[i] = plugin;
7271
+ });
7272
+ Object.defineProperty(pluginArray, 'length', { value: plugins.length });
7273
+ pluginArray.item = (i) => pluginArray[i] || null;
7274
+ pluginArray.namedItem = (name) => plugins.find(p => p.name === name) ? pluginArray[plugins.findIndex(p => p.name === name)] : null;
7275
+ pluginArray.refresh = () => {};
7276
+ return pluginArray;
7277
+ },
7278
+ configurable: true,
7279
+ });
7280
+
7281
+ // \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
7282
+ Object.defineProperty(navigator, 'languages', {
7283
+ get: () => ['en-US', 'en'],
7284
+ configurable: true,
7285
+ });
7286
+
7287
+ // \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
7288
+ if (!window.chrome) {
7289
+ window.chrome = {};
7290
+ }
7291
+ if (!window.chrome.runtime) {
7292
+ window.chrome.runtime = {
7293
+ connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {} }; },
7294
+ sendMessage: function() {},
7295
+ onMessage: { addListener: function() {}, removeListener: function() {} },
7296
+ id: undefined,
7297
+ };
7298
+ }
7299
+ `;
7300
+ async function applyStealthPatches(page) {
7301
+ await page.context().addInitScript(STEALTH_SCRIPT);
7302
+ await page.context().setExtraHTTPHeaders({
7303
+ "User-Agent": REALISTIC_USER_AGENT
7304
+ });
7305
+ }
7306
+
7307
+ // src/lib/dialogs.ts
7308
+ var pendingDialogs = new Map;
7309
+ var AUTO_DISMISS_MS = 5000;
7310
+ function setupDialogHandler(page, sessionId) {
7311
+ const onDialog = (dialog) => {
7312
+ const info = {
7313
+ type: dialog.type(),
7314
+ message: dialog.message(),
7315
+ default_value: dialog.defaultValue(),
7316
+ timestamp: new Date().toISOString()
7317
+ };
7318
+ const autoTimer = setTimeout(() => {
7319
+ try {
7320
+ dialog.dismiss().catch(() => {});
7321
+ } catch {}
7322
+ const list = pendingDialogs.get(sessionId);
7323
+ if (list) {
7324
+ const idx = list.findIndex((p) => p.dialog === dialog);
7325
+ if (idx >= 0)
7326
+ list.splice(idx, 1);
7327
+ if (list.length === 0)
7328
+ pendingDialogs.delete(sessionId);
7329
+ }
7330
+ }, AUTO_DISMISS_MS);
7331
+ const pending = { dialog, info, autoTimer };
7332
+ if (!pendingDialogs.has(sessionId)) {
7333
+ pendingDialogs.set(sessionId, []);
7334
+ }
7335
+ pendingDialogs.get(sessionId).push(pending);
7336
+ };
7337
+ page.on("dialog", onDialog);
7338
+ return () => {
7339
+ page.off("dialog", onDialog);
7340
+ const list = pendingDialogs.get(sessionId);
7341
+ if (list) {
7342
+ for (const p of list)
7343
+ clearTimeout(p.autoTimer);
7344
+ pendingDialogs.delete(sessionId);
7345
+ }
7346
+ };
7347
+ }
7348
+
7075
7349
  // src/lib/session.ts
7076
7350
  var handles = new Map;
7077
7351
  async function createSession2(opts = {}) {
@@ -7094,17 +7368,44 @@ async function createSession2(opts = {}) {
7094
7368
  userAgent: opts.userAgent
7095
7369
  });
7096
7370
  }
7371
+ let sessionName = opts.name ?? (opts.startUrl ? (() => {
7372
+ try {
7373
+ return new URL(opts.startUrl).hostname;
7374
+ } catch {
7375
+ return;
7376
+ }
7377
+ })() : undefined);
7097
7378
  const session = createSession({
7098
7379
  engine: resolvedEngine,
7099
7380
  projectId: opts.projectId,
7100
7381
  agentId: opts.agentId,
7101
- startUrl: opts.startUrl
7382
+ startUrl: opts.startUrl,
7383
+ name: sessionName
7102
7384
  });
7103
- handles.set(session.id, { browser, page, engine: resolvedEngine });
7385
+ if (opts.stealth) {
7386
+ try {
7387
+ await applyStealthPatches(page);
7388
+ } catch {}
7389
+ }
7390
+ const cleanups = [];
7391
+ if (opts.captureNetwork !== false) {
7392
+ try {
7393
+ cleanups.push(enableNetworkLogging(page, session.id));
7394
+ } catch {}
7395
+ }
7396
+ if (opts.captureConsole !== false) {
7397
+ try {
7398
+ cleanups.push(enableConsoleCapture(page, session.id));
7399
+ } catch {}
7400
+ }
7401
+ try {
7402
+ cleanups.push(setupDialogHandler(page, session.id));
7403
+ } catch {}
7404
+ handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
7104
7405
  if (opts.startUrl) {
7105
7406
  try {
7106
7407
  await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
7107
- } catch (err) {}
7408
+ } catch {}
7108
7409
  }
7109
7410
  return { session, page };
7110
7411
  }
@@ -7112,11 +7413,22 @@ function getSessionPage(sessionId) {
7112
7413
  const handle = handles.get(sessionId);
7113
7414
  if (!handle)
7114
7415
  throw new SessionNotFoundError(sessionId);
7416
+ try {
7417
+ handle.page.url();
7418
+ } catch {
7419
+ handles.delete(sessionId);
7420
+ throw new SessionNotFoundError(sessionId);
7421
+ }
7115
7422
  return handle.page;
7116
7423
  }
7117
7424
  async function closeSession2(sessionId) {
7118
7425
  const handle = handles.get(sessionId);
7119
7426
  if (handle) {
7427
+ for (const cleanup of handle.cleanups) {
7428
+ try {
7429
+ cleanup();
7430
+ } catch {}
7431
+ }
7120
7432
  try {
7121
7433
  await handle.page.context().close();
7122
7434
  } catch {}
@@ -7133,6 +7445,12 @@ function listSessions2(filter) {
7133
7445
 
7134
7446
  // src/lib/actions.ts
7135
7447
  init_types();
7448
+
7449
+ // src/lib/snapshot.ts
7450
+ var lastSnapshots = new Map;
7451
+ var sessionRefMaps = new Map;
7452
+
7453
+ // src/lib/actions.ts
7136
7454
  async function click(page, selector, opts) {
7137
7455
  try {
7138
7456
  await page.click(selector, {
@@ -7264,7 +7582,7 @@ import { homedir as homedir2 } from "os";
7264
7582
 
7265
7583
  // src/db/gallery.ts
7266
7584
  init_schema();
7267
- import { randomUUID as randomUUID2 } from "crypto";
7585
+ import { randomUUID as randomUUID4 } from "crypto";
7268
7586
  function deserialize(row) {
7269
7587
  return {
7270
7588
  id: row.id,
@@ -7288,7 +7606,7 @@ function deserialize(row) {
7288
7606
  }
7289
7607
  function createEntry(data) {
7290
7608
  const db = getDatabase();
7291
- const id = randomUUID2();
7609
+ const id = randomUUID4();
7292
7610
  db.prepare(`
7293
7611
  INSERT INTO gallery_entries
7294
7612
  (id, session_id, project_id, url, title, path, thumbnail_path, format,
@@ -7440,11 +7758,20 @@ async function takeScreenshot(page, opts) {
7440
7758
  }
7441
7759
  const originalSizeBytes = rawBuffer.length;
7442
7760
  let finalBuffer;
7443
- if (compress && format !== "png") {
7444
- finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
7445
- } else if (compress && format === "png") {
7446
- finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
7447
- } else {
7761
+ let compressed = true;
7762
+ let fallback = false;
7763
+ try {
7764
+ if (compress && format !== "png") {
7765
+ finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
7766
+ } else if (compress && format === "png") {
7767
+ finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
7768
+ } else {
7769
+ finalBuffer = rawBuffer;
7770
+ compressed = false;
7771
+ }
7772
+ } catch (sharpErr) {
7773
+ fallback = true;
7774
+ compressed = false;
7448
7775
  finalBuffer = rawBuffer;
7449
7776
  }
7450
7777
  const compressedSizeBytes = finalBuffer.length;
@@ -7472,7 +7799,8 @@ async function takeScreenshot(page, opts) {
7472
7799
  compressed_size_bytes: compressedSizeBytes,
7473
7800
  compression_ratio: compressionRatio,
7474
7801
  thumbnail_path: thumbnailPath,
7475
- thumbnail_base64: thumbnailBase64
7802
+ thumbnail_base64: thumbnailBase64,
7803
+ ...fallback ? { fallback: true, compressed: false } : {}
7476
7804
  };
7477
7805
  if (opts?.track !== false) {
7478
7806
  try {
@@ -7508,119 +7836,6 @@ async function takeScreenshot(page, opts) {
7508
7836
  }
7509
7837
  }
7510
7838
 
7511
- // src/db/network-log.ts
7512
- init_schema();
7513
- import { randomUUID as randomUUID3 } from "crypto";
7514
- function logRequest(data) {
7515
- const db = getDatabase();
7516
- const id = randomUUID3();
7517
- db.prepare(`INSERT INTO network_log (id, session_id, method, url, status_code, request_headers,
7518
- response_headers, request_body, body_size, duration_ms, resource_type)
7519
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, data.session_id, data.method, data.url, data.status_code ?? null, data.request_headers ?? null, data.response_headers ?? null, data.request_body ?? null, data.body_size ?? null, data.duration_ms ?? null, data.resource_type ?? null);
7520
- return getNetworkRequest(id);
7521
- }
7522
- function getNetworkRequest(id) {
7523
- const db = getDatabase();
7524
- return db.query("SELECT * FROM network_log WHERE id = ?").get(id) ?? null;
7525
- }
7526
- function getNetworkLog(sessionId) {
7527
- const db = getDatabase();
7528
- return db.query("SELECT * FROM network_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
7529
- }
7530
- function clearNetworkLog(sessionId) {
7531
- const db = getDatabase();
7532
- db.prepare("DELETE FROM network_log WHERE session_id = ?").run(sessionId);
7533
- }
7534
-
7535
- // src/lib/network.ts
7536
- function enableNetworkLogging(page, sessionId) {
7537
- const requestStart = new Map;
7538
- const onRequest = (req) => {
7539
- requestStart.set(req.url(), Date.now());
7540
- };
7541
- const onResponse = (res) => {
7542
- const start = requestStart.get(res.url()) ?? Date.now();
7543
- const duration = Date.now() - start;
7544
- const req = res.request();
7545
- try {
7546
- logRequest({
7547
- session_id: sessionId,
7548
- method: req.method(),
7549
- url: res.url(),
7550
- status_code: res.status(),
7551
- request_headers: JSON.stringify(req.headers()),
7552
- response_headers: JSON.stringify(res.headers()),
7553
- body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
7554
- duration_ms: duration,
7555
- resource_type: req.resourceType()
7556
- });
7557
- } catch {}
7558
- };
7559
- page.on("request", onRequest);
7560
- page.on("response", onResponse);
7561
- return () => {
7562
- page.off("request", onRequest);
7563
- page.off("response", onResponse);
7564
- };
7565
- }
7566
- function startHAR(page) {
7567
- const entries = [];
7568
- const requestStart = new Map;
7569
- const onRequest = (req) => {
7570
- requestStart.set(req.url() + req.method(), {
7571
- time: Date.now(),
7572
- method: req.method(),
7573
- headers: req.headers(),
7574
- postData: req.postData() ?? undefined
7575
- });
7576
- };
7577
- const onResponse = async (res) => {
7578
- const key = res.url() + res.request().method();
7579
- const start = requestStart.get(key);
7580
- if (!start)
7581
- return;
7582
- const duration = Date.now() - start.time;
7583
- const entry = {
7584
- startedDateTime: new Date(start.time).toISOString(),
7585
- time: duration,
7586
- request: {
7587
- method: start.method,
7588
- url: res.url(),
7589
- headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
7590
- postData: start.postData ? { text: start.postData } : undefined
7591
- },
7592
- response: {
7593
- status: res.status(),
7594
- statusText: res.statusText(),
7595
- headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
7596
- content: {
7597
- size: parseInt(res.headers()["content-length"] ?? "0") || 0,
7598
- mimeType: res.headers()["content-type"] ?? "application/octet-stream"
7599
- }
7600
- },
7601
- timings: { send: 0, wait: duration, receive: 0 }
7602
- };
7603
- entries.push(entry);
7604
- requestStart.delete(key);
7605
- };
7606
- page.on("request", onRequest);
7607
- page.on("response", onResponse);
7608
- return {
7609
- entries,
7610
- stop: () => {
7611
- page.off("request", onRequest);
7612
- page.off("response", onResponse);
7613
- return {
7614
- log: {
7615
- version: "1.2",
7616
- creator: { name: "@hasna/browser", version: "0.0.1" },
7617
- entries
7618
- }
7619
- };
7620
- }
7621
- };
7622
- }
7623
-
7624
7839
  // src/engines/cdp.ts
7625
7840
  init_types();
7626
7841
 
@@ -7768,54 +7983,6 @@ async function getPerformanceMetrics(page) {
7768
7983
  };
7769
7984
  }
7770
7985
 
7771
- // src/db/console-log.ts
7772
- init_schema();
7773
- import { randomUUID as randomUUID4 } from "crypto";
7774
- function logConsoleMessage(data) {
7775
- const db = getDatabase();
7776
- const id = randomUUID4();
7777
- db.prepare("INSERT INTO console_log (id, session_id, level, message, source, line_number) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.level, data.message, data.source ?? null, data.line_number ?? null);
7778
- return getConsoleMessage(id);
7779
- }
7780
- function getConsoleMessage(id) {
7781
- const db = getDatabase();
7782
- return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
7783
- }
7784
- function getConsoleLog(sessionId, level) {
7785
- const db = getDatabase();
7786
- if (level) {
7787
- return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
7788
- }
7789
- return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
7790
- }
7791
-
7792
- // src/lib/console.ts
7793
- function enableConsoleCapture(page, sessionId) {
7794
- const onConsole = (msg) => {
7795
- const levelMap = {
7796
- log: "log",
7797
- warn: "warn",
7798
- error: "error",
7799
- debug: "debug",
7800
- info: "info",
7801
- warning: "warn"
7802
- };
7803
- const level = levelMap[msg.type()] ?? "log";
7804
- const location = msg.location();
7805
- try {
7806
- logConsoleMessage({
7807
- session_id: sessionId,
7808
- level,
7809
- message: msg.text(),
7810
- source: location.url || undefined,
7811
- line_number: location.lineNumber || undefined
7812
- });
7813
- } catch {}
7814
- };
7815
- page.on("console", onConsole);
7816
- return () => page.off("console", onConsole);
7817
- }
7818
-
7819
7986
  // src/lib/crawler.ts
7820
7987
  init_types();
7821
7988
 
@@ -56,12 +56,16 @@ export interface SessionOptions {
56
56
  projectId?: string;
57
57
  agentId?: string;
58
58
  startUrl?: string;
59
+ name?: string;
59
60
  headless?: boolean;
60
61
  viewport?: {
61
62
  width: number;
62
63
  height: number;
63
64
  };
64
65
  userAgent?: string;
66
+ captureNetwork?: boolean;
67
+ captureConsole?: boolean;
68
+ stealth?: boolean;
65
69
  }
66
70
  export interface Snapshot {
67
71
  id: string;
@@ -344,6 +348,37 @@ export interface PDFResult {
344
348
  size_bytes: number;
345
349
  page_count?: number;
346
350
  }
351
+ export interface RefInfo {
352
+ role: string;
353
+ name: string;
354
+ description?: string;
355
+ visible: boolean;
356
+ enabled: boolean;
357
+ value?: string;
358
+ checked?: boolean;
359
+ }
360
+ export interface SnapshotResult {
361
+ tree: string;
362
+ refs: Record<string, RefInfo>;
363
+ interactive_count: number;
364
+ }
365
+ export interface SnapshotDiff {
366
+ added: Array<{
367
+ ref: string;
368
+ info: RefInfo;
369
+ }>;
370
+ removed: Array<{
371
+ ref: string;
372
+ info: RefInfo;
373
+ }>;
374
+ modified: Array<{
375
+ ref: string;
376
+ before: RefInfo;
377
+ after: RefInfo;
378
+ }>;
379
+ url_changed: boolean;
380
+ title_changed: boolean;
381
+ }
347
382
  export interface BrowserConfig {
348
383
  default_engine: BrowserEngine;
349
384
  headless: boolean;