@lmcc-dev/mult-fetch-mcp-server 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/README.zh.md +16 -8
- package/dist/i18n-test-report.json +8 -0
- package/dist/i18n-unused-keys-report.json +8 -0
- package/dist/src/client.js +50 -35
- package/dist/src/index.js +0 -0
- package/dist/src/lib/BrowserFetcher.js +12 -12
- package/dist/src/lib/NodeFetcher.js +22 -22
- package/dist/src/lib/fetch.js +40 -0
- package/dist/src/lib/fetchers/FetcherFactory.js +43 -0
- package/dist/src/lib/fetchers/browser/BrowserFetcher.js +381 -0
- package/dist/src/lib/fetchers/browser/BrowserInstance.js +180 -0
- package/dist/src/lib/fetchers/browser/PageOperations.js +168 -0
- package/dist/src/lib/fetchers/common/types.js +41 -0
- package/dist/src/lib/fetchers/common/utils.js +141 -0
- package/dist/src/lib/fetchers/index.js +62 -0
- package/dist/src/lib/fetchers/node/HttpClient.js +239 -0
- package/dist/src/lib/fetchers/node/NodeFetcher.js +180 -0
- package/dist/src/lib/i18n/index.js +105 -17
- package/dist/src/lib/i18n/keys/base.js +41 -0
- package/dist/src/lib/i18n/keys/browser.js +91 -0
- package/dist/src/lib/i18n/keys/client.js +68 -0
- package/dist/src/lib/i18n/keys/errors.js +49 -0
- package/dist/src/lib/i18n/keys/factory.js +46 -0
- package/dist/src/lib/i18n/keys/fetcher.js +87 -0
- package/dist/src/lib/i18n/keys/index.js +16 -0
- package/dist/src/lib/i18n/keys/node.js +69 -0
- package/dist/src/lib/i18n/keys/prompts.js +79 -0
- package/dist/src/lib/i18n/keys/resources.js +53 -0
- package/dist/src/lib/i18n/keys/server.js +67 -0
- package/dist/src/lib/i18n/keys/tools.js +39 -0
- package/dist/src/lib/i18n/keys.js +488 -119
- package/dist/src/lib/i18n/locales/en/browser.js +67 -57
- package/dist/src/lib/i18n/locales/en/client.js +57 -33
- package/dist/src/lib/i18n/locales/en/errors.js +36 -12
- package/dist/src/lib/i18n/locales/en/factory.js +38 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +8 -2
- package/dist/src/lib/i18n/locales/en/index.js +10 -8
- package/dist/src/lib/i18n/locales/en/node.js +57 -35
- package/dist/src/lib/i18n/locales/en/prompts.js +42 -65
- package/dist/src/lib/i18n/locales/en/resources.js +52 -55
- package/dist/src/lib/i18n/locales/en/server.js +49 -24
- package/dist/src/lib/i18n/locales/en/tools.js +27 -19
- package/dist/src/lib/i18n/locales/zh/browser.js +71 -62
- package/dist/src/lib/i18n/locales/zh/client.js +57 -32
- package/dist/src/lib/i18n/locales/zh/errors.js +36 -12
- package/dist/src/lib/i18n/locales/zh/factory.js +38 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +36 -30
- package/dist/src/lib/i18n/locales/zh/index.js +10 -8
- package/dist/src/lib/i18n/locales/zh/node.js +61 -36
- package/dist/src/lib/i18n/locales/zh/prompts.js +35 -57
- package/dist/src/lib/i18n/locales/zh/resources.js +52 -55
- package/dist/src/lib/i18n/locales/zh/server.js +51 -25
- package/dist/src/lib/i18n/locales/zh/tools.js +27 -19
- package/dist/src/lib/i18n/logger.js +90 -19
- package/dist/src/lib/logger.js +135 -14
- package/dist/src/lib/server/browser.js +2 -2
- package/dist/src/lib/server/fetcher.js +13 -34
- package/dist/src/lib/server/index.js +2 -2
- package/dist/src/lib/server/prompts.js +1 -1
- package/dist/src/lib/server/resources.js +1 -1
- package/dist/src/lib/server/tools.js +46 -10
- package/dist/src/lib/utils/ErrorHandler.js +125 -0
- package/dist/src/mcp-server.js +0 -0
- package/dist/src/test-i18n.js +139 -0
- package/dist/tests/BrowserFetcher.test.js +0 -0
- package/dist/tests/NodeFetcher.test.js +0 -0
- package/dist/tests/client.test.js +42 -81
- package/dist/tests/fetch.test.js +70 -0
- package/dist/tests/fetchers/Fetcher.test.js +108 -0
- package/dist/tests/fetchers/browser/BrowserFetcher.test.js +195 -0
- package/dist/tests/fetchers/browser/BrowserInstance.test.js +163 -0
- package/dist/tests/fetchers/node/NodeFetcher.test.js +211 -0
- package/dist/tests/i18n-missing-keys.js +364 -0
- package/dist/tests/i18n-remove-unused-keys.js +236 -0
- package/dist/tests/i18n-test-report.json +2004 -0
- package/dist/tests/i18n-unused-keys.js +425 -0
- package/dist/tests/i18n.test.js +6 -7
- package/dist/tests/logger.test.js +14 -13
- package/dist/tests/mcp-server.test.js +7 -7
- package/dist/tests/server/browser.test.js +25 -21
- package/dist/tests/server/fetcher.test.js +58 -101
- package/dist/tests/server/index.test.js +30 -30
- package/dist/tests/server/prompts.test.js +36 -33
- package/dist/tests/server/resources.test.js +120 -84
- package/dist/tests/server/tools.test.js +7 -6
- package/dist/tests/setup.js +36 -0
- package/dist/tests/src/lib/i18n/index.js +108 -0
- package/dist/tests/src/lib/i18n/keys/base.js +47 -0
- package/dist/tests/src/lib/i18n/keys/browser.js +93 -0
- package/dist/tests/src/lib/i18n/keys/client.js +70 -0
- package/dist/tests/src/lib/i18n/keys/errors.js +34 -0
- package/dist/tests/src/lib/i18n/keys/fetcher.js +84 -0
- package/dist/tests/src/lib/i18n/keys/index.js +31 -0
- package/dist/tests/src/lib/i18n/keys/node.js +56 -0
- package/dist/tests/src/lib/i18n/keys/prompts.js +82 -0
- package/dist/tests/src/lib/i18n/keys/resources.js +50 -0
- package/dist/tests/src/lib/i18n/keys/server.js +64 -0
- package/dist/tests/src/lib/i18n/keys/tools.js +34 -0
- package/dist/tests/src/lib/i18n/locales/en/browser.js +88 -0
- package/dist/tests/src/lib/i18n/locales/en/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/en/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/en/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/en/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/en/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/en/prompts.js +52 -0
- package/dist/tests/src/lib/i18n/locales/en/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/en/server.js +56 -0
- package/dist/tests/src/lib/i18n/locales/en/tools.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/browser.js +87 -0
- package/dist/tests/src/lib/i18n/locales/zh/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/zh/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/zh/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/zh/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/zh/prompts.js +53 -0
- package/dist/tests/src/lib/i18n/locales/zh/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/zh/server.js +57 -0
- package/dist/tests/src/lib/i18n/locales/zh/tools.js +28 -0
- package/dist/tests/src/lib/i18n/logger.js +114 -0
- package/dist/tests/src/lib/logger.js +181 -0
- package/dist/tests/test-direct-client.js +0 -0
- package/dist/tests/test-i18n.js +530 -0
- package/dist/tests/test-mcp-methods.js +1 -1
- package/dist/tests/test-mcp.js +0 -0
- package/dist/tests/test-mini4k.js +0 -0
- package/dist/tests/tests/test-i18n.js +588 -0
- package/dist/tests/types.test.js +1 -1
- package/dist/tests/utils/ErrorHandler.test.js +144 -0
- package/dist/vitest.config.js +29 -0
- package/package.json +31 -27
- package/dist/src/cli-language.js +0 -78
- package/dist/src/client.d.ts +0 -6
- package/dist/src/client.js.map +0 -1
- package/dist/src/index.d.ts +0 -15
- package/dist/src/index.js.map +0 -1
- package/dist/src/lib/BrowserFetcher.d.ts +0 -175
- package/dist/src/lib/BrowserFetcher.js.map +0 -1
- package/dist/src/lib/NodeFetcher.d.ts +0 -92
- package/dist/src/lib/NodeFetcher.js.map +0 -1
- package/dist/src/lib/example.js +0 -93
- package/dist/src/lib/i18n/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/browser.d.ts +0 -61
- package/dist/src/lib/i18n/locales/en/browser.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/client.d.ts +0 -39
- package/dist/src/lib/i18n/locales/en/client.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/errors.d.ts +0 -17
- package/dist/src/lib/i18n/locales/en/errors.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/fetcher.d.ts +0 -66
- package/dist/src/lib/i18n/locales/en/fetcher.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/index.d.ts +0 -344
- package/dist/src/lib/i18n/locales/en/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/node.d.ts +0 -41
- package/dist/src/lib/i18n/locales/en/node.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/prompts.d.ts +0 -65
- package/dist/src/lib/i18n/locales/en/prompts.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/resources.d.ts +0 -41
- package/dist/src/lib/i18n/locales/en/resources.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/server.d.ts +0 -30
- package/dist/src/lib/i18n/locales/en/server.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/tools.d.ts +0 -24
- package/dist/src/lib/i18n/locales/en/tools.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/browser.d.ts +0 -61
- package/dist/src/lib/i18n/locales/zh/browser.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/client.d.ts +0 -38
- package/dist/src/lib/i18n/locales/zh/client.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/errors.d.ts +0 -17
- package/dist/src/lib/i18n/locales/zh/errors.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/fetcher.d.ts +0 -66
- package/dist/src/lib/i18n/locales/zh/fetcher.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/index.d.ts +0 -343
- package/dist/src/lib/i18n/locales/zh/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/node.d.ts +0 -41
- package/dist/src/lib/i18n/locales/zh/node.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/prompts.d.ts +0 -65
- package/dist/src/lib/i18n/locales/zh/prompts.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/resources.d.ts +0 -41
- package/dist/src/lib/i18n/locales/zh/resources.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/server.d.ts +0 -30
- package/dist/src/lib/i18n/locales/zh/server.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/tools.d.ts +0 -24
- package/dist/src/lib/i18n/locales/zh/tools.js.map +0 -1
- package/dist/src/lib/i18n/logger.d.ts +0 -38
- package/dist/src/lib/i18n/logger.js.map +0 -1
- package/dist/src/lib/logger.d.ts +0 -32
- package/dist/src/lib/logger.js.map +0 -1
- package/dist/src/lib/server/browser.d.ts +0 -34
- package/dist/src/lib/server/browser.js.map +0 -1
- package/dist/src/lib/server/fetcher.d.ts +0 -20
- package/dist/src/lib/server/fetcher.js.map +0 -1
- package/dist/src/lib/server/index.d.ts +0 -10
- package/dist/src/lib/server/index.js.map +0 -1
- package/dist/src/lib/server/logger.js +0 -23
- package/dist/src/lib/server/prompts.d.ts +0 -11
- package/dist/src/lib/server/prompts.js.map +0 -1
- package/dist/src/lib/server/resources.d.ts +0 -11
- package/dist/src/lib/server/resources.js.map +0 -1
- package/dist/src/lib/server/tools.d.ts +0 -12
- package/dist/src/lib/server/tools.js.map +0 -1
- package/dist/src/lib/server/types.d.ts +0 -71
- package/dist/src/lib/server/types.js.map +0 -1
- package/dist/src/lib/types.d.ts +0 -119
- package/dist/src/lib/types.js.map +0 -1
- package/dist/src/mcp-server.d.ts +0 -6
- package/dist/src/mcp-server.js.map +0 -1
- package/dist/tests/jest.setup.d.ts +0 -6
- package/dist/tests/jest.setup.js +0 -30
- package/dist/tests/jest.setup.js.map +0 -1
- package/dist/tests/test-direct-client.d.ts +0 -6
- package/dist/tests/test-direct-client.js.map +0 -1
- package/dist/tests/test-mcp-methods.d.ts +0 -6
- package/dist/tests/test-mcp-methods.js.map +0 -1
- package/dist/tests/test-mcp.d.ts +0 -6
- package/dist/tests/test-mcp.js.map +0 -1
- package/dist/tests/test-mini4k.d.ts +0 -6
- package/dist/tests/test-mini4k.js.map +0 -1
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
+
*/
|
|
6
|
+
import TurndownService from 'turndown';
|
|
7
|
+
import { JSDOM } from 'jsdom';
|
|
8
|
+
import { log, COMPONENTS } from '../../logger.js';
|
|
9
|
+
import { BrowserInstance } from './BrowserInstance.js';
|
|
10
|
+
import { PageOperations, CookieManager } from './PageOperations.js';
|
|
11
|
+
import { getRandomUserAgent, getSystemProxy } from '../common/utils.js';
|
|
12
|
+
/**
|
|
13
|
+
* 浏览器模式获取器类 (Browser mode fetcher class)
|
|
14
|
+
* 使用Puppeteer实现浏览器模式的网页获取 (Implements webpage fetching in browser mode using Puppeteer)
|
|
15
|
+
*/
|
|
16
|
+
export class BrowserFetcher {
|
|
17
|
+
/**
|
|
18
|
+
* 使用Puppeteer获取网页内容 (Get webpage content using Puppeteer)
|
|
19
|
+
* 浏览器模式获取的底层实现方法 (Low-level implementation method for browser mode fetching)
|
|
20
|
+
* @param params 请求参数 (Request parameters)
|
|
21
|
+
* @returns 获取结果 (Fetch result)
|
|
22
|
+
*/
|
|
23
|
+
static async fetch({ url, headers = {}, proxy, timeout = 30000, waitForSelector = 'body', waitForTimeout = 5000, scrollToBottom = false, saveCookies = true, closeBrowser = false, useSystemProxy = true, debug = false, }) {
|
|
24
|
+
log('browser.fetchRequest', debug, {
|
|
25
|
+
url,
|
|
26
|
+
waitForSelector,
|
|
27
|
+
waitForTimeout,
|
|
28
|
+
scrollToBottom,
|
|
29
|
+
saveCookies,
|
|
30
|
+
closeBrowser,
|
|
31
|
+
useSystemProxy
|
|
32
|
+
}, COMPONENTS.BROWSER_FETCH);
|
|
33
|
+
// 检查内存使用情况
|
|
34
|
+
BrowserInstance.checkMemoryUsage(debug);
|
|
35
|
+
// 获取代理设置 - 优先使用用户指定的代理,其次使用系统代理
|
|
36
|
+
const effectiveProxy = proxy || (useSystemProxy ? getSystemProxy(useSystemProxy, debug, COMPONENTS.BROWSER_FETCH) : undefined);
|
|
37
|
+
// 详细记录代理设置情况
|
|
38
|
+
if (proxy) {
|
|
39
|
+
log('fetcher.usingSpecifiedProxy', debug, { proxy }, COMPONENTS.BROWSER_FETCH);
|
|
40
|
+
}
|
|
41
|
+
else if (useSystemProxy) {
|
|
42
|
+
log('fetcher.attemptingToUseSystemProxy', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
log('fetcher.notUsingProxy', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
46
|
+
}
|
|
47
|
+
log('fetcher.finalProxyUsed', debug, { proxy: effectiveProxy || 'none' }, COMPONENTS.BROWSER_FETCH);
|
|
48
|
+
try {
|
|
49
|
+
// 如果只是要关闭浏览器,不需要获取内容
|
|
50
|
+
if (url === 'about:blank' && closeBrowser) {
|
|
51
|
+
await BrowserInstance.closeBrowser(debug);
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: 'text', text: 'Browser closed successfully' }],
|
|
54
|
+
isError: false
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// 获取浏览器实例
|
|
58
|
+
const browser = await BrowserInstance.getBrowser(debug);
|
|
59
|
+
// 创建新页面
|
|
60
|
+
const page = await browser.newPage();
|
|
61
|
+
// 设置页面超时
|
|
62
|
+
page.setDefaultTimeout(timeout);
|
|
63
|
+
// 设置用户代理
|
|
64
|
+
await page.setUserAgent(getRandomUserAgent());
|
|
65
|
+
// 设置请求头
|
|
66
|
+
await page.setExtraHTTPHeaders(headers);
|
|
67
|
+
// 如果有存储的cookies,使用它们
|
|
68
|
+
const storedCookies = CookieManager.getCookies(url);
|
|
69
|
+
if (storedCookies && saveCookies) {
|
|
70
|
+
log('browser.usingStoredCookies', debug, { domain: url }, COMPONENTS.BROWSER_FETCH);
|
|
71
|
+
await page.setExtraHTTPHeaders({
|
|
72
|
+
'Cookie': storedCookies
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// 如果使用代理,记录日志
|
|
76
|
+
if (effectiveProxy) {
|
|
77
|
+
log('browser.usingProxy', debug, { proxy: effectiveProxy }, COMPONENTS.BROWSER_FETCH);
|
|
78
|
+
}
|
|
79
|
+
// 导航到URL
|
|
80
|
+
log('browser.navigating', debug, { url }, COMPONENTS.BROWSER_FETCH);
|
|
81
|
+
await page.goto(url, {
|
|
82
|
+
waitUntil: 'networkidle2',
|
|
83
|
+
timeout: timeout
|
|
84
|
+
});
|
|
85
|
+
// 尝试处理Cloudflare保护
|
|
86
|
+
const bypassedCloudflare = await PageOperations.handleCloudflareProtection(page, url, debug);
|
|
87
|
+
if (!bypassedCloudflare) {
|
|
88
|
+
log('browser.continuingWithoutBypass', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
89
|
+
}
|
|
90
|
+
// 等待选择器
|
|
91
|
+
log('browser.waitingForSelector', debug, { selector: waitForSelector }, COMPONENTS.BROWSER_FETCH);
|
|
92
|
+
await page.waitForSelector(waitForSelector, {
|
|
93
|
+
timeout: timeout
|
|
94
|
+
}).catch(() => { });
|
|
95
|
+
// 等待额外时间
|
|
96
|
+
log('browser.waitingForTimeout', debug, { timeout: waitForTimeout }, COMPONENTS.BROWSER_FETCH);
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, waitForTimeout));
|
|
98
|
+
// 如果需要滚动到底部
|
|
99
|
+
if (scrollToBottom) {
|
|
100
|
+
log('browser.scrolling', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
101
|
+
await PageOperations.autoScroll(page, debug);
|
|
102
|
+
}
|
|
103
|
+
// 获取页面内容
|
|
104
|
+
log('browser.gettingContent', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
105
|
+
const content = await page.content();
|
|
106
|
+
// 如果需要保存cookies
|
|
107
|
+
if (saveCookies) {
|
|
108
|
+
log('browser.savingCookies', debug, { domain: url }, COMPONENTS.BROWSER_FETCH);
|
|
109
|
+
const cookies = await page.evaluate(() => document.cookie);
|
|
110
|
+
if (cookies) {
|
|
111
|
+
CookieManager.saveCookies(url, cookies);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 记录内容长度
|
|
115
|
+
const contentLength = content.length;
|
|
116
|
+
log('browser.contentLength', debug, { length: contentLength }, COMPONENTS.BROWSER_FETCH);
|
|
117
|
+
// 如果内容太长,截断它
|
|
118
|
+
let finalContent = content;
|
|
119
|
+
if (contentLength > 10 * 1024 * 1024) { // 10MB
|
|
120
|
+
finalContent = content.substring(0, 10 * 1024 * 1024);
|
|
121
|
+
log('browser.contentTruncated', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
122
|
+
}
|
|
123
|
+
// 关闭页面
|
|
124
|
+
await page.close();
|
|
125
|
+
log('browser.pageClosed', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
126
|
+
// 如果需要关闭浏览器
|
|
127
|
+
if (closeBrowser) {
|
|
128
|
+
await BrowserInstance.closeBrowser(debug);
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: 'text', text: finalContent }],
|
|
132
|
+
isError: false
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
log('browser.fetchError', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
|
|
137
|
+
// 如果需要关闭浏览器
|
|
138
|
+
if (closeBrowser) {
|
|
139
|
+
await BrowserInstance.closeBrowser(debug);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: 'text', text: `Error fetching ${url}: ${error}` }],
|
|
143
|
+
isError: true
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 使用Puppeteer获取HTML内容 (Get HTML content using Puppeteer)
|
|
149
|
+
* 通过浏览器模式获取网页HTML (Fetch webpage HTML in browser mode)
|
|
150
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
151
|
+
* @returns 获取结果 (Fetch result)
|
|
152
|
+
*/
|
|
153
|
+
async html(requestPayload) {
|
|
154
|
+
const { url, debug = false, closeBrowser = false } = requestPayload;
|
|
155
|
+
// 如果只是要关闭浏览器,不需要获取内容
|
|
156
|
+
if (url === 'about:blank' && closeBrowser) {
|
|
157
|
+
log('browser.closingInstance', debug, { debug }, COMPONENTS.BROWSER_FETCH);
|
|
158
|
+
await BrowserInstance.closeBrowser(debug);
|
|
159
|
+
return {
|
|
160
|
+
content: [{ type: 'text', text: 'Browser closed successfully' }],
|
|
161
|
+
isError: false
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// 设置最大重试次数
|
|
165
|
+
const maxRetries = 2;
|
|
166
|
+
const fetchWithRetry = async (retryCount = 0) => {
|
|
167
|
+
log('browser.fetchingWithRetry', debug, { attempt: retryCount + 1, maxAttempts: maxRetries, url }, COMPONENTS.BROWSER_FETCH);
|
|
168
|
+
try {
|
|
169
|
+
return await BrowserFetcher.fetch(requestPayload);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (retryCount < maxRetries) {
|
|
173
|
+
// 计算延迟时间,随着重试次数增加而增加
|
|
174
|
+
const delayMs = 1000 * (retryCount + 1);
|
|
175
|
+
log('browser.retryingAfterDelay', debug, { delayMs }, COMPONENTS.BROWSER_FETCH);
|
|
176
|
+
// 等待一段时间后重试
|
|
177
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
178
|
+
return fetchWithRetry(retryCount + 1);
|
|
179
|
+
}
|
|
180
|
+
// 超过最大重试次数,返回错误
|
|
181
|
+
return {
|
|
182
|
+
content: [{ type: 'text', text: `Error fetching ${url} after ${maxRetries + 1} attempts: ${error}` }],
|
|
183
|
+
isError: true
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
try {
|
|
188
|
+
return await fetchWithRetry();
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
// 如果需要关闭浏览器
|
|
192
|
+
if (closeBrowser) {
|
|
193
|
+
log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
194
|
+
await BrowserInstance.closeBrowser(debug);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 使用Puppeteer获取JSON内容 (Get JSON content using Puppeteer)
|
|
200
|
+
* 通过浏览器模式获取JSON数据 (Fetch JSON data in browser mode)
|
|
201
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
202
|
+
* @returns 获取结果 (Fetch result)
|
|
203
|
+
*/
|
|
204
|
+
async json(requestPayload) {
|
|
205
|
+
const { url, debug = false, closeBrowser = false } = requestPayload;
|
|
206
|
+
// 如果只是要关闭浏览器,不需要获取内容
|
|
207
|
+
if (url === 'about:blank' && closeBrowser) {
|
|
208
|
+
log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
209
|
+
await BrowserInstance.closeBrowser(debug);
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: 'text', text: 'Browser closed successfully' }],
|
|
212
|
+
isError: false
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// 设置最大重试次数
|
|
216
|
+
const maxRetries = 2;
|
|
217
|
+
const fetchWithRetry = async (retryCount = 0) => {
|
|
218
|
+
log('browser.fetchingWithRetry', debug, { attempt: retryCount + 1, maxAttempts: maxRetries + 1, url }, COMPONENTS.BROWSER_FETCH);
|
|
219
|
+
try {
|
|
220
|
+
const result = await BrowserFetcher.fetch(requestPayload);
|
|
221
|
+
// 如果获取成功,尝试解析JSON
|
|
222
|
+
if (!result.isError) {
|
|
223
|
+
try {
|
|
224
|
+
const jsonText = result.content[0].text;
|
|
225
|
+
// 尝试解析JSON
|
|
226
|
+
JSON.parse(jsonText);
|
|
227
|
+
// 如果解析成功,直接返回结果
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
// JSON解析失败
|
|
232
|
+
log('browser.failedToParseJSON', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
|
|
233
|
+
return {
|
|
234
|
+
content: [{ type: 'text', text: `Error parsing JSON from ${url}: ${error}` }],
|
|
235
|
+
isError: true
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
log('browser.fetchErrorWithAttempt', debug, { error: String(error), attempt: retryCount + 1, maxAttempts: maxRetries + 1 }, COMPONENTS.BROWSER_FETCH);
|
|
243
|
+
if (retryCount < maxRetries) {
|
|
244
|
+
// 计算延迟时间,随着重试次数增加而增加
|
|
245
|
+
const delayMs = 1000 * (retryCount + 1);
|
|
246
|
+
log('browser.retryingAfterDelay', debug, { delayMs }, COMPONENTS.BROWSER_FETCH);
|
|
247
|
+
// 等待一段时间后重试
|
|
248
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
249
|
+
return fetchWithRetry(retryCount + 1);
|
|
250
|
+
}
|
|
251
|
+
// 超过最大重试次数,返回错误
|
|
252
|
+
return {
|
|
253
|
+
content: [{ type: 'text', text: `Error fetching JSON from ${url} after ${maxRetries + 1} attempts: ${error}` }],
|
|
254
|
+
isError: true
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
try {
|
|
259
|
+
return await fetchWithRetry();
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
// 如果需要关闭浏览器
|
|
263
|
+
if (closeBrowser) {
|
|
264
|
+
log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
265
|
+
await BrowserInstance.closeBrowser(debug);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* 使用Puppeteer获取纯文本内容 (Get plain text content using Puppeteer)
|
|
271
|
+
* 通过浏览器模式获取网页纯文本 (Fetch webpage plain text in browser mode)
|
|
272
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
273
|
+
* @returns 获取结果 (Fetch result)
|
|
274
|
+
*/
|
|
275
|
+
async txt(requestPayload) {
|
|
276
|
+
const { url, debug = false, closeBrowser = false } = requestPayload;
|
|
277
|
+
// 如果只是要关闭浏览器,不需要获取内容
|
|
278
|
+
if (url === 'about:blank' && closeBrowser) {
|
|
279
|
+
log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
280
|
+
await BrowserInstance.closeBrowser(debug);
|
|
281
|
+
return {
|
|
282
|
+
content: [{ type: 'text', text: 'Browser closed successfully' }],
|
|
283
|
+
isError: false
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// 设置最大重试次数
|
|
287
|
+
const maxRetries = 2;
|
|
288
|
+
const fetchWithRetry = async (retryCount = 0) => {
|
|
289
|
+
log('browser.fetchingWithRetry', debug, { attempt: retryCount + 1, maxAttempts: maxRetries + 1, url }, COMPONENTS.BROWSER_FETCH);
|
|
290
|
+
try {
|
|
291
|
+
const result = await BrowserFetcher.fetch(requestPayload);
|
|
292
|
+
// 如果获取成功,提取纯文本
|
|
293
|
+
if (!result.isError) {
|
|
294
|
+
const htmlContent = result.content[0].text;
|
|
295
|
+
// 使用JSDOM提取纯文本
|
|
296
|
+
const dom = new JSDOM(htmlContent);
|
|
297
|
+
const textContent = dom.window.document.body.textContent || '';
|
|
298
|
+
// 清理文本(移除多余空白)
|
|
299
|
+
const cleanedText = textContent
|
|
300
|
+
.replace(/\s+/g, ' ')
|
|
301
|
+
.trim();
|
|
302
|
+
return {
|
|
303
|
+
content: [{ type: 'text', text: cleanedText }],
|
|
304
|
+
isError: false
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
log('browser.fetchErrorWithAttempt', debug, { error: String(error), attempt: retryCount + 1, maxAttempts: maxRetries + 1 }, COMPONENTS.BROWSER_FETCH);
|
|
311
|
+
if (retryCount < maxRetries) {
|
|
312
|
+
// 计算延迟时间,随着重试次数增加而增加
|
|
313
|
+
const delayMs = 1000 * (retryCount + 1);
|
|
314
|
+
log('browser.retryingAfterDelay', debug, { delayMs }, COMPONENTS.BROWSER_FETCH);
|
|
315
|
+
// 等待一段时间后重试
|
|
316
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
317
|
+
return fetchWithRetry(retryCount + 1);
|
|
318
|
+
}
|
|
319
|
+
// 超过最大重试次数,返回错误
|
|
320
|
+
return {
|
|
321
|
+
content: [{ type: 'text', text: `Error fetching text from ${url} after ${maxRetries + 1} attempts: ${error}` }],
|
|
322
|
+
isError: true
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
try {
|
|
327
|
+
return await fetchWithRetry();
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
// 如果需要关闭浏览器
|
|
331
|
+
if (closeBrowser) {
|
|
332
|
+
log('browser.closingInstance', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
333
|
+
await BrowserInstance.closeBrowser(debug);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 使用Puppeteer获取Markdown内容 (Get Markdown content using Puppeteer)
|
|
339
|
+
* 通过浏览器模式获取HTML并转换为Markdown (Fetch HTML in browser mode and convert to Markdown)
|
|
340
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
341
|
+
* @returns 获取结果 (Fetch result)
|
|
342
|
+
*/
|
|
343
|
+
async markdown(requestPayload) {
|
|
344
|
+
const { url, debug = false } = requestPayload;
|
|
345
|
+
log('browser.startingBrowserFetchForMarkdown', debug, { url }, COMPONENTS.BROWSER_FETCH);
|
|
346
|
+
try {
|
|
347
|
+
// 获取HTML内容
|
|
348
|
+
const htmlResult = await this.html(requestPayload);
|
|
349
|
+
if (htmlResult.isError) {
|
|
350
|
+
return htmlResult;
|
|
351
|
+
}
|
|
352
|
+
// 将HTML转换为Markdown
|
|
353
|
+
const turndownService = new TurndownService({
|
|
354
|
+
headingStyle: 'atx',
|
|
355
|
+
codeBlockStyle: 'fenced',
|
|
356
|
+
bulletListMarker: '-'
|
|
357
|
+
});
|
|
358
|
+
// 添加表格支持
|
|
359
|
+
turndownService.addRule('tables', {
|
|
360
|
+
filter: ['table'],
|
|
361
|
+
replacement: function (content, node) {
|
|
362
|
+
const tableContent = content.trim();
|
|
363
|
+
return '\n\n' + tableContent + '\n\n';
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
// 转换HTML为Markdown
|
|
367
|
+
const markdown = turndownService.turndown(htmlResult.content[0].text);
|
|
368
|
+
return {
|
|
369
|
+
content: [{ type: 'text', text: markdown }],
|
|
370
|
+
isError: false
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
log('browser.errorInBrowserFetchForMarkdown', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
|
|
375
|
+
return {
|
|
376
|
+
content: [{ type: 'text', text: `Error converting HTML to Markdown for ${url}: ${error}` }],
|
|
377
|
+
isError: true
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
+
*/
|
|
6
|
+
import puppeteerExtra from 'puppeteer-extra';
|
|
7
|
+
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
|
8
|
+
import { log, COMPONENTS } from '../../logger.js';
|
|
9
|
+
import { getRandomUserAgent } from '../common/utils.js';
|
|
10
|
+
// 添加stealth插件
|
|
11
|
+
// @ts-ignore - 忽略puppeteer-extra的类型错误
|
|
12
|
+
puppeteerExtra.use(StealthPlugin());
|
|
13
|
+
/**
|
|
14
|
+
* 浏览器实例管理类 (Browser instance management class)
|
|
15
|
+
* 负责浏览器的创建、获取和关闭 (Responsible for creating, getting and closing browsers)
|
|
16
|
+
*/
|
|
17
|
+
export class BrowserInstance {
|
|
18
|
+
/**
|
|
19
|
+
* 浏览器实例 (Browser instance)
|
|
20
|
+
* 静态单例,避免重复创建 (Static singleton to avoid repeated creation)
|
|
21
|
+
*/
|
|
22
|
+
static browser = null;
|
|
23
|
+
/**
|
|
24
|
+
* 浏览器启动状态标志 (Browser startup status flag)
|
|
25
|
+
* 防止并发启动多个浏览器实例 (Prevents concurrent startup of multiple browser instances)
|
|
26
|
+
*/
|
|
27
|
+
static browserStarting = false;
|
|
28
|
+
/**
|
|
29
|
+
* 浏览器启动Promise (Browser startup Promise)
|
|
30
|
+
* 用于等待浏览器启动完成 (Used to wait for browser startup completion)
|
|
31
|
+
*/
|
|
32
|
+
static browserStartPromise = null;
|
|
33
|
+
/**
|
|
34
|
+
* 上次内存检查时间 (Last memory check time)
|
|
35
|
+
*/
|
|
36
|
+
static lastMemoryCheck = 0;
|
|
37
|
+
/**
|
|
38
|
+
* 内存检查间隔(毫秒) (Memory check interval in milliseconds)
|
|
39
|
+
*/
|
|
40
|
+
static memoryCheckInterval = 60000;
|
|
41
|
+
/**
|
|
42
|
+
* 检查内存使用情况 (Check memory usage)
|
|
43
|
+
* 定期监控浏览器内存使用,必要时关闭浏览器释放资源 (Periodically monitor browser memory usage, close browser to release resources when necessary)
|
|
44
|
+
* @param debug 是否启用调试输出 (Whether to enable debug output)
|
|
45
|
+
*/
|
|
46
|
+
static checkMemoryUsage(debug = false) {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
// 每隔一段时间检查一次内存 (Check memory at regular intervals)
|
|
49
|
+
if (now - this.lastMemoryCheck < this.memoryCheckInterval) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.lastMemoryCheck = now;
|
|
53
|
+
try {
|
|
54
|
+
const memoryUsage = process.memoryUsage();
|
|
55
|
+
const heapUsed = Math.round(memoryUsage.heapUsed / 1024 / 1024);
|
|
56
|
+
const heapTotal = Math.round(memoryUsage.heapTotal / 1024 / 1024);
|
|
57
|
+
const rss = Math.round(memoryUsage.rss / 1024 / 1024);
|
|
58
|
+
// 记录内存使用情况
|
|
59
|
+
log('browser.memoryUsage', debug, { usage: heapUsed }, COMPONENTS.BROWSER_FETCH);
|
|
60
|
+
// 如果内存使用过高,记录警告
|
|
61
|
+
if (heapUsed > 500 || rss > 1000) {
|
|
62
|
+
log('browser.memoryTooHigh', debug, { heapUsed, heapTotal, rss }, COMPONENTS.BROWSER_FETCH);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
log('browser.memoryCheckError', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 获取浏览器实例 (Get browser instance)
|
|
71
|
+
* 如果浏览器未启动,则启动浏览器 (Start browser if not started)
|
|
72
|
+
* @param debug 是否启用调试输出 (Whether to enable debug output)
|
|
73
|
+
* @returns 浏览器实例 (Browser instance)
|
|
74
|
+
*/
|
|
75
|
+
static async getBrowser(debug = false) {
|
|
76
|
+
// 检查内存使用情况 (Check memory usage)
|
|
77
|
+
this.checkMemoryUsage(debug);
|
|
78
|
+
// 如果浏览器已经存在,直接返回 (If browser already exists, return directly)
|
|
79
|
+
if (this.browser) {
|
|
80
|
+
log('browser.reusingExistingBrowser', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
81
|
+
return this.browser;
|
|
82
|
+
}
|
|
83
|
+
// 如果浏览器正在启动中,等待启动完成 (If browser is starting, wait for startup to complete)
|
|
84
|
+
if (this.browserStarting && this.browserStartPromise) {
|
|
85
|
+
log('browser.waitingForBrowserStart', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
86
|
+
return this.browserStartPromise;
|
|
87
|
+
}
|
|
88
|
+
// 标记浏览器正在启动 (Mark browser as starting)
|
|
89
|
+
this.browserStarting = true;
|
|
90
|
+
// 创建启动Promise (Create startup Promise)
|
|
91
|
+
this.browserStartPromise = new Promise(async (resolve, reject) => {
|
|
92
|
+
try {
|
|
93
|
+
log('browser.startingBrowser', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
94
|
+
// 准备浏览器启动参数 (Prepare browser startup parameters)
|
|
95
|
+
const launchOptions = {
|
|
96
|
+
headless: 'new',
|
|
97
|
+
args: [
|
|
98
|
+
'--no-sandbox',
|
|
99
|
+
'--disable-setuid-sandbox',
|
|
100
|
+
'--disable-dev-shm-usage',
|
|
101
|
+
'--disable-accelerated-2d-canvas',
|
|
102
|
+
'--no-first-run',
|
|
103
|
+
'--no-zygote',
|
|
104
|
+
'--disable-gpu',
|
|
105
|
+
'--disable-infobars',
|
|
106
|
+
'--window-position=0,0',
|
|
107
|
+
'--ignore-certificate-errors',
|
|
108
|
+
'--ignore-certificate-errors-spki-list',
|
|
109
|
+
'--disable-extensions',
|
|
110
|
+
'--disable-default-apps',
|
|
111
|
+
'--enable-features=NetworkService',
|
|
112
|
+
'--disable-features=IsolateOrigins,site-per-process',
|
|
113
|
+
'--disable-web-security',
|
|
114
|
+
'--disable-site-isolation-trials',
|
|
115
|
+
'--disable-features=BlockInsecurePrivateNetworkRequests',
|
|
116
|
+
'--disable-features=IsolateOrigins',
|
|
117
|
+
'--disable-features=site-per-process',
|
|
118
|
+
'--disable-blink-features=AutomationControlled',
|
|
119
|
+
'--user-agent=' + getRandomUserAgent()
|
|
120
|
+
],
|
|
121
|
+
ignoreHTTPSErrors: true,
|
|
122
|
+
defaultViewport: {
|
|
123
|
+
width: 1920,
|
|
124
|
+
height: 1080
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
// 检查是否有环境变量指定的Chrome路径 (Check if there is a Chrome path specified by environment variables)
|
|
128
|
+
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
|
|
129
|
+
if (executablePath) {
|
|
130
|
+
log('browser.usingCustomChromePath', debug, { path: executablePath }, COMPONENTS.BROWSER_FETCH);
|
|
131
|
+
launchOptions.executablePath = executablePath;
|
|
132
|
+
launchOptions.channel = undefined;
|
|
133
|
+
}
|
|
134
|
+
// 启动浏览器 (Launch browser)
|
|
135
|
+
// @ts-ignore - 忽略puppeteer-extra的类型错误
|
|
136
|
+
this.browser = await puppeteerExtra.launch(launchOptions);
|
|
137
|
+
log('browser.browserStarted', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
138
|
+
// 设置浏览器关闭事件处理 (Set browser close event handling)
|
|
139
|
+
this.browser.on('disconnected', () => {
|
|
140
|
+
log('browser.browserDisconnected', debug, {}, COMPONENTS.BROWSER_FETCH);
|
|
141
|
+
this.browser = null;
|
|
142
|
+
this.browserStarting = false;
|
|
143
|
+
this.browserStartPromise = null;
|
|
144
|
+
});
|
|
145
|
+
// 重置标志 (Reset flags)
|
|
146
|
+
this.browserStarting = false;
|
|
147
|
+
// 解析Promise (Resolve Promise)
|
|
148
|
+
resolve(this.browser);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
// 记录错误 (Log error)
|
|
152
|
+
log('browser.browserStartError', true, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
|
|
153
|
+
// 重置标志 (Reset flags)
|
|
154
|
+
this.browser = null;
|
|
155
|
+
this.browserStarting = false;
|
|
156
|
+
this.browserStartPromise = null;
|
|
157
|
+
// 拒绝Promise (Reject Promise)
|
|
158
|
+
reject(error);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return this.browserStartPromise;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 关闭浏览器 (Close browser)
|
|
165
|
+
* @param debug 是否启用调试输出 (Whether to enable debug output)
|
|
166
|
+
*/
|
|
167
|
+
static async closeBrowser(debug = false) {
|
|
168
|
+
log('browser.closing', debug, { debug }, COMPONENTS.BROWSER_FETCH);
|
|
169
|
+
try {
|
|
170
|
+
if (this.browser) {
|
|
171
|
+
await this.browser.close();
|
|
172
|
+
this.browser = null;
|
|
173
|
+
log('browser.closed', debug, { debug }, COMPONENTS.BROWSER_FETCH);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
log('browser.closingError', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|