@skrillex1224/playwright-toolkit 2.1.164 → 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,77 @@ 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
+ });
1463
1552
  var formatBytes = (bytes = 0) => {
1464
1553
  const value = toSafeNumber(bytes);
1465
1554
  if (value <= 0) return "0B";
@@ -1506,6 +1595,190 @@ var addDownloadBytes = (state, route, bytes = 0) => {
1506
1595
  }
1507
1596
  state.directDownloadBytes += b;
1508
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
+ };
1509
1782
  var createTrafficMeter = ({
1510
1783
  enableProxy = false,
1511
1784
  byPassRules = [],
@@ -1513,7 +1786,10 @@ var createTrafficMeter = ({
1513
1786
  } = {}) => {
1514
1787
  const state = createTrafficState();
1515
1788
  const requestMap = /* @__PURE__ */ new Map();
1789
+ const orphanReceivedMap = /* @__PURE__ */ new Map();
1516
1790
  const wsRouteMap = /* @__PURE__ */ new Map();
1791
+ const attachedPages = /* @__PURE__ */ new WeakSet();
1792
+ const attachedContexts = /* @__PURE__ */ new WeakSet();
1517
1793
  const resolveRoute = (url = "") => {
1518
1794
  return resolveRouteByProxy({
1519
1795
  requestUrl: url,
@@ -1521,62 +1797,169 @@ var createTrafficMeter = ({
1521
1797
  byPassRules
1522
1798
  }).route;
1523
1799
  };
1800
+ const fallbackRoute = () => enableProxy ? "proxy" : "direct";
1524
1801
  const debugLog = (message) => {
1525
1802
  if (!debugMode) return;
1526
1803
  logger6.info(`[\u9010\u8BF7\u6C42\u8C03\u8BD5] ${message}`);
1527
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
+ };
1528
1860
  const recordRequest = (params = {}) => {
1529
1861
  const requestId = String(params.requestId || "");
1530
1862
  const request = params.request && typeof params.request === "object" ? params.request : {};
1531
1863
  const url = String(request.url || "");
1532
1864
  const route = resolveRoute(url);
1533
- const resourceType = String(params.type || request.type || "other").trim().toLowerCase() || "other";
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
+ }
1534
1875
  addRequests(state, route, 1);
1535
1876
  const uploadBytes = estimateRequestBytes(request);
1536
1877
  addUploadBytes(state, route, uploadBytes);
1878
+ addRequestProfile(state, route, domain, resourceType, uploadBytes);
1537
1879
  if (requestId) {
1538
1880
  requestMap.set(requestId, {
1539
- route,
1881
+ route: ensureRoute(route),
1540
1882
  resourceType,
1883
+ domain,
1541
1884
  url,
1542
- uploadBytes
1885
+ uploadBytes,
1886
+ downloadBytes: 0
1543
1887
  });
1544
1888
  }
1545
1889
  debugLog(
1546
1890
  `request id=${requestId || "-"} route=${route} type=${resourceType} upload=${formatBytes(uploadBytes)} (${uploadBytes}) method=${String(request.method || "GET")} url=${url || "-"}`
1547
1891
  );
1548
1892
  };
1549
- const recordLoadingFinished = (params = {}) => {
1893
+ const recordDataReceived = (params = {}) => {
1550
1894
  const requestId = String(params.requestId || "");
1551
- const requestState = requestId ? requestMap.get(requestId) : null;
1552
- const route = requestState?.route || "direct";
1553
- const resourceType = requestState?.resourceType || "other";
1554
- const downloadBytes = toSafeNumber(params.encodedDataLength);
1555
- addDownloadBytes(state, route, downloadBytes);
1556
- const uploadBytes = toSafeNumber(requestState?.uploadBytes);
1557
- const totalBytes = uploadBytes + downloadBytes;
1558
- debugLog(
1559
- `finish id=${requestId || "-"} route=${route} type=${resourceType} download=${formatBytes(downloadBytes)} (${downloadBytes}) total=${formatBytes(totalBytes)} (${totalBytes}) url=${requestState?.url || "-"}`
1560
- );
1561
- if (requestId) {
1562
- 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;
1563
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;
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");
1564
1926
  };
1565
1927
  const recordRequestFailed = (params = {}) => {
1566
1928
  const requestId = String(params.requestId || "");
1567
1929
  const requestState = requestId ? requestMap.get(requestId) : null;
1568
- debugLog(
1569
- `failed id=${requestId || "-"} route=${requestState?.route || "direct"} type=${requestState?.resourceType || "other"} reason=${String(params.errorText || "").trim() || "unknown"} canceled=${Boolean(params.canceled)} url=${requestState?.url || "-"}`
1570
- );
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
+ }
1571
1952
  if (requestId) {
1572
1953
  requestMap.delete(requestId);
1954
+ orphanReceivedMap.delete(requestId);
1573
1955
  }
1574
1956
  };
1575
1957
  const bindWebSocketRoute = (params = {}) => {
1576
1958
  const requestId = String(params.requestId || "");
1577
1959
  if (!requestId) return;
1578
- const route = resolveRoute(params.url || "");
1579
- wsRouteMap.set(requestId, { route, url: String(params.url || "") });
1960
+ const url = String(params.url || "");
1961
+ const route = resolveRoute(url);
1962
+ wsRouteMap.set(requestId, { route, url, domain: normalizeDomainKey(parseHostname(url)) });
1580
1963
  };
1581
1964
  const clearWebSocketRoute = (params = {}) => {
1582
1965
  const requestId = String(params.requestId || "");
@@ -1584,48 +1967,64 @@ var createTrafficMeter = ({
1584
1967
  wsRouteMap.delete(requestId);
1585
1968
  };
1586
1969
  const getWebSocketMeta = (requestId = "") => {
1587
- if (!requestId) return { route: "direct", url: "" };
1970
+ if (!requestId) return { route: fallbackRoute(), url: "", domain: UNKNOWN_DOMAIN };
1588
1971
  const meta = wsRouteMap.get(requestId);
1589
1972
  if (!meta || typeof meta !== "object") {
1590
- return { route: "direct", url: "" };
1973
+ return { route: fallbackRoute(), url: "", domain: UNKNOWN_DOMAIN };
1591
1974
  }
1592
1975
  return {
1593
1976
  route: ensureRoute(meta.route),
1594
- url: String(meta.url || "")
1977
+ url: String(meta.url || ""),
1978
+ domain: normalizeDomainKey(meta.domain)
1595
1979
  };
1596
1980
  };
1597
1981
  const recordWebSocketFrameSent = (params = {}) => {
1598
1982
  const requestId = String(params.requestId || "");
1599
- const { route, url } = getWebSocketMeta(requestId);
1983
+ const { route, url, domain } = getWebSocketMeta(requestId);
1600
1984
  const payload = params.response && typeof params.response === "object" ? params.response.payloadData : "";
1601
1985
  const bytes = byteLength(payload || "");
1602
1986
  addUploadBytes(state, route, bytes);
1987
+ addUploadProfile(state, route, domain, "websocket", bytes);
1603
1988
  debugLog(`ws-send id=${requestId || "-"} route=${route} bytes=${formatBytes(bytes)} (${bytes}) url=${url || "-"}`);
1604
1989
  };
1605
1990
  const recordWebSocketFrameReceived = (params = {}) => {
1606
1991
  const requestId = String(params.requestId || "");
1607
- const { route, url } = getWebSocketMeta(requestId);
1992
+ const { route, url, domain } = getWebSocketMeta(requestId);
1608
1993
  const payload = params.response && typeof params.response === "object" ? params.response.payloadData : "";
1609
1994
  const bytes = byteLength(payload || "");
1610
1995
  addDownloadBytes(state, route, bytes);
1996
+ addDownloadProfile(state, route, domain, "websocket", bytes);
1611
1997
  debugLog(`ws-recv id=${requestId || "-"} route=${route} bytes=${formatBytes(bytes)} (${bytes}) url=${url || "-"}`);
1612
1998
  };
1613
1999
  const attachPage = async (page) => {
1614
2000
  if (!page || typeof page.context !== "function") return;
2001
+ if (attachedPages.has(page)) return;
2002
+ attachedPages.add(page);
1615
2003
  try {
1616
2004
  const context = page.context();
1617
2005
  if (!context || typeof context.newCDPSession !== "function") {
1618
2006
  return;
1619
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
+ }
1620
2017
  const session = await context.newCDPSession(page);
1621
2018
  await session.send("Network.enable");
1622
2019
  session.on("Network.requestWillBeSent", recordRequest);
2020
+ session.on("Network.dataReceived", recordDataReceived);
1623
2021
  session.on("Network.loadingFinished", recordLoadingFinished);
1624
2022
  session.on("Network.loadingFailed", recordRequestFailed);
1625
2023
  session.on("Network.webSocketCreated", bindWebSocketRoute);
1626
2024
  session.on("Network.webSocketClosed", clearWebSocketRoute);
1627
2025
  session.on("Network.webSocketFrameSent", recordWebSocketFrameSent);
1628
2026
  session.on("Network.webSocketFrameReceived", recordWebSocketFrameReceived);
2027
+ debugLog("CDP \u76D1\u542C\u5DF2\u6CE8\u518C");
1629
2028
  } catch (error) {
1630
2029
  logger6.warn(`CDP \u76D1\u542C\u6CE8\u518C\u5931\u8D25: ${error?.message || error}`);
1631
2030
  }
@@ -1635,7 +2034,7 @@ var createTrafficMeter = ({
1635
2034
  const proxyBytes = state.proxyUploadBytes + state.proxyDownloadBytes;
1636
2035
  const directBytes = state.directUploadBytes + state.directDownloadBytes;
1637
2036
  return {
1638
- meter: "cdp",
2037
+ meter: "cdp-data-received-v3",
1639
2038
  totalRequests: state.totalRequests,
1640
2039
  proxyRequests: state.proxyRequests,
1641
2040
  directRequests: state.directRequests,
@@ -1647,12 +2046,29 @@ var createTrafficMeter = ({
1647
2046
  directDownloadBytes: state.directDownloadBytes,
1648
2047
  totalBytes,
1649
2048
  proxyBytes,
1650
- 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)
1651
2066
  };
1652
2067
  };
1653
2068
  const reset = () => {
1654
2069
  Object.assign(state, createTrafficState());
1655
2070
  requestMap.clear();
2071
+ orphanReceivedMap.clear();
1656
2072
  wsRouteMap.clear();
1657
2073
  };
1658
2074
  return {