@skrillex1224/playwright-toolkit 2.1.163 → 2.1.165

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/index.js CHANGED
@@ -1387,6 +1387,27 @@ var resolveRouteByProxy = ({
1387
1387
  // src/traffic-meter.js
1388
1388
  var logger6 = createInternalLogger("TrafficMeter");
1389
1389
  var encoder = new TextEncoder();
1390
+ var MAX_DOMAIN_BUCKETS = 160;
1391
+ var MAX_REASON_BUCKETS = 64;
1392
+ var MAX_TOP_ITEMS = 12;
1393
+ var MAX_HINT_ITEMS = 8;
1394
+ var UNKNOWN_DOMAIN = "(unknown)";
1395
+ var OTHER_DOMAINS = "(other-domains)";
1396
+ var OTHER_REASONS = "(other-reasons)";
1397
+ var STATIC_RESOURCE_TYPES = /* @__PURE__ */ new Set([
1398
+ "script",
1399
+ "stylesheet",
1400
+ "image",
1401
+ "font",
1402
+ "media",
1403
+ "manifest"
1404
+ ]);
1405
+ var BACKEND_RESOURCE_TYPES = /* @__PURE__ */ new Set([
1406
+ "xhr",
1407
+ "fetch",
1408
+ "websocket",
1409
+ "eventsource"
1410
+ ]);
1390
1411
  var toSafeNumber = (value) => {
1391
1412
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
1392
1413
  return Math.round(value);
@@ -1430,9 +1451,90 @@ var createTrafficState = () => ({
1430
1451
  directUploadBytes: 0,
1431
1452
  totalDownloadBytes: 0,
1432
1453
  proxyDownloadBytes: 0,
1433
- directDownloadBytes: 0
1454
+ directDownloadBytes: 0,
1455
+ totalFailedRequests: 0,
1456
+ proxyFailedRequests: 0,
1457
+ directFailedRequests: 0,
1458
+ totalCanceledRequests: 0,
1459
+ proxyCanceledRequests: 0,
1460
+ directCanceledRequests: 0,
1461
+ orphanDataReceivedBytes: 0,
1462
+ orphanProxyDataReceivedBytes: 0,
1463
+ orphanFinishDeltaBytes: 0,
1464
+ orphanProxyFinishDeltaBytes: 0,
1465
+ domainStats: /* @__PURE__ */ new Map(),
1466
+ typeStats: /* @__PURE__ */ new Map(),
1467
+ failedReasonStats: /* @__PURE__ */ new Map()
1434
1468
  });
1435
1469
  var ensureRoute = (route) => route === "proxy" ? "proxy" : "direct";
1470
+ var normalizeResourceType = (value) => {
1471
+ const type = String(value || "").trim().toLowerCase();
1472
+ if (!type) return "other";
1473
+ if (type === "ws") return "websocket";
1474
+ return type;
1475
+ };
1476
+ var parseHostname = (url = "") => {
1477
+ try {
1478
+ const hostname = new URL(String(url || "")).hostname.toLowerCase();
1479
+ return hostname || "";
1480
+ } catch {
1481
+ return "";
1482
+ }
1483
+ };
1484
+ var normalizeDomainKey = (domain = "") => {
1485
+ const key = String(domain || "").trim().toLowerCase();
1486
+ return key || UNKNOWN_DOMAIN;
1487
+ };
1488
+ var isStaticType = (resourceType = "") => STATIC_RESOURCE_TYPES.has(normalizeResourceType(resourceType));
1489
+ var isBackendType = (resourceType = "") => BACKEND_RESOURCE_TYPES.has(normalizeResourceType(resourceType));
1490
+ var createDomainBucket = (domain) => ({
1491
+ domain,
1492
+ requests: 0,
1493
+ proxyRequests: 0,
1494
+ directRequests: 0,
1495
+ uploadBytes: 0,
1496
+ downloadBytes: 0,
1497
+ totalBytes: 0,
1498
+ proxyBytes: 0,
1499
+ directBytes: 0,
1500
+ failedRequests: 0,
1501
+ canceledRequests: 0,
1502
+ staticBytes: 0,
1503
+ backendBytes: 0
1504
+ });
1505
+ var createTypeBucket = (resourceType) => ({
1506
+ resourceType,
1507
+ requests: 0,
1508
+ proxyRequests: 0,
1509
+ directRequests: 0,
1510
+ uploadBytes: 0,
1511
+ downloadBytes: 0,
1512
+ totalBytes: 0,
1513
+ proxyBytes: 0,
1514
+ directBytes: 0,
1515
+ failedRequests: 0,
1516
+ canceledRequests: 0
1517
+ });
1518
+ var createReasonBucket = (reason) => ({
1519
+ reason,
1520
+ count: 0,
1521
+ canceledCount: 0,
1522
+ proxyCount: 0,
1523
+ directCount: 0
1524
+ });
1525
+ var formatBytes = (bytes = 0) => {
1526
+ const value = toSafeNumber(bytes);
1527
+ if (value <= 0) return "0B";
1528
+ const units = ["B", "KB", "MB", "GB", "TB"];
1529
+ let size = value;
1530
+ let unit = 0;
1531
+ while (size >= 1024 && unit < units.length - 1) {
1532
+ size /= 1024;
1533
+ unit += 1;
1534
+ }
1535
+ const precision = size >= 100 || unit === 0 ? 0 : 2;
1536
+ return `${size.toFixed(precision)}${units[unit]}`;
1537
+ };
1436
1538
  var addRequests = (state, route, count = 1) => {
1437
1539
  const c = toSafeNumber(count);
1438
1540
  if (c <= 0) return;
@@ -1466,13 +1568,201 @@ var addDownloadBytes = (state, route, bytes = 0) => {
1466
1568
  }
1467
1569
  state.directDownloadBytes += b;
1468
1570
  };
1571
+ var incrementBucketRequests = (bucket, route) => {
1572
+ if (!bucket) return;
1573
+ bucket.requests += 1;
1574
+ if (ensureRoute(route) === "proxy") {
1575
+ bucket.proxyRequests += 1;
1576
+ return;
1577
+ }
1578
+ bucket.directRequests += 1;
1579
+ };
1580
+ var incrementBucketBytes = (bucket, route, bytes = 0, direction = "download", resourceType = "other") => {
1581
+ if (!bucket) return;
1582
+ const b = toSafeNumber(bytes);
1583
+ if (b <= 0) return;
1584
+ if (direction === "upload") {
1585
+ bucket.uploadBytes += b;
1586
+ } else {
1587
+ bucket.downloadBytes += b;
1588
+ }
1589
+ bucket.totalBytes += b;
1590
+ if (ensureRoute(route) === "proxy") {
1591
+ bucket.proxyBytes += b;
1592
+ } else {
1593
+ bucket.directBytes += b;
1594
+ }
1595
+ if (bucket.domain !== void 0) {
1596
+ if (isStaticType(resourceType)) {
1597
+ bucket.staticBytes += b;
1598
+ } else if (isBackendType(resourceType)) {
1599
+ bucket.backendBytes += b;
1600
+ }
1601
+ }
1602
+ };
1603
+ var incrementBucketFailed = (bucket, canceled = false) => {
1604
+ if (!bucket) return;
1605
+ bucket.failedRequests += 1;
1606
+ if (canceled) {
1607
+ bucket.canceledRequests += 1;
1608
+ }
1609
+ };
1610
+ var ensureReasonText = (reason) => {
1611
+ const normalized = String(reason || "").trim().toLowerCase();
1612
+ return normalized || "unknown";
1613
+ };
1614
+ var pickDomainBucket = (state, domain = "") => {
1615
+ const domainKey = normalizeDomainKey(domain);
1616
+ const knownBucket = state.domainStats.get(domainKey);
1617
+ if (knownBucket) return knownBucket;
1618
+ if (state.domainStats.size < MAX_DOMAIN_BUCKETS) {
1619
+ const bucket2 = createDomainBucket(domainKey);
1620
+ state.domainStats.set(domainKey, bucket2);
1621
+ return bucket2;
1622
+ }
1623
+ const overflow = state.domainStats.get(OTHER_DOMAINS);
1624
+ if (overflow) return overflow;
1625
+ const bucket = createDomainBucket(OTHER_DOMAINS);
1626
+ state.domainStats.set(OTHER_DOMAINS, bucket);
1627
+ return bucket;
1628
+ };
1629
+ var pickTypeBucket = (state, resourceType = "other") => {
1630
+ const typeKey = normalizeResourceType(resourceType);
1631
+ const knownBucket = state.typeStats.get(typeKey);
1632
+ if (knownBucket) return knownBucket;
1633
+ const bucket = createTypeBucket(typeKey);
1634
+ state.typeStats.set(typeKey, bucket);
1635
+ return bucket;
1636
+ };
1637
+ var pickReasonBucket = (state, reason = "") => {
1638
+ const reasonKey = ensureReasonText(reason);
1639
+ const knownBucket = state.failedReasonStats.get(reasonKey);
1640
+ if (knownBucket) return knownBucket;
1641
+ if (state.failedReasonStats.size < MAX_REASON_BUCKETS) {
1642
+ const bucket2 = createReasonBucket(reasonKey);
1643
+ state.failedReasonStats.set(reasonKey, bucket2);
1644
+ return bucket2;
1645
+ }
1646
+ const overflow = state.failedReasonStats.get(OTHER_REASONS);
1647
+ if (overflow) return overflow;
1648
+ const bucket = createReasonBucket(OTHER_REASONS);
1649
+ state.failedReasonStats.set(OTHER_REASONS, bucket);
1650
+ return bucket;
1651
+ };
1652
+ var addRequestProfile = (state, route, domain, resourceType, uploadBytes = 0) => {
1653
+ const domainBucket = pickDomainBucket(state, domain);
1654
+ const typeBucket = pickTypeBucket(state, resourceType);
1655
+ incrementBucketRequests(domainBucket, route);
1656
+ incrementBucketRequests(typeBucket, route);
1657
+ incrementBucketBytes(domainBucket, route, uploadBytes, "upload", resourceType);
1658
+ incrementBucketBytes(typeBucket, route, uploadBytes, "upload", resourceType);
1659
+ };
1660
+ var addUploadProfile = (state, route, domain, resourceType, uploadBytes = 0) => {
1661
+ const domainBucket = pickDomainBucket(state, domain);
1662
+ const typeBucket = pickTypeBucket(state, resourceType);
1663
+ incrementBucketBytes(domainBucket, route, uploadBytes, "upload", resourceType);
1664
+ incrementBucketBytes(typeBucket, route, uploadBytes, "upload", resourceType);
1665
+ };
1666
+ var addDownloadProfile = (state, route, domain, resourceType, downloadBytes = 0) => {
1667
+ const domainBucket = pickDomainBucket(state, domain);
1668
+ const typeBucket = pickTypeBucket(state, resourceType);
1669
+ incrementBucketBytes(domainBucket, route, downloadBytes, "download", resourceType);
1670
+ incrementBucketBytes(typeBucket, route, downloadBytes, "download", resourceType);
1671
+ };
1672
+ var addFailedProfile = (state, route, domain, resourceType, reason = "unknown", canceled = false) => {
1673
+ const domainBucket = pickDomainBucket(state, domain);
1674
+ const typeBucket = pickTypeBucket(state, resourceType);
1675
+ const reasonBucket = pickReasonBucket(state, reason);
1676
+ incrementBucketFailed(domainBucket, canceled);
1677
+ incrementBucketFailed(typeBucket, canceled);
1678
+ reasonBucket.count += 1;
1679
+ if (ensureRoute(route) === "proxy") {
1680
+ reasonBucket.proxyCount += 1;
1681
+ } else {
1682
+ reasonBucket.directCount += 1;
1683
+ }
1684
+ if (canceled) {
1685
+ reasonBucket.canceledCount += 1;
1686
+ }
1687
+ };
1688
+ var toRoundedRatio = (numerator, denominator) => {
1689
+ if (denominator <= 0) return 0;
1690
+ return Number((numerator / denominator * 100).toFixed(2));
1691
+ };
1692
+ var sortByBytesAndRequests = (left, right) => {
1693
+ if (right.totalBytes !== left.totalBytes) return right.totalBytes - left.totalBytes;
1694
+ if (right.requests !== left.requests) return right.requests - left.requests;
1695
+ return String(left.domain || left.resourceType || "").localeCompare(String(right.domain || right.resourceType || ""));
1696
+ };
1697
+ var buildTopDomains = (state) => {
1698
+ return Array.from(state.domainStats.values()).filter((item) => item && item.totalBytes > 0).sort(sortByBytesAndRequests).slice(0, MAX_TOP_ITEMS).map((item) => ({
1699
+ domain: item.domain,
1700
+ requests: item.requests,
1701
+ proxyRequests: item.proxyRequests,
1702
+ directRequests: item.directRequests,
1703
+ uploadBytes: item.uploadBytes,
1704
+ downloadBytes: item.downloadBytes,
1705
+ totalBytes: item.totalBytes,
1706
+ proxyBytes: item.proxyBytes,
1707
+ directBytes: item.directBytes,
1708
+ failedRequests: item.failedRequests,
1709
+ canceledRequests: item.canceledRequests,
1710
+ staticBytes: item.staticBytes,
1711
+ backendBytes: item.backendBytes
1712
+ }));
1713
+ };
1714
+ var buildTopResourceTypes = (state) => {
1715
+ return Array.from(state.typeStats.values()).filter((item) => item && (item.totalBytes > 0 || item.requests > 0)).sort(sortByBytesAndRequests).slice(0, MAX_TOP_ITEMS).map((item) => ({
1716
+ resourceType: item.resourceType,
1717
+ requests: item.requests,
1718
+ proxyRequests: item.proxyRequests,
1719
+ directRequests: item.directRequests,
1720
+ uploadBytes: item.uploadBytes,
1721
+ downloadBytes: item.downloadBytes,
1722
+ totalBytes: item.totalBytes,
1723
+ proxyBytes: item.proxyBytes,
1724
+ directBytes: item.directBytes,
1725
+ failedRequests: item.failedRequests,
1726
+ canceledRequests: item.canceledRequests
1727
+ }));
1728
+ };
1729
+ var buildFailedReasons = (state) => {
1730
+ return Array.from(state.failedReasonStats.values()).filter((item) => item && item.count > 0).sort((left, right) => {
1731
+ if (right.count !== left.count) return right.count - left.count;
1732
+ return String(left.reason || "").localeCompare(String(right.reason || ""));
1733
+ }).slice(0, MAX_TOP_ITEMS).map((item) => ({
1734
+ reason: item.reason,
1735
+ count: item.count,
1736
+ canceledCount: item.canceledCount,
1737
+ proxyCount: item.proxyCount,
1738
+ directCount: item.directCount
1739
+ }));
1740
+ };
1741
+ var buildProxyByPassHints = (state) => {
1742
+ return Array.from(state.domainStats.values()).filter((item) => item && item.domain !== UNKNOWN_DOMAIN && item.domain !== OTHER_DOMAINS).filter((item) => item.proxyBytes >= 128 * 1024 && item.proxyRequests >= 2 && item.totalBytes > 0).filter((item) => item.staticBytes > item.backendBytes && toRoundedRatio(item.staticBytes, item.totalBytes) >= 80).sort((left, right) => {
1743
+ if (right.proxyBytes !== left.proxyBytes) return right.proxyBytes - left.proxyBytes;
1744
+ return right.proxyRequests - left.proxyRequests;
1745
+ }).slice(0, MAX_HINT_ITEMS).map((item) => ({
1746
+ domain: item.domain,
1747
+ proxyBytes: item.proxyBytes,
1748
+ proxyRequests: item.proxyRequests,
1749
+ totalBytes: item.totalBytes,
1750
+ staticBytes: item.staticBytes,
1751
+ backendBytes: item.backendBytes,
1752
+ staticRatioPct: toRoundedRatio(item.staticBytes, item.totalBytes)
1753
+ }));
1754
+ };
1469
1755
  var createTrafficMeter = ({
1470
1756
  enableProxy = false,
1471
- byPassRules = []
1757
+ byPassRules = [],
1758
+ debugMode = false
1472
1759
  } = {}) => {
1473
1760
  const state = createTrafficState();
1474
1761
  const requestMap = /* @__PURE__ */ new Map();
1762
+ const orphanReceivedMap = /* @__PURE__ */ new Map();
1475
1763
  const wsRouteMap = /* @__PURE__ */ new Map();
1764
+ const attachedPages = /* @__PURE__ */ new WeakSet();
1765
+ const attachedContexts = /* @__PURE__ */ new WeakSet();
1476
1766
  const resolveRoute = (url = "") => {
1477
1767
  return resolveRouteByProxy({
1478
1768
  requestUrl: url,
@@ -1480,80 +1770,234 @@ var createTrafficMeter = ({
1480
1770
  byPassRules
1481
1771
  }).route;
1482
1772
  };
1773
+ const fallbackRoute = () => enableProxy ? "proxy" : "direct";
1774
+ const debugLog = (message) => {
1775
+ if (!debugMode) return;
1776
+ logger6.info(`[\u9010\u8BF7\u6C42\u8C03\u8BD5] ${message}`);
1777
+ };
1778
+ const addFailed = (route, canceled = false) => {
1779
+ const normalizedRoute = ensureRoute(route);
1780
+ state.totalFailedRequests += 1;
1781
+ if (normalizedRoute === "proxy") {
1782
+ state.proxyFailedRequests += 1;
1783
+ } else {
1784
+ state.directFailedRequests += 1;
1785
+ }
1786
+ if (!canceled) return;
1787
+ state.totalCanceledRequests += 1;
1788
+ if (normalizedRoute === "proxy") {
1789
+ state.proxyCanceledRequests += 1;
1790
+ } else {
1791
+ state.directCanceledRequests += 1;
1792
+ }
1793
+ };
1794
+ const finalizeByEncodedLength = (requestId, encodedDataLength, source = "finished") => {
1795
+ const safeRequestId = String(requestId || "");
1796
+ if (!safeRequestId) return;
1797
+ const requestState = requestMap.get(safeRequestId);
1798
+ const orphanReceived = toSafeNumber(orphanReceivedMap.get(safeRequestId));
1799
+ const encoded = toSafeNumber(encodedDataLength);
1800
+ if (requestState) {
1801
+ const routed2 = ensureRoute(requestState.route);
1802
+ const downloaded = toSafeNumber(requestState.downloadBytes);
1803
+ const delta2 = Math.max(0, encoded - downloaded);
1804
+ if (delta2 > 0) {
1805
+ addDownloadBytes(state, routed2, delta2);
1806
+ addDownloadProfile(state, routed2, requestState.domain, requestState.resourceType, delta2);
1807
+ requestState.downloadBytes = downloaded + delta2;
1808
+ }
1809
+ const uploadBytes = toSafeNumber(requestState.uploadBytes);
1810
+ const total = uploadBytes + toSafeNumber(requestState.downloadBytes);
1811
+ debugLog(
1812
+ `final id=${safeRequestId} source=${source} status=ok route=${routed2} type=${requestState.resourceType || "other"} upload=${formatBytes(uploadBytes)} (${uploadBytes}) download=${formatBytes(requestState.downloadBytes)} (${requestState.downloadBytes}) total=${formatBytes(total)} (${total}) url=${requestState.url || "-"}`
1813
+ );
1814
+ requestMap.delete(safeRequestId);
1815
+ orphanReceivedMap.delete(safeRequestId);
1816
+ return;
1817
+ }
1818
+ const routed = fallbackRoute();
1819
+ const delta = Math.max(0, encoded - orphanReceived);
1820
+ if (delta > 0) {
1821
+ addDownloadBytes(state, routed, delta);
1822
+ addDownloadProfile(state, routed, UNKNOWN_DOMAIN, "other", delta);
1823
+ }
1824
+ state.orphanFinishDeltaBytes += delta;
1825
+ if (routed === "proxy") {
1826
+ state.orphanProxyFinishDeltaBytes += delta;
1827
+ }
1828
+ debugLog(
1829
+ `final id=${safeRequestId} source=${source} status=orphan route=${routed} encoded=${formatBytes(encoded)} (${encoded}) delta=${formatBytes(delta)} (${delta})`
1830
+ );
1831
+ orphanReceivedMap.delete(safeRequestId);
1832
+ };
1483
1833
  const recordRequest = (params = {}) => {
1484
1834
  const requestId = String(params.requestId || "");
1485
1835
  const request = params.request && typeof params.request === "object" ? params.request : {};
1486
- const route = resolveRoute(request.url || "");
1836
+ const url = String(request.url || "");
1837
+ const route = resolveRoute(url);
1838
+ const resourceType = normalizeResourceType(params.type || request.type || "other");
1839
+ const domain = normalizeDomainKey(parseHostname(url));
1840
+ if (requestId && requestMap.has(requestId)) {
1841
+ const redirectResponse = params.redirectResponse && typeof params.redirectResponse === "object" ? params.redirectResponse : null;
1842
+ finalizeByEncodedLength(
1843
+ requestId,
1844
+ redirectResponse ? redirectResponse.encodedDataLength : 0,
1845
+ "redirect"
1846
+ );
1847
+ }
1487
1848
  addRequests(state, route, 1);
1488
1849
  const uploadBytes = estimateRequestBytes(request);
1489
1850
  addUploadBytes(state, route, uploadBytes);
1851
+ addRequestProfile(state, route, domain, resourceType, uploadBytes);
1490
1852
  if (requestId) {
1491
1853
  requestMap.set(requestId, {
1492
- route
1854
+ route: ensureRoute(route),
1855
+ resourceType,
1856
+ domain,
1857
+ url,
1858
+ uploadBytes,
1859
+ downloadBytes: 0
1493
1860
  });
1494
1861
  }
1862
+ debugLog(
1863
+ `request id=${requestId || "-"} route=${route} type=${resourceType} upload=${formatBytes(uploadBytes)} (${uploadBytes}) method=${String(request.method || "GET")} url=${url || "-"}`
1864
+ );
1495
1865
  };
1496
- const recordLoadingFinished = (params = {}) => {
1866
+ const recordDataReceived = (params = {}) => {
1497
1867
  const requestId = String(params.requestId || "");
1498
- const requestState = requestId ? requestMap.get(requestId) : null;
1499
- const route = requestState?.route || "direct";
1500
- const downloadBytes = toSafeNumber(params.encodedDataLength);
1501
- addDownloadBytes(state, route, downloadBytes);
1502
- if (requestId) {
1503
- requestMap.delete(requestId);
1868
+ const bytes = toSafeNumber(params.encodedDataLength);
1869
+ if (bytes <= 0) return;
1870
+ if (!requestId) {
1871
+ const routed2 = fallbackRoute();
1872
+ addDownloadBytes(state, routed2, bytes);
1873
+ addDownloadProfile(state, routed2, UNKNOWN_DOMAIN, "other", bytes);
1874
+ state.orphanDataReceivedBytes += bytes;
1875
+ if (routed2 === "proxy") {
1876
+ state.orphanProxyDataReceivedBytes += bytes;
1877
+ }
1878
+ return;
1879
+ }
1880
+ const requestState = requestMap.get(requestId);
1881
+ if (requestState) {
1882
+ requestState.downloadBytes = toSafeNumber(requestState.downloadBytes) + bytes;
1883
+ addDownloadBytes(state, requestState.route, bytes);
1884
+ addDownloadProfile(state, requestState.route, requestState.domain, requestState.resourceType, bytes);
1885
+ return;
1504
1886
  }
1887
+ const prev = toSafeNumber(orphanReceivedMap.get(requestId));
1888
+ orphanReceivedMap.set(requestId, prev + bytes);
1889
+ const routed = fallbackRoute();
1890
+ addDownloadBytes(state, routed, bytes);
1891
+ addDownloadProfile(state, routed, UNKNOWN_DOMAIN, "other", bytes);
1892
+ state.orphanDataReceivedBytes += bytes;
1893
+ if (routed === "proxy") {
1894
+ state.orphanProxyDataReceivedBytes += bytes;
1895
+ }
1896
+ };
1897
+ const recordLoadingFinished = (params = {}) => {
1898
+ finalizeByEncodedLength(params.requestId, params.encodedDataLength, "loadingFinished");
1505
1899
  };
1506
1900
  const recordRequestFailed = (params = {}) => {
1507
1901
  const requestId = String(params.requestId || "");
1902
+ const requestState = requestId ? requestMap.get(requestId) : null;
1903
+ const canceled = Boolean(params.canceled);
1904
+ const reason = ensureReasonText(params.errorText);
1905
+ const routed = ensureRoute(requestState?.route || fallbackRoute());
1906
+ const resourceType = normalizeResourceType(requestState?.resourceType || "other");
1907
+ const domain = normalizeDomainKey(requestState?.domain || "");
1908
+ if (requestState) {
1909
+ addFailed(routed, canceled);
1910
+ addFailedProfile(state, routed, domain, resourceType, reason, canceled);
1911
+ const uploadBytes = toSafeNumber(requestState.uploadBytes);
1912
+ const downloadBytes = toSafeNumber(requestState.downloadBytes);
1913
+ const totalBytes = uploadBytes + downloadBytes;
1914
+ debugLog(
1915
+ `final id=${requestId || "-"} source=loadingFailed status=failed route=${routed} type=${requestState.resourceType || "other"} upload=${formatBytes(uploadBytes)} (${uploadBytes}) download=${formatBytes(downloadBytes)} (${downloadBytes}) total=${formatBytes(totalBytes)} (${totalBytes}) canceled=${canceled} reason=${reason} url=${requestState.url || "-"}`
1916
+ );
1917
+ } else {
1918
+ const orphanDownload = toSafeNumber(orphanReceivedMap.get(requestId));
1919
+ addFailed(routed, canceled);
1920
+ addFailedProfile(state, routed, UNKNOWN_DOMAIN, "other", reason, canceled);
1921
+ debugLog(
1922
+ `final id=${requestId || "-"} source=loadingFailed status=orphan-failed route=${routed} upload=0B (0) download=${formatBytes(orphanDownload)} (${orphanDownload}) total=${formatBytes(orphanDownload)} (${orphanDownload}) canceled=${canceled} reason=${reason} url=-`
1923
+ );
1924
+ }
1508
1925
  if (requestId) {
1509
1926
  requestMap.delete(requestId);
1927
+ orphanReceivedMap.delete(requestId);
1510
1928
  }
1511
1929
  };
1512
1930
  const bindWebSocketRoute = (params = {}) => {
1513
1931
  const requestId = String(params.requestId || "");
1514
1932
  if (!requestId) return;
1515
- const route = resolveRoute(params.url || "");
1516
- wsRouteMap.set(requestId, route);
1933
+ const url = String(params.url || "");
1934
+ const route = resolveRoute(url);
1935
+ wsRouteMap.set(requestId, { route, url, domain: normalizeDomainKey(parseHostname(url)) });
1517
1936
  };
1518
1937
  const clearWebSocketRoute = (params = {}) => {
1519
1938
  const requestId = String(params.requestId || "");
1520
1939
  if (!requestId) return;
1521
1940
  wsRouteMap.delete(requestId);
1522
1941
  };
1523
- const getWebSocketRoute = (requestId = "") => {
1524
- if (!requestId) return "direct";
1525
- return wsRouteMap.get(requestId) || "direct";
1942
+ const getWebSocketMeta = (requestId = "") => {
1943
+ if (!requestId) return { route: fallbackRoute(), url: "", domain: UNKNOWN_DOMAIN };
1944
+ const meta = wsRouteMap.get(requestId);
1945
+ if (!meta || typeof meta !== "object") {
1946
+ return { route: fallbackRoute(), url: "", domain: UNKNOWN_DOMAIN };
1947
+ }
1948
+ return {
1949
+ route: ensureRoute(meta.route),
1950
+ url: String(meta.url || ""),
1951
+ domain: normalizeDomainKey(meta.domain)
1952
+ };
1526
1953
  };
1527
1954
  const recordWebSocketFrameSent = (params = {}) => {
1528
1955
  const requestId = String(params.requestId || "");
1529
- const route = getWebSocketRoute(requestId);
1956
+ const { route, url, domain } = getWebSocketMeta(requestId);
1530
1957
  const payload = params.response && typeof params.response === "object" ? params.response.payloadData : "";
1531
1958
  const bytes = byteLength(payload || "");
1532
1959
  addUploadBytes(state, route, bytes);
1960
+ addUploadProfile(state, route, domain, "websocket", bytes);
1961
+ debugLog(`ws-send id=${requestId || "-"} route=${route} bytes=${formatBytes(bytes)} (${bytes}) url=${url || "-"}`);
1533
1962
  };
1534
1963
  const recordWebSocketFrameReceived = (params = {}) => {
1535
1964
  const requestId = String(params.requestId || "");
1536
- const route = getWebSocketRoute(requestId);
1965
+ const { route, url, domain } = getWebSocketMeta(requestId);
1537
1966
  const payload = params.response && typeof params.response === "object" ? params.response.payloadData : "";
1538
1967
  const bytes = byteLength(payload || "");
1539
1968
  addDownloadBytes(state, route, bytes);
1969
+ addDownloadProfile(state, route, domain, "websocket", bytes);
1970
+ debugLog(`ws-recv id=${requestId || "-"} route=${route} bytes=${formatBytes(bytes)} (${bytes}) url=${url || "-"}`);
1540
1971
  };
1541
1972
  const attachPage = async (page) => {
1542
1973
  if (!page || typeof page.context !== "function") return;
1974
+ if (attachedPages.has(page)) return;
1975
+ attachedPages.add(page);
1543
1976
  try {
1544
1977
  const context = page.context();
1545
1978
  if (!context || typeof context.newCDPSession !== "function") {
1546
1979
  return;
1547
1980
  }
1981
+ if (!attachedContexts.has(context) && typeof context.on === "function") {
1982
+ attachedContexts.add(context);
1983
+ context.on("page", (nextPage) => {
1984
+ if (!nextPage) return;
1985
+ attachPage(nextPage).catch((error) => {
1986
+ logger6.warn(`\u5B50\u9875\u9762 CDP \u76D1\u542C\u6CE8\u518C\u5931\u8D25: ${error?.message || error}`);
1987
+ });
1988
+ });
1989
+ }
1548
1990
  const session = await context.newCDPSession(page);
1549
1991
  await session.send("Network.enable");
1550
1992
  session.on("Network.requestWillBeSent", recordRequest);
1993
+ session.on("Network.dataReceived", recordDataReceived);
1551
1994
  session.on("Network.loadingFinished", recordLoadingFinished);
1552
1995
  session.on("Network.loadingFailed", recordRequestFailed);
1553
1996
  session.on("Network.webSocketCreated", bindWebSocketRoute);
1554
1997
  session.on("Network.webSocketClosed", clearWebSocketRoute);
1555
1998
  session.on("Network.webSocketFrameSent", recordWebSocketFrameSent);
1556
1999
  session.on("Network.webSocketFrameReceived", recordWebSocketFrameReceived);
2000
+ debugLog("CDP \u76D1\u542C\u5DF2\u6CE8\u518C");
1557
2001
  } catch (error) {
1558
2002
  logger6.warn(`CDP \u76D1\u542C\u6CE8\u518C\u5931\u8D25: ${error?.message || error}`);
1559
2003
  }
@@ -1563,7 +2007,7 @@ var createTrafficMeter = ({
1563
2007
  const proxyBytes = state.proxyUploadBytes + state.proxyDownloadBytes;
1564
2008
  const directBytes = state.directUploadBytes + state.directDownloadBytes;
1565
2009
  return {
1566
- meter: "cdp",
2010
+ meter: "cdp-data-received-v3",
1567
2011
  totalRequests: state.totalRequests,
1568
2012
  proxyRequests: state.proxyRequests,
1569
2013
  directRequests: state.directRequests,
@@ -1575,12 +2019,29 @@ var createTrafficMeter = ({
1575
2019
  directDownloadBytes: state.directDownloadBytes,
1576
2020
  totalBytes,
1577
2021
  proxyBytes,
1578
- directBytes
2022
+ directBytes,
2023
+ totalFailedRequests: state.totalFailedRequests,
2024
+ proxyFailedRequests: state.proxyFailedRequests,
2025
+ directFailedRequests: state.directFailedRequests,
2026
+ totalCanceledRequests: state.totalCanceledRequests,
2027
+ proxyCanceledRequests: state.proxyCanceledRequests,
2028
+ directCanceledRequests: state.directCanceledRequests,
2029
+ orphanDataReceivedBytes: state.orphanDataReceivedBytes,
2030
+ orphanProxyDataReceivedBytes: state.orphanProxyDataReceivedBytes,
2031
+ orphanFinishDeltaBytes: state.orphanFinishDeltaBytes,
2032
+ orphanProxyFinishDeltaBytes: state.orphanProxyFinishDeltaBytes,
2033
+ openRequestCount: requestMap.size,
2034
+ orphanOpenCount: orphanReceivedMap.size,
2035
+ topDomains: buildTopDomains(state),
2036
+ topResourceTypes: buildTopResourceTypes(state),
2037
+ failedReasons: buildFailedReasons(state),
2038
+ proxyBypassHints: buildProxyByPassHints(state)
1579
2039
  };
1580
2040
  };
1581
2041
  const reset = () => {
1582
2042
  Object.assign(state, createTrafficState());
1583
2043
  requestMap.clear();
2044
+ orphanReceivedMap.clear();
1584
2045
  wsRouteMap.clear();
1585
2046
  };
1586
2047
  return {
@@ -1630,6 +2091,7 @@ var Launch = {
1630
2091
  proxyConfiguration = {},
1631
2092
  log: logOptions = null,
1632
2093
  runInHeadfulMode = false,
2094
+ debugMode = false,
1633
2095
  isRunningOnApify = false,
1634
2096
  launcher = null,
1635
2097
  preNavigationHooks = [],
@@ -1639,7 +2101,8 @@ var Launch = {
1639
2101
  const byPassRules = buildByPassDomainRules(byPassDomains);
1640
2102
  const trafficMeter2 = createTrafficMeter({
1641
2103
  enableProxy: Boolean(launchProxy),
1642
- byPassRules
2104
+ byPassRules,
2105
+ debugMode: Boolean(debugMode)
1643
2106
  });
1644
2107
  setTrafficMeter(trafficMeter2);
1645
2108
  const launchOptions = {
@@ -1657,14 +2120,19 @@ var Launch = {
1657
2120
  logger7.info(
1658
2121
  `[\u4EE3\u7406\u5DF2\u542F\u7528] \u4EE3\u7406\u670D\u52A1=${launchProxy.server} \u76F4\u8FDE\u57DF\u540D=${(byPassDomains || []).join(",")}`
1659
2122
  );
2123
+ logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
1660
2124
  } else if (enableByPassLogger && enableProxy && !launchProxy) {
1661
2125
  logger7.info(
1662
2126
  `[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A`
1663
2127
  );
2128
+ logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
1664
2129
  } else if (enableByPassLogger && !enableProxy && proxyUrl) {
1665
2130
  logger7.info(
1666
2131
  `[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E`
1667
2132
  );
2133
+ logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
2134
+ } else if (enableByPassLogger) {
2135
+ logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
1668
2136
  }
1669
2137
  const onPageCreated = (page) => {
1670
2138
  const recommendedGotoOptions = {