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