@skrillex1224/playwright-toolkit 2.1.134 → 2.1.135

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/README.md CHANGED
@@ -182,12 +182,6 @@ Captcha.useCaptchaMonitor(page, {
182
182
  urlPattern: '/captcha',
183
183
  onDetected: async () => { await Actor.fail('检测到验证码'); }
184
184
  });
185
-
186
- // Title 监控模式
187
- Captcha.useCaptchaMonitor(page, {
188
- titlePattern: /human verification|access denied/i,
189
- onDetected: async () => { await Actor.fail('检测到风控标题'); }
190
- });
191
185
  ```
192
186
 
193
187
  ### Constants
package/dist/index.cjs CHANGED
@@ -1345,19 +1345,15 @@ var LiveView = {
1345
1345
  var import_uuid = require("uuid");
1346
1346
  var logger7 = createInternalLogger("Captcha");
1347
1347
  function useCaptchaMonitor(page, options) {
1348
- const { domSelector, urlPattern, titlePattern, onDetected } = options;
1349
- if (!domSelector && !urlPattern && !titlePattern) {
1350
- throw new Error("[CaptchaMonitor] \u5FC5\u987B\u63D0\u4F9B domSelector / urlPattern / titlePattern \u81F3\u5C11\u4E00\u4E2A");
1348
+ const { domSelector, urlPattern, onDetected } = options;
1349
+ if (!domSelector && !urlPattern) {
1350
+ throw new Error("[CaptchaMonitor] \u5FC5\u987B\u63D0\u4F9B domSelector \u6216 urlPattern \u81F3\u5C11\u4E00\u4E2A");
1351
1351
  }
1352
1352
  if (!onDetected || typeof onDetected !== "function") {
1353
1353
  throw new Error("[CaptchaMonitor] onDetected \u5FC5\u987B\u662F\u4E00\u4E2A\u51FD\u6570");
1354
1354
  }
1355
- if (titlePattern && typeof titlePattern !== "string" && !(titlePattern instanceof RegExp)) {
1356
- throw new Error("[CaptchaMonitor] titlePattern \u5FC5\u987B\u662F string \u6216 RegExp");
1357
- }
1358
1355
  let isHandled = false;
1359
1356
  let frameHandler = null;
1360
- let titleFrameHandler = null;
1361
1357
  let exposedFunctionName = null;
1362
1358
  const triggerDetected = async () => {
1363
1359
  if (isHandled) return;
@@ -1447,51 +1443,6 @@ function useCaptchaMonitor(page, options) {
1447
1443
  page.off("framenavigated", frameHandler);
1448
1444
  });
1449
1445
  }
1450
- if (titlePattern) {
1451
- let titleTimer = null;
1452
- let lastTitle = null;
1453
- const matchTitle = (title) => {
1454
- if (!titlePattern) return false;
1455
- const normalized = String(title || "");
1456
- if (titlePattern instanceof RegExp) {
1457
- titlePattern.lastIndex = 0;
1458
- return titlePattern.test(normalized);
1459
- }
1460
- return normalized.includes(String(titlePattern));
1461
- };
1462
- const checkCurrentTitle = async (force = false) => {
1463
- try {
1464
- const currentTitle = await page.title();
1465
- if (!force && currentTitle === lastTitle) return;
1466
- lastTitle = currentTitle;
1467
- if (!matchTitle(currentTitle)) return;
1468
- await triggerDetected();
1469
- } catch {
1470
- }
1471
- };
1472
- titleFrameHandler = async (frame) => {
1473
- if (frame !== page.mainFrame()) return;
1474
- await checkCurrentTitle();
1475
- };
1476
- page.on("framenavigated", titleFrameHandler);
1477
- checkCurrentTitle(true).catch(() => {
1478
- });
1479
- titleTimer = setInterval(() => {
1480
- if (isHandled) return;
1481
- checkCurrentTitle(false).catch(() => {
1482
- });
1483
- }, 1e3);
1484
- logger7.success("useCaptchaMonitor", `Title \u76D1\u63A7\u5DF2\u542F\u7528: ${String(titlePattern)}`);
1485
- cleanupFns.push(async () => {
1486
- if (titleTimer) {
1487
- clearInterval(titleTimer);
1488
- titleTimer = null;
1489
- }
1490
- if (titleFrameHandler) {
1491
- page.off("framenavigated", titleFrameHandler);
1492
- }
1493
- });
1494
- }
1495
1446
  return {
1496
1447
  stop: async () => {
1497
1448
  logger7.info("useCaptchaMonitor", "\u6B63\u5728\u505C\u6B62\u76D1\u63A7...");
@@ -1756,12 +1707,6 @@ var SHARED_HTTPS_AGENT = new import_https2.Agent({
1756
1707
  maxFreeSockets: 10,
1757
1708
  rejectUnauthorized: false
1758
1709
  });
1759
- var DirectConfig = {
1760
- /** 直连请求超时时间(秒) */
1761
- directTimeout: 12,
1762
- /** 静默扩展名:这些扩展名的直连成功日志用 debug 级别 */
1763
- silentExtensions: [".js"]
1764
- };
1765
1710
  var ARCHIVE_EXTENSIONS = [".7z", ".zip", ".rar", ".gz", ".bz2", ".tar", ".zst"];
1766
1711
  var EXECUTABLE_EXTENSIONS = [".exe", ".apk", ".bin", ".dmg", ".jar", ".class"];
1767
1712
  var DOCUMENT_EXTENSIONS = [".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".csv"];
@@ -1787,7 +1732,14 @@ var IMAGE_EXTENSIONS = [
1787
1732
  var MEDIA_EXTENSIONS = [".mp3", ".mp4", ".avi", ".mkv", ".webm", ".midi", ".mid", ".ogg", ".flac", ".swf"];
1788
1733
  var FONT_EXTENSIONS = [".woff", ".woff2", ".ttf", ".otf"];
1789
1734
  var CSS_EXTENSIONS = [".css"];
1735
+ var JS_EXTENSIONS = [".js"];
1790
1736
  var OTHER_EXTENSIONS = [".ps", ".iso"];
1737
+ var DirectConfig = {
1738
+ /** 直连请求超时时间(秒) */
1739
+ directTimeout: 12,
1740
+ /** 静默扩展名:这些扩展名的直连成功日志用 debug 级别 */
1741
+ silentExtensions: JS_EXTENSIONS
1742
+ };
1791
1743
  var DEFAULT_BLOCKING_CONFIG = {
1792
1744
  /** 屏蔽压缩包 */
1793
1745
  blockArchive: true,
@@ -1804,9 +1756,29 @@ var DEFAULT_BLOCKING_CONFIG = {
1804
1756
  /** 屏蔽 CSS (注意:可能影响页面视觉效果) */
1805
1757
  blockCss: false,
1806
1758
  /** 屏蔽其他资源 */
1807
- blockOther: true,
1808
- /** 额外自定义扩展名列表 */
1809
- customExtensions: []
1759
+ blockOther: true
1760
+ };
1761
+ var DEFAULT_DIRECT_CONFIG = {
1762
+ /** 直连域名 */
1763
+ domains: [],
1764
+ /** 直连压缩包 */
1765
+ directAllArchive: false,
1766
+ /** 直连可执行文件 */
1767
+ directAllExecutable: false,
1768
+ /** 直连办公文档 */
1769
+ directAllDocument: false,
1770
+ /** 直连图片 */
1771
+ directAllImage: false,
1772
+ /** 直连音视频 */
1773
+ directAllMedia: false,
1774
+ /** 直连字体 */
1775
+ directAllFont: false,
1776
+ /** 直连 CSS */
1777
+ directAllCss: false,
1778
+ /** 直连 JS */
1779
+ directAllJs: false,
1780
+ /** 直连其他资源 */
1781
+ directAllOther: false
1810
1782
  };
1811
1783
  var SHARED_GOT_OPTIONS = {
1812
1784
  http2: false,
@@ -1834,9 +1806,6 @@ var Interception = {
1834
1806
  if (mergedConfig.blockFont) extensions.push(...FONT_EXTENSIONS);
1835
1807
  if (mergedConfig.blockCss) extensions.push(...CSS_EXTENSIONS);
1836
1808
  if (mergedConfig.blockOther) extensions.push(...OTHER_EXTENSIONS);
1837
- if (mergedConfig.customExtensions?.length > 0) {
1838
- extensions.push(...mergedConfig.customExtensions);
1839
- }
1840
1809
  return [...new Set(extensions)];
1841
1810
  },
1842
1811
  /**
@@ -1856,12 +1825,34 @@ var Interception = {
1856
1825
  other: { name: "\u5176\u4ED6\u8D44\u6E90", extensions: OTHER_EXTENSIONS }
1857
1826
  };
1858
1827
  },
1828
+ /**
1829
+ * 根据配置生成需要直连的扩展名列表
1830
+ *
1831
+ * @param {Object} [config] - 直连扩展名配置
1832
+ * @returns {string[]} 需要直连的扩展名列表
1833
+ */
1834
+ getDirectExtensions(config = {}) {
1835
+ const mergedConfig = { ...DEFAULT_DIRECT_CONFIG, ...config };
1836
+ const extensions = [];
1837
+ if (mergedConfig.directAllArchive) extensions.push(...ARCHIVE_EXTENSIONS);
1838
+ if (mergedConfig.directAllExecutable) extensions.push(...EXECUTABLE_EXTENSIONS);
1839
+ if (mergedConfig.directAllDocument) extensions.push(...DOCUMENT_EXTENSIONS);
1840
+ if (mergedConfig.directAllImage) extensions.push(...IMAGE_EXTENSIONS);
1841
+ if (mergedConfig.directAllMedia) extensions.push(...MEDIA_EXTENSIONS);
1842
+ if (mergedConfig.directAllFont) extensions.push(...FONT_EXTENSIONS);
1843
+ if (mergedConfig.directAllCss) extensions.push(...CSS_EXTENSIONS);
1844
+ if (mergedConfig.directAllJs) extensions.push(...JS_EXTENSIONS);
1845
+ if (mergedConfig.directAllOther) extensions.push(...OTHER_EXTENSIONS);
1846
+ return [...new Set(extensions)];
1847
+ },
1859
1848
  /**
1860
1849
  * 设置网络拦截规则(资源屏蔽 + CDN 直连)
1861
1850
  *
1862
1851
  * @param {import('playwright').Page} page - Playwright Page 对象
1863
1852
  * @param {Object} [options] - 配置选项
1864
- * @param {string[]} [options.directDomains] - 需要直连的域名列表
1853
+ * @param {string[]} [options.directDomains] - [已过时] 需要直连的域名列表,请改用 directConfig.domains
1854
+ * @param {string[]} [options.directExtensions] - 需要直连的扩展名(如 .js/.css)
1855
+ * @param {Object} [options.directConfig] - 直连配置(参考 blockingConfig),支持 domains/directAllXxx 系列开关
1865
1856
  * @param {Object} [options.blockingConfig] - 资源屏蔽配置
1866
1857
  * @param {boolean} [options.fallbackToProxy] - 直连失败时是否回退到代理(默认 true)
1867
1858
  * @returns {Promise<void>}
@@ -1869,12 +1860,27 @@ var Interception = {
1869
1860
  async setup(page, options = {}) {
1870
1861
  const {
1871
1862
  directDomains = [],
1863
+ directExtensions = [],
1864
+ directConfig = {},
1872
1865
  blockingConfig = {},
1873
1866
  fallbackToProxy = true
1874
1867
  } = options;
1875
1868
  const mergedBlockingConfig = { ...DEFAULT_BLOCKING_CONFIG, ...blockingConfig };
1869
+ const mergedDirectConfig = { ...DEFAULT_DIRECT_CONFIG, ...directConfig };
1876
1870
  const blockedExtensions = this.getBlockedExtensions(mergedBlockingConfig);
1877
- const hasDirectDomains = directDomains.length > 0;
1871
+ const configuredDirectExtensions = this.getDirectExtensions(mergedDirectConfig);
1872
+ const normalizedDirectDomains = Array.from(
1873
+ new Set(
1874
+ [...directDomains, ...mergedDirectConfig.domains || []].map((domain) => String(domain || "").toLowerCase().trim()).filter(Boolean)
1875
+ )
1876
+ );
1877
+ const hasDirectDomains = normalizedDirectDomains.length > 0;
1878
+ const normalizedDirectExtensions = Array.from(
1879
+ new Set(
1880
+ [...directExtensions, ...configuredDirectExtensions].map((ext) => String(ext || "").toLowerCase().trim()).filter(Boolean).map((ext) => ext.startsWith(".") ? ext : `.${ext}`)
1881
+ )
1882
+ );
1883
+ const hasDirectExtensions = normalizedDirectExtensions.length > 0;
1878
1884
  const enabledCategories = [];
1879
1885
  if (mergedBlockingConfig.blockArchive) enabledCategories.push("\u538B\u7F29\u5305");
1880
1886
  if (mergedBlockingConfig.blockExecutable) enabledCategories.push("\u53EF\u6267\u884C\u6587\u4EF6");
@@ -1884,7 +1890,10 @@ var Interception = {
1884
1890
  if (mergedBlockingConfig.blockFont) enabledCategories.push("\u5B57\u4F53");
1885
1891
  if (mergedBlockingConfig.blockCss) enabledCategories.push("CSS");
1886
1892
  if (mergedBlockingConfig.blockOther) enabledCategories.push("\u5176\u4ED6");
1887
- logger9.start("setup", hasDirectDomains ? `\u76F4\u8FDE\u57DF\u540D: [${directDomains.length} \u4E2A] | \u5C4F\u853D: [${enabledCategories.join(", ")}]` : `\u4EC5\u8D44\u6E90\u5C4F\u853D\u6A21\u5F0F | \u5C4F\u853D: [${enabledCategories.join(", ")}]`);
1893
+ const directRules = [];
1894
+ if (hasDirectDomains) directRules.push(`\u76F4\u8FDE\u57DF\u540D: [${normalizedDirectDomains.length} \u4E2A]`);
1895
+ if (hasDirectExtensions) directRules.push(`\u76F4\u8FDE\u6269\u5C55\u540D: [${normalizedDirectExtensions.join(", ")}]`);
1896
+ logger9.start("setup", directRules.length > 0 ? `${directRules.join(" | ")} | \u5C4F\u853D: [${enabledCategories.join(", ")}]` : `\u4EC5\u8D44\u6E90\u5C4F\u853D\u6A21\u5F0F | \u5C4F\u853D: [${enabledCategories.join(", ")}]`);
1888
1897
  await page.route("**/*", async (route) => {
1889
1898
  let handled = false;
1890
1899
  try {
@@ -1899,14 +1908,16 @@ var Interception = {
1899
1908
  handled = true;
1900
1909
  return;
1901
1910
  }
1902
- let isDirect = false;
1911
+ let isDirectByDomain = false;
1903
1912
  if (hasDirectDomains) {
1904
1913
  try {
1905
1914
  const hostname = new URL(url).hostname;
1906
- isDirect = directDomains.some((domain) => hostname.startsWith(domain));
1915
+ isDirectByDomain = normalizedDirectDomains.some((domain) => hostname.startsWith(domain));
1907
1916
  } catch (e) {
1908
1917
  }
1909
1918
  }
1919
+ const isDirectByExtension = hasDirectExtensions ? normalizedDirectExtensions.some((ext) => urlPath.endsWith(ext)) : false;
1920
+ const isDirect = isDirectByDomain || isDirectByExtension;
1910
1921
  if (isDirect) {
1911
1922
  try {
1912
1923
  const reqHeaders = await request.allHeaders();