cdp-toolkit 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -13,6 +13,9 @@ function leaseTtlMs() {
13
13
  const raw = Number(process.env.CDP_LEASE_TTL_MS);
14
14
  return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_LEASE_TTL_MS;
15
15
  }
16
+ function defaultLabel() {
17
+ return `pid-${process.pid}`;
18
+ }
16
19
  function safeId(id) {
17
20
  return id.replace(/[^A-Za-z0-9._-]/g, "_");
18
21
  }
@@ -704,6 +707,31 @@ function decodeUid(uid) {
704
707
  throw driverError("foreign-uid", `malformed cdp uid payload: ${uid}`);
705
708
  return n;
706
709
  }
710
+ function normalizeCdpCookie(c) {
711
+ const same = (c.sameSite ?? "").toLowerCase();
712
+ const sameSite = same === "strict" || same === "lax" || same === "none" ? same : "default";
713
+ return {
714
+ name: c.name,
715
+ value: c.value,
716
+ domain: c.domain,
717
+ path: c.path,
718
+ expires: typeof c.expires === "number" ? c.expires : -1,
719
+ size: typeof c.size === "number" ? c.size : Buffer.byteLength(`${c.name}${c.value}`, "utf8"),
720
+ httpOnly: c.httpOnly === true,
721
+ secure: c.secure === true,
722
+ sameSite,
723
+ session: c.session === true
724
+ };
725
+ }
726
+ function cdpSameSite(sameSite) {
727
+ if (sameSite === "strict")
728
+ return "Strict";
729
+ if (sameSite === "lax")
730
+ return "Lax";
731
+ if (sameSite === "none")
732
+ return "None";
733
+ return;
734
+ }
707
735
  async function centerOf(conn, objectId) {
708
736
  const fn = "function(){this.scrollIntoView({block:'center',inline:'center'});const r=this.getBoundingClientRect();if(r.width===0&&r.height===0)return null;return {x:r.left+r.width/2,y:r.top+r.height/2};}";
709
737
  const { result, exceptionDetails } = await conn.send("Runtime.callFunctionOn", { objectId, functionDeclaration: fn, returnByValue: true });
@@ -1226,6 +1254,38 @@ class CdpPageDriver {
1226
1254
  });
1227
1255
  });
1228
1256
  }
1257
+ async getCookies() {
1258
+ await this.ensureDomain("Network");
1259
+ const { cookies } = await this.conn.send("Network.getCookies");
1260
+ return (cookies ?? []).map(normalizeCdpCookie);
1261
+ }
1262
+ async setCookie(params) {
1263
+ await this.ensureDomain("Network");
1264
+ const sameSite = cdpSameSite(params.sameSite);
1265
+ const { success } = await this.conn.send("Network.setCookie", {
1266
+ name: params.name,
1267
+ value: params.value,
1268
+ ...params.url !== undefined ? { url: params.url } : {},
1269
+ ...params.domain !== undefined ? { domain: params.domain } : {},
1270
+ ...params.path !== undefined ? { path: params.path } : {},
1271
+ ...params.expires !== undefined ? { expires: params.expires } : {},
1272
+ ...params.httpOnly !== undefined ? { httpOnly: params.httpOnly } : {},
1273
+ ...params.secure !== undefined ? { secure: params.secure } : {},
1274
+ ...sameSite !== undefined ? { sameSite } : {}
1275
+ });
1276
+ if (success !== true) {
1277
+ throw driverError("page-error", `Network.setCookie declined the cookie '${params.name}'. Chrome rejects a cookie whose domain does not match the url, a secure cookie on an insecure origin, and an oversized name or value.`);
1278
+ }
1279
+ }
1280
+ async deleteCookies(filter) {
1281
+ await this.ensureDomain("Network");
1282
+ await this.conn.send("Network.deleteCookies", {
1283
+ name: filter.name,
1284
+ ...filter.url !== undefined ? { url: filter.url } : {},
1285
+ ...filter.domain !== undefined ? { domain: filter.domain } : {},
1286
+ ...filter.path !== undefined ? { path: filter.path } : {}
1287
+ });
1288
+ }
1229
1289
  async startBodyCapture() {
1230
1290
  await this.ensureDomain("Network");
1231
1291
  return async () => {
@@ -1285,6 +1345,10 @@ async function withBrowserConn(fn) {
1285
1345
  }
1286
1346
  }
1287
1347
  function noSuchTarget(e) {
1348
+ if (e instanceof LeaseConflictError)
1349
+ throw e;
1350
+ if (typeof e?.code === "string")
1351
+ throw e;
1288
1352
  throw driverError("no-such-target", e instanceof CdpError ? e.message : String(e));
1289
1353
  }
1290
1354
 
@@ -1328,6 +1392,97 @@ function createCdpDriver() {
1328
1392
  return new CdpBrowserDriver;
1329
1393
  }
1330
1394
 
1395
+ // src/origins.ts
1396
+ import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
1397
+ import { join as join2 } from "node:path";
1398
+ function safeId2(id) {
1399
+ return id.replace(/[^A-Za-z0-9._-]/g, "_");
1400
+ }
1401
+ function originFile(backend, targetId) {
1402
+ return join2(leaseDir(), `origin-${backend}-${safeId2(targetId)}.json`);
1403
+ }
1404
+ var ORIGIN_FILE_RE = /^origin-(chrome|firefox)-(.+)\.json$/;
1405
+ async function recordOrigin(backend, targetId, opts = {}) {
1406
+ const record = {
1407
+ backend,
1408
+ targetId,
1409
+ label: typeof opts.label === "string" && opts.label.length ? opts.label : defaultLabel(),
1410
+ pid: process.pid,
1411
+ createdAt: opts.now ?? Date.now()
1412
+ };
1413
+ const file = originFile(backend, targetId);
1414
+ try {
1415
+ await mkdir2(leaseDir(), { recursive: true });
1416
+ try {
1417
+ await writeFile2(file, JSON.stringify(record), { flag: "wx" });
1418
+ return;
1419
+ } catch (err) {
1420
+ if (err.code !== "EEXIST")
1421
+ throw err;
1422
+ }
1423
+ await unlink2(file).catch(() => {
1424
+ return;
1425
+ });
1426
+ await writeFile2(file, JSON.stringify(record), { flag: "wx" });
1427
+ } catch {}
1428
+ }
1429
+ async function listOrigins(opts = {}) {
1430
+ let names = [];
1431
+ try {
1432
+ names = await readdir2(leaseDir());
1433
+ } catch {
1434
+ return [];
1435
+ }
1436
+ const live = opts.liveIds === undefined ? undefined : new Set(opts.liveIds.map((id) => safeId2(id)));
1437
+ const out = [];
1438
+ for (const name of names) {
1439
+ const parts = ORIGIN_FILE_RE.exec(name);
1440
+ if (!parts)
1441
+ continue;
1442
+ const backend = parts[1];
1443
+ const nameId = parts[2];
1444
+ const scoped = live !== undefined && (opts.liveBackend === undefined || opts.liveBackend === backend);
1445
+ if (scoped && !live.has(nameId)) {
1446
+ await unlink2(join2(leaseDir(), name)).catch(() => {
1447
+ return;
1448
+ });
1449
+ continue;
1450
+ }
1451
+ const fromName = { backend, targetId: nameId, label: "", pid: 0, createdAt: 0 };
1452
+ let rec;
1453
+ try {
1454
+ rec = JSON.parse(await readFile2(join2(leaseDir(), name), "utf8"));
1455
+ } catch (err) {
1456
+ const code = err.code;
1457
+ if (code === "ENOENT")
1458
+ continue;
1459
+ out.push({ ...fromName, unreadable: code ?? "unparseable" });
1460
+ continue;
1461
+ }
1462
+ if (typeof rec?.targetId !== "string" || typeof rec?.label !== "string" || typeof rec?.createdAt !== "number") {
1463
+ out.push({ ...fromName, unreadable: "unparseable" });
1464
+ continue;
1465
+ }
1466
+ out.push({ backend: rec.backend, targetId: rec.targetId, label: rec.label, pid: rec.pid, createdAt: rec.createdAt });
1467
+ }
1468
+ return out;
1469
+ }
1470
+ async function originIndex(backend, liveIds) {
1471
+ const rows = await listOrigins({ liveIds, liveBackend: backend }).catch(() => []);
1472
+ const index = new Map;
1473
+ for (const row of rows) {
1474
+ if (row.backend !== backend)
1475
+ continue;
1476
+ index.set(row.targetId, row);
1477
+ }
1478
+ return index;
1479
+ }
1480
+ async function newTrackedPage(driver, backend, opts = {}) {
1481
+ const page = await driver.newPage(opts.url);
1482
+ await recordOrigin(backend, page.id, { label: opts.label });
1483
+ return page;
1484
+ }
1485
+
1331
1486
  // src/leases-tools.ts
1332
1487
  class LeaseToolError extends Error {
1333
1488
  }
@@ -1336,12 +1491,12 @@ function backendOf(driver) {
1336
1491
  }
1337
1492
  async function claimPage(driver, args = {}) {
1338
1493
  const backend = backendOf(driver);
1339
- const label = typeof args.label === "string" && args.label.length ? args.label : `pid-${process.pid}`;
1494
+ const label = typeof args.label === "string" && args.label.length ? args.label : defaultLabel();
1340
1495
  const pages = await driver.listPages();
1341
1496
  let targetId = args.targetId;
1342
1497
  let url = args.url ?? "about:blank";
1343
1498
  if (targetId === undefined || targetId === "") {
1344
- const page = await driver.newPage(args.url);
1499
+ const page = await newTrackedPage(driver, backend, { url: args.url, label });
1345
1500
  targetId = page.id;
1346
1501
  url = page.url;
1347
1502
  } else {
@@ -1383,8 +1538,8 @@ var LEASE_TOOLS = {
1383
1538
  };
1384
1539
 
1385
1540
  // src/shared-tools.ts
1386
- import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
1387
- import { join as join2 } from "node:path";
1541
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
1542
+ import { dirname, join as join3 } from "node:path";
1388
1543
  var ARTIFACT_DIR = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
1389
1544
  var STATE_DIR = process.env.CDP_STATE_DIR ?? "/tmp/cdp-toolkit";
1390
1545
 
@@ -1452,16 +1607,35 @@ function locatorOf(args) {
1452
1607
  throw new SharedToolError("provide exactly one of { uid } or { selector }");
1453
1608
  return hasUid ? { uid: args.uid } : { css: args.selector };
1454
1609
  }
1610
+ async function reapSet(driver, pages, all) {
1611
+ if (all === true)
1612
+ return pages.map((p) => p.id);
1613
+ try {
1614
+ return (await driver.listPages({ all: true })).map((p) => p.id);
1615
+ } catch {
1616
+ return;
1617
+ }
1618
+ }
1455
1619
  async function listPages(driver, args = {}) {
1456
1620
  const pages = await driver.listPages({ all: args.all });
1457
- return { pages, count: pages.length };
1621
+ const ledger = await originIndex(backendOf2(driver), await reapSet(driver, pages, args.all));
1622
+ const annotated = pages.map((p) => {
1623
+ const rec = ledger.get(p.id);
1624
+ if (!rec)
1625
+ return { ...p, origin: "unknown" };
1626
+ if (rec.unreadable !== undefined)
1627
+ return { ...p, origin: "unknown", originUnreadable: rec.unreadable };
1628
+ return { ...p, origin: "agent", label: rec.label, createdAt: rec.createdAt };
1629
+ });
1630
+ return { pages: annotated, count: annotated.length };
1458
1631
  }
1459
1632
  async function newPage(driver, args = {}) {
1460
- const p = await driver.newPage(args.url);
1633
+ const label = typeof args.label === "string" && args.label.length ? args.label : defaultLabel();
1634
+ const p = await newTrackedPage(driver, backendOf2(driver), { url: args.url, label });
1461
1635
  if (args.claim !== true)
1462
1636
  return { targetId: p.id, url: p.url };
1463
1637
  const { record, token } = await claimLease(backendOf2(driver), p.id, {
1464
- label: typeof args.label === "string" && args.label.length ? args.label : `pid-${process.pid}`,
1638
+ label,
1465
1639
  ttlMs: typeof args.ttlMs === "number" && args.ttlMs > 0 ? args.ttlMs : leaseTtlMs()
1466
1640
  });
1467
1641
  return { targetId: p.id, url: p.url, lease: token, label: record.label, expiresAt: record.lastUsedAt + record.ttlMs };
@@ -1477,8 +1651,8 @@ async function closePage(driver, args) {
1477
1651
  return { closed: p.id, success: true, ...released.released ? { leaseReleased: true } : {} };
1478
1652
  }
1479
1653
  async function writeSelectedFile(id) {
1480
- await mkdir2(STATE_DIR, { recursive: true });
1481
- await writeFile2(join2(STATE_DIR, "selected"), id, "utf8");
1654
+ await mkdir3(STATE_DIR, { recursive: true });
1655
+ await writeFile3(join3(STATE_DIR, "selected"), id, "utf8");
1482
1656
  }
1483
1657
  async function selectPage(driver, args) {
1484
1658
  if (args.target === undefined || args.target === "")
@@ -1518,11 +1692,108 @@ async function waitForText(driver, args) {
1518
1692
  return { found: true, waitedMs: r.elapsedMs };
1519
1693
  });
1520
1694
  }
1695
+ function serializeForSink(value) {
1696
+ const json = JSON.stringify(value === undefined ? null : value, null, 2);
1697
+ return json ?? "null";
1698
+ }
1699
+ async function writeJsonSink(savePath, value) {
1700
+ const path = savePath.startsWith("/") ? savePath : join3(ARTIFACT_DIR, savePath);
1701
+ await mkdir3(dirname(path), { recursive: true });
1702
+ const json = serializeForSink(value);
1703
+ await writeFile3(path, json, "utf8");
1704
+ return { path, bytes: Buffer.byteLength(json, "utf8") };
1705
+ }
1706
+ function sinkRequested(savePath) {
1707
+ return savePath !== undefined && savePath !== "";
1708
+ }
1521
1709
  async function evaluateScript(driver, args) {
1522
1710
  if (typeof args.expression !== "string" || args.expression.length === 0) {
1523
1711
  throw new SharedToolError("evaluateScript: 'expression' must be a non-empty string");
1524
1712
  }
1525
- return withPage2(driver, args.target, (page) => page.evaluate(args.expression, { args: args.args, awaitPromise: args.awaitPromise ?? true }));
1713
+ return withPage2(driver, args.target, async (page) => {
1714
+ const value = await page.evaluate(args.expression, { args: args.args, awaitPromise: args.awaitPromise ?? true });
1715
+ if (!sinkRequested(args.savePath))
1716
+ return value;
1717
+ const { path, bytes } = await writeJsonSink(args.savePath, value);
1718
+ const result = {
1719
+ path,
1720
+ bytes,
1721
+ type: value === null ? "null" : typeof value,
1722
+ target: target3(page.info)
1723
+ };
1724
+ return result;
1725
+ });
1726
+ }
1727
+ function cookieDomainMatches(cookieDomain, filter) {
1728
+ const bare = (d) => (d.startsWith(".") ? d.slice(1) : d).toLowerCase();
1729
+ const c = bare(cookieDomain);
1730
+ const f = bare(filter);
1731
+ return c === f || c.endsWith(`.${f}`);
1732
+ }
1733
+ function filterCookies(cookies, filter) {
1734
+ return cookies.filter((c) => {
1735
+ if (filter.name !== undefined && filter.name !== "" && c.name !== filter.name)
1736
+ return false;
1737
+ if (filter.domain !== undefined && filter.domain !== "" && !cookieDomainMatches(c.domain, filter.domain))
1738
+ return false;
1739
+ return true;
1740
+ });
1741
+ }
1742
+ async function listCookies(driver, args = {}) {
1743
+ return withPage2(driver, args.target, async (page) => {
1744
+ const all = await page.getCookies();
1745
+ const cookies = filterCookies(all, { domain: args.domain, name: args.name });
1746
+ if (!sinkRequested(args.savePath)) {
1747
+ return { cookies, count: cookies.length, target: target3(page.info) };
1748
+ }
1749
+ const { path, bytes } = await writeJsonSink(args.savePath, cookies);
1750
+ return { path, bytes, count: cookies.length, target: target3(page.info) };
1751
+ });
1752
+ }
1753
+ function assertCookieSite(args, toolName) {
1754
+ const hasUrl = args.url !== undefined && args.url !== "";
1755
+ const hasDomain = args.domain !== undefined && args.domain !== "";
1756
+ if (!hasUrl && !hasDomain) {
1757
+ throw new Error(`${toolName} requires either 'url' or 'domain': a cookie has to be attributed to a site, and neither was given.`);
1758
+ }
1759
+ }
1760
+ function assertCookieName(name, toolName) {
1761
+ if (name === undefined || name === "") {
1762
+ throw new Error(`${toolName} requires a non-empty 'name'.`);
1763
+ }
1764
+ }
1765
+ async function setCookie(driver, args) {
1766
+ assertCookieName(args.name, "set_cookie");
1767
+ if (typeof args.value !== "string")
1768
+ throw new Error("set_cookie requires a string 'value'.");
1769
+ assertCookieSite(args, "set_cookie");
1770
+ return withPage2(driver, args.target, async (page) => {
1771
+ await page.setCookie({
1772
+ name: args.name,
1773
+ value: args.value,
1774
+ ...args.url !== undefined ? { url: args.url } : {},
1775
+ ...args.domain !== undefined ? { domain: args.domain } : {},
1776
+ ...args.path !== undefined ? { path: args.path } : {},
1777
+ ...args.expires !== undefined ? { expires: args.expires } : {},
1778
+ ...args.httpOnly !== undefined ? { httpOnly: args.httpOnly } : {},
1779
+ ...args.secure !== undefined ? { secure: args.secure } : {},
1780
+ ...args.sameSite !== undefined ? { sameSite: args.sameSite } : {}
1781
+ });
1782
+ return { set: true, target: target3(page.info) };
1783
+ });
1784
+ }
1785
+ async function deleteCookies(driver, args) {
1786
+ assertCookieName(args.name, "delete_cookies");
1787
+ assertCookieSite(args, "delete_cookies");
1788
+ return withPage2(driver, args.target, async (page) => {
1789
+ await page.deleteCookies({
1790
+ name: args.name,
1791
+ ...args.url !== undefined ? { url: args.url } : {},
1792
+ ...args.domain !== undefined ? { domain: args.domain } : {},
1793
+ ...args.path !== undefined ? { path: args.path } : {}
1794
+ });
1795
+ return { deleted: true, target: target3(page.info) };
1796
+ });
1526
1797
  }
1527
1798
  var BARE_TOKEN_EXTRAS = new Set(["disabled", "focused"]);
1528
1799
  var UNQUOTED_EXTRAS = new Set(["checked", "expanded", "url"]);
@@ -1633,10 +1904,10 @@ async function takeScreenshot(driver, args = {}) {
1633
1904
  const { data, format: outFormat } = await page.screenshot({ format, quality: args.quality, fullPage: args.fullPage, ...clip ? { clip } : {} });
1634
1905
  if (data.byteLength === 0)
1635
1906
  throw new SharedToolError("captureScreenshot returned empty data");
1636
- await mkdir2(ARTIFACT_DIR, { recursive: true });
1907
+ await mkdir3(ARTIFACT_DIR, { recursive: true });
1637
1908
  const ext = outFormat === "jpeg" ? "jpg" : "png";
1638
- const path = args.savePath ?? join2(ARTIFACT_DIR, `screenshot-${page.info.id.slice(0, 8)}-${stamp()}.${ext}`);
1639
- await writeFile2(path, Buffer.from(data));
1909
+ const path = args.savePath ?? join3(ARTIFACT_DIR, `screenshot-${page.info.id.slice(0, 8)}-${stamp()}.${ext}`);
1910
+ await writeFile3(path, Buffer.from(data));
1640
1911
  const result = {
1641
1912
  path,
1642
1913
  bytes: data.byteLength,
@@ -1683,6 +1954,9 @@ var SHARED_TOOLS = {
1683
1954
  navigate_page: navigatePage,
1684
1955
  wait_for: waitForText,
1685
1956
  evaluate_script: evaluateScript,
1957
+ list_cookies: listCookies,
1958
+ set_cookie: setCookie,
1959
+ delete_cookies: deleteCookies,
1686
1960
  take_snapshot: takeSnapshot,
1687
1961
  click,
1688
1962
  hover,
@@ -1699,10 +1973,10 @@ var SHARED_TOOLS = {
1699
1973
  };
1700
1974
 
1701
1975
  // src/tools/console.ts
1702
- import { readFile as readFile2 } from "node:fs/promises";
1976
+ import { readFile as readFile3 } from "node:fs/promises";
1703
1977
 
1704
1978
  // src/tools/recorder.ts
1705
- import { mkdir as mkdir3, appendFile, writeFile as writeFile3, copyFile } from "node:fs/promises";
1979
+ import { mkdir as mkdir4, appendFile, writeFile as writeFile4, copyFile } from "node:fs/promises";
1706
1980
  var ARTIFACT_DIR2 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
1707
1981
  function recFile(targetId) {
1708
1982
  return `${ARTIFACT_DIR2}/rec-${targetId}.jsonl`;
@@ -1723,11 +1997,11 @@ var CONSOLE_EVENTS = ["Runtime.consoleAPICalled", "Runtime.exceptionThrown", "Lo
1723
1997
  async function startRecorder(target, opts = {}) {
1724
1998
  const network = opts.network ?? true;
1725
1999
  const consoleCap = opts.console ?? true;
1726
- await mkdir3(ARTIFACT_DIR2, { recursive: true });
2000
+ await mkdir4(ARTIFACT_DIR2, { recursive: true });
1727
2001
  const { conn, target: resolved } = await openPage(target, { timeoutMs: opts.timeoutMs });
1728
2002
  const file = opts.file ?? recFile(resolved.id);
1729
2003
  if (opts.truncate) {
1730
- await writeFile3(file, "");
2004
+ await writeFile4(file, "");
1731
2005
  }
1732
2006
  let chain = Promise.resolve();
1733
2007
  let dropped = 0;
@@ -1869,7 +2143,7 @@ function lineToEntry(line, index) {
1869
2143
  async function readConsoleEntries(file) {
1870
2144
  let raw;
1871
2145
  try {
1872
- raw = await readFile2(file, "utf8");
2146
+ raw = await readFile3(file, "utf8");
1873
2147
  } catch {
1874
2148
  return [];
1875
2149
  }
@@ -1925,7 +2199,7 @@ async function getConsoleMessage(args = {}) {
1925
2199
  }
1926
2200
 
1927
2201
  // src/tools/network.ts
1928
- import { readFile as readFile3 } from "node:fs/promises";
2202
+ import { readFile as readFile4 } from "node:fs/promises";
1929
2203
  var DEFAULT_CAPTURE_MS2 = 2500;
1930
2204
  function asHeaders(v) {
1931
2205
  if (!v || typeof v !== "object")
@@ -1992,7 +2266,7 @@ function correlate(lines) {
1992
2266
  async function readNetworkRequests(file) {
1993
2267
  let raw;
1994
2268
  try {
1995
- raw = await readFile3(file, "utf8");
2269
+ raw = await readFile4(file, "utf8");
1996
2270
  } catch {
1997
2271
  return [];
1998
2272
  }
@@ -2074,8 +2348,8 @@ async function getNetworkRequest(args = {}) {
2074
2348
  }
2075
2349
 
2076
2350
  // src/tools/performance.ts
2077
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, stat, unlink as unlink2 } from "node:fs/promises";
2078
- import { join as join3 } from "node:path";
2351
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, stat, unlink as unlink3 } from "node:fs/promises";
2352
+ import { join as join4 } from "node:path";
2079
2353
  var ARTIFACT_DIR3 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
2080
2354
  var STATE_DIR2 = process.env.CDP_STATE_DIR ?? "/tmp/cdp-toolkit";
2081
2355
  var DEFAULT_CATEGORIES = [
@@ -2093,7 +2367,7 @@ function shortId(id) {
2093
2367
  return id.slice(0, 8);
2094
2368
  }
2095
2369
  function stateFilePath() {
2096
- return join3(STATE_DIR2, "trace-state.json");
2370
+ return join4(STATE_DIR2, "trace-state.json");
2097
2371
  }
2098
2372
  function bufferTraceData(conn, sink) {
2099
2373
  return conn.on("Tracing.dataCollected", (params) => {
@@ -2115,10 +2389,10 @@ async function endTrace(conn, sink, timeoutMs = 30000) {
2115
2389
  return sink;
2116
2390
  }
2117
2391
  async function writeTrace(events, targetId) {
2118
- await mkdir4(ARTIFACT_DIR3, { recursive: true });
2119
- const path = join3(ARTIFACT_DIR3, `trace-${shortId(targetId)}-${stamp2()}.json`);
2392
+ await mkdir5(ARTIFACT_DIR3, { recursive: true });
2393
+ const path = join4(ARTIFACT_DIR3, `trace-${shortId(targetId)}-${stamp2()}.json`);
2120
2394
  const body = JSON.stringify({ traceEvents: events, metadata: { source: "cdp-toolkit" } });
2121
- await writeFile4(path, body, "utf8");
2395
+ await writeFile5(path, body, "utf8");
2122
2396
  const { size } = await stat(path);
2123
2397
  return { path, bytes: size };
2124
2398
  }
@@ -2138,7 +2412,7 @@ async function performanceStartTrace(args = {}) {
2138
2412
  throw err;
2139
2413
  }
2140
2414
  liveTraces.set(target.id, { conn, target, events, startedAt: Date.now() });
2141
- await mkdir4(STATE_DIR2, { recursive: true });
2415
+ await mkdir5(STATE_DIR2, { recursive: true });
2142
2416
  const state = {
2143
2417
  targetId: target.id,
2144
2418
  url: target.url,
@@ -2146,7 +2420,7 @@ async function performanceStartTrace(args = {}) {
2146
2420
  pid: process.pid,
2147
2421
  categories
2148
2422
  };
2149
- await writeFile4(stateFilePath(), JSON.stringify(state), "utf8");
2423
+ await writeFile5(stateFilePath(), JSON.stringify(state), "utf8");
2150
2424
  return {
2151
2425
  target: { id: target.id, url: target.url, title: target.title },
2152
2426
  categories,
@@ -2159,7 +2433,7 @@ async function performanceStopTrace(args = {}) {
2159
2433
  if (!entry) {
2160
2434
  let hint = "";
2161
2435
  try {
2162
- const raw = await readFile4(stateFilePath(), "utf8");
2436
+ const raw = await readFile5(stateFilePath(), "utf8");
2163
2437
  const state = JSON.parse(raw);
2164
2438
  hint = state.pid === process.pid ? ` (state file lists target ${shortId(state.targetId)} but its live connection is gone)` : ` (trace was started by a DIFFERENT process pid=${state.pid}; live recordings cannot cross process boundaries)`;
2165
2439
  } catch {}
@@ -2171,7 +2445,7 @@ async function performanceStopTrace(args = {}) {
2171
2445
  } finally {
2172
2446
  conn.close();
2173
2447
  liveTraces.delete(target.id);
2174
- await unlink2(stateFilePath()).catch(() => {});
2448
+ await unlink3(stateFilePath()).catch(() => {});
2175
2449
  }
2176
2450
  const written = await writeTrace(events, target.id);
2177
2451
  const metrics = analyzeEvents(events);
@@ -2289,7 +2563,7 @@ async function performanceAnalyzeInsight(args = {}) {
2289
2563
  }
2290
2564
  let raw;
2291
2565
  try {
2292
- raw = await readFile4(args.tracePath, "utf8");
2566
+ raw = await readFile5(args.tracePath, "utf8");
2293
2567
  } catch (err) {
2294
2568
  throw new CdpError(`cannot read trace file '${args.tracePath}': ${err.message}`);
2295
2569
  }
@@ -2306,8 +2580,8 @@ async function performanceAnalyzeInsight(args = {}) {
2306
2580
  }
2307
2581
 
2308
2582
  // src/tools/heap.ts
2309
- import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2310
- import { join as join4 } from "node:path";
2583
+ import { mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
2584
+ import { join as join5 } from "node:path";
2311
2585
  var ARTIFACT_DIR4 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
2312
2586
  function stamp3(targetId) {
2313
2587
  const short = targetId.slice(0, 8);
@@ -2332,9 +2606,9 @@ async function takeHeapsnapshot(args = {}) {
2332
2606
  if (snapshot.length === 0) {
2333
2607
  throw new Error("HeapProfiler.takeHeapSnapshot produced no chunks");
2334
2608
  }
2335
- await mkdir5(ARTIFACT_DIR4, { recursive: true });
2336
- const path = args.savePath ? args.savePath.startsWith("/") ? args.savePath : join4(ARTIFACT_DIR4, args.savePath) : join4(ARTIFACT_DIR4, `take_heapsnapshot-${stamp3(target.id)}.heapsnapshot`);
2337
- await writeFile5(path, snapshot, "utf8");
2609
+ await mkdir6(ARTIFACT_DIR4, { recursive: true });
2610
+ const path = args.savePath ? args.savePath.startsWith("/") ? args.savePath : join5(ARTIFACT_DIR4, args.savePath) : join5(ARTIFACT_DIR4, `take_heapsnapshot-${stamp3(target.id)}.heapsnapshot`);
2611
+ await writeFile6(path, snapshot, "utf8");
2338
2612
  const bytes = Buffer.byteLength(snapshot, "utf8");
2339
2613
  return {
2340
2614
  path,
@@ -2347,7 +2621,7 @@ async function takeHeapsnapshot(args = {}) {
2347
2621
 
2348
2622
  // src/tools/lighthouse.ts
2349
2623
  import { spawn } from "node:child_process";
2350
- import { mkdir as mkdir6, readFile as readFile5, stat as stat2 } from "node:fs/promises";
2624
+ import { mkdir as mkdir7, readFile as readFile6, stat as stat2 } from "node:fs/promises";
2351
2625
  var ARTIFACT_DIR5 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
2352
2626
  function cdpPort() {
2353
2627
  const base = process.env.CDP_BASE ?? "http://127.0.0.1:9222";
@@ -2408,7 +2682,7 @@ async function lighthouseAudit(args) {
2408
2682
  const url = args.url.trim();
2409
2683
  const timeoutMs = args.timeoutMs ?? 120000;
2410
2684
  const port = cdpPort();
2411
- await mkdir6(ARTIFACT_DIR5, { recursive: true });
2685
+ await mkdir7(ARTIFACT_DIR5, { recursive: true });
2412
2686
  const outPath = args.savePath ?? `${ARTIFACT_DIR5}/lighthouse-${shortHost(url)}-${stamp4()}.json`;
2413
2687
  const formFactor = args.formFactor ?? "desktop";
2414
2688
  const lhArgs = [
@@ -2437,7 +2711,7 @@ async function lighthouseAudit(args) {
2437
2711
  }
2438
2712
  let raw;
2439
2713
  try {
2440
- raw = await readFile5(outPath, "utf8");
2714
+ raw = await readFile6(outPath, "utf8");
2441
2715
  } catch {
2442
2716
  raw = undefined;
2443
2717
  }
@@ -2494,6 +2768,9 @@ var TOOLS = {
2494
2768
  navigate_page: onCdp(SHARED_TOOLS.navigate_page),
2495
2769
  wait_for: onCdp(SHARED_TOOLS.wait_for),
2496
2770
  evaluate_script: onCdp(SHARED_TOOLS.evaluate_script),
2771
+ list_cookies: onCdp(SHARED_TOOLS.list_cookies),
2772
+ set_cookie: onCdp(SHARED_TOOLS.set_cookie),
2773
+ delete_cookies: onCdp(SHARED_TOOLS.delete_cookies),
2497
2774
  take_snapshot: onCdp(SHARED_TOOLS.take_snapshot),
2498
2775
  click: onCdp(SHARED_TOOLS.click),
2499
2776
  hover: onCdp(SHARED_TOOLS.hover),
@@ -3030,6 +3307,37 @@ function buildSnapshotFunctionSource() {
3030
3307
  var MAX_NAME_LENGTH = 120;
3031
3308
 
3032
3309
  // src/bidi/driver.ts
3310
+ function normalizeBidiCookie(c) {
3311
+ const value = c.value.type === "base64" ? Buffer.from(c.value.value, "base64").toString("utf8") : c.value.value;
3312
+ const hasExpiry = typeof c.expiry === "number";
3313
+ return {
3314
+ name: c.name,
3315
+ value,
3316
+ domain: c.domain,
3317
+ path: c.path,
3318
+ expires: hasExpiry ? c.expiry : -1,
3319
+ size: typeof c.size === "number" ? c.size : Buffer.byteLength(`${c.name}${value}`, "utf8"),
3320
+ httpOnly: c.httpOnly === true,
3321
+ secure: c.secure === true,
3322
+ sameSite: c.sameSite ?? "default",
3323
+ session: !hasExpiry
3324
+ };
3325
+ }
3326
+ function hostFromUrl(url, toolName) {
3327
+ if (url === undefined || url === "") {
3328
+ throw driverError2("unsupported", `${toolName} on Firefox needs a 'domain': BiDi's storage cookie calls have no 'url' parameter, and no url was given to derive one from.`);
3329
+ }
3330
+ let host = "";
3331
+ try {
3332
+ host = new URL(url).hostname;
3333
+ } catch {
3334
+ throw driverError2("unsupported", `${toolName} could not parse the url '${url}' to derive the 'domain' BiDi requires. Pass 'domain' explicitly.`);
3335
+ }
3336
+ if (host === "") {
3337
+ throw driverError2("unsupported", `${toolName} cannot derive a 'domain' from '${url}': that url has no host. Pass 'domain' explicitly.`);
3338
+ }
3339
+ return host;
3340
+ }
3033
3341
  function driverError2(code, message, data) {
3034
3342
  return Object.assign(new Error(message), { code, ...data !== undefined ? { data } : {} });
3035
3343
  }
@@ -3638,6 +3946,49 @@ class BidiPageDriver {
3638
3946
  }
3639
3947
  return this.handleOneDialog(accept, promptText, opts?.timeoutMs ?? 15000);
3640
3948
  }
3949
+ async getCookies() {
3950
+ try {
3951
+ const { cookies } = await this.conn.send("storage.getCookies", { partition: { type: "context", context: this.contextId } });
3952
+ return (cookies ?? []).map(normalizeBidiCookie);
3953
+ } catch (e) {
3954
+ throw mapBidiError(e);
3955
+ }
3956
+ }
3957
+ async setCookie(params) {
3958
+ const domain = params.domain ?? hostFromUrl(params.url, "set_cookie");
3959
+ try {
3960
+ await this.conn.send("storage.setCookie", {
3961
+ cookie: {
3962
+ name: params.name,
3963
+ value: { type: "string", value: params.value },
3964
+ domain,
3965
+ ...params.path !== undefined ? { path: params.path } : {},
3966
+ ...params.expires !== undefined ? { expiry: params.expires } : {},
3967
+ ...params.httpOnly !== undefined ? { httpOnly: params.httpOnly } : {},
3968
+ ...params.secure !== undefined ? { secure: params.secure } : {},
3969
+ ...params.sameSite !== undefined ? { sameSite: params.sameSite } : {}
3970
+ },
3971
+ partition: { type: "context", context: this.contextId }
3972
+ });
3973
+ } catch (e) {
3974
+ throw mapBidiError(e);
3975
+ }
3976
+ }
3977
+ async deleteCookies(filter) {
3978
+ const domain = filter.domain ?? hostFromUrl(filter.url, "delete_cookies");
3979
+ try {
3980
+ await this.conn.send("storage.deleteCookies", {
3981
+ filter: {
3982
+ name: filter.name,
3983
+ domain,
3984
+ ...filter.path !== undefined ? { path: filter.path } : {}
3985
+ },
3986
+ partition: { type: "context", context: this.contextId }
3987
+ });
3988
+ } catch (e) {
3989
+ throw mapBidiError(e);
3990
+ }
3991
+ }
3641
3992
  async startBodyCapture() {
3642
3993
  let collector;
3643
3994
  try {
@@ -3812,7 +4163,7 @@ import { spawn as spawn2 } from "node:child_process";
3812
4163
  import { mkdtempSync, rmSync } from "node:fs";
3813
4164
  import { createConnection, createServer } from "node:net";
3814
4165
  import { tmpdir } from "node:os";
3815
- import { join as join5 } from "node:path";
4166
+ import { join as join6 } from "node:path";
3816
4167
  function resolveBinary() {
3817
4168
  const candidates = [
3818
4169
  process.env.FIREFOX_BIN,
@@ -3886,7 +4237,7 @@ async function launchFirefox(opts = {}) {
3886
4237
  const binary = resolveBinary();
3887
4238
  const port = await pickPort(opts.port);
3888
4239
  const isThrowaway = opts.profilePath === undefined;
3889
- const profilePath = opts.profilePath ?? mkdtempSync(join5(tmpdir(), "cdp-toolkit-ff-profile-"));
4240
+ const profilePath = opts.profilePath ?? mkdtempSync(join6(tmpdir(), "cdp-toolkit-ff-profile-"));
3890
4241
  const args = ["--profile", profilePath, "--remote-debugging-port", String(port), "--no-remote"];
3891
4242
  if (opts.headless)
3892
4243
  args.push("--headless");