@jshookmcp/jshook 0.1.5 → 0.1.6
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/LICENSE +661 -661
- package/README.md +72 -40
- package/README.zh.md +77 -40
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +13 -1
- package/dist/modules/analyzer/IntelligentAnalyzer.js +19 -11
- package/dist/modules/browser/BrowserModeManager.d.ts +5 -0
- package/dist/modules/browser/BrowserModeManager.js +96 -10
- package/dist/modules/browser/CamoufoxBrowserManager.d.ts +4 -0
- package/dist/modules/browser/CamoufoxBrowserManager.js +64 -3
- package/dist/modules/browser/TabRegistry.js +3 -2
- package/dist/modules/browser/UnifiedBrowserManager.d.ts +5 -0
- package/dist/modules/browser/UnifiedBrowserManager.js +62 -9
- package/dist/modules/captcha/AICaptchaDetector.js +185 -185
- package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
- package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
- package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
- package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
- package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
- package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
- package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
- package/dist/modules/monitor/PerformanceMonitor.js +64 -32
- package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
- package/dist/modules/process/LinuxProcessManager.js +7 -3
- package/dist/modules/process/MacProcessManager.d.ts +3 -1
- package/dist/modules/process/MacProcessManager.js +32 -28
- package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
- package/dist/modules/process/ProcessManager.impl.js +54 -13
- package/dist/modules/process/index.d.ts +3 -1
- package/dist/modules/process/index.js +2 -2
- package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
- package/dist/modules/process/memory/AuditTrail.js +44 -0
- package/dist/modules/process/memory/availability.js +49 -49
- package/dist/modules/process/memory/injector.js +185 -185
- package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
- package/dist/modules/process/memory/linux/mapsParser.js +28 -0
- package/dist/modules/process/memory/reader.js +50 -50
- package/dist/modules/process/memory/regions.enumerate.js +45 -1
- package/dist/modules/process/memory/regions.protection.js +48 -2
- package/dist/modules/process/memory/scanner.d.ts +4 -1
- package/dist/modules/process/memory/scanner.js +383 -182
- package/dist/modules/process/memory/writer.js +54 -54
- package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
- package/dist/native/NativeMemoryManager.impl.js +72 -24
- package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
- package/dist/native/NativeMemoryManager.utils.js +44 -1
- package/dist/native/scripts/linux/enum-windows.sh +12 -12
- package/dist/native/scripts/macos/enum-windows.applescript +22 -22
- package/dist/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
- package/dist/native/scripts/windows/enum-windows.ps1 +44 -44
- package/dist/native/scripts/windows/inject-dll.ps1 +21 -21
- package/dist/server/MCPServer.search.d.ts +3 -0
- package/dist/server/MCPServer.search.js +21 -2
- package/dist/server/ToolCallContextGuard.d.ts +2 -0
- package/dist/server/ToolCallContextGuard.js +29 -14
- package/dist/server/ToolSearch.js +11 -5
- package/dist/server/domains/browser/definitions.tools.page-core.js +53 -53
- package/dist/server/domains/browser/definitions.tools.runtime.js +40 -40
- package/dist/server/domains/browser/definitions.tools.security.js +76 -76
- package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
- package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
- package/dist/server/domains/process/definitions.js +20 -7
- package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
- package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
- package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
- package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
- package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
- package/dist/server/domains/process/manifest.js +1 -0
- package/dist/server/domains/transform/handlers.impl.transform-base.js +102 -102
- package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
- package/dist/server/domains/workflow/handlers.impl.workflow-base.js +51 -51
- package/dist/server/registry/discovery.js +17 -12
- package/dist/server/registry/index.js +10 -2
- package/dist/utils/TokenBudgetManager.d.ts +1 -0
- package/dist/utils/TokenBudgetManager.js +22 -0
- package/package.json +5 -1
- package/src/native/scripts/linux/enum-windows.sh +12 -12
- package/src/native/scripts/macos/enum-windows.applescript +22 -22
- package/src/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
- package/src/native/scripts/windows/enum-windows.ps1 +44 -44
- package/src/native/scripts/windows/inject-dll.ps1 +21 -21
|
@@ -7,6 +7,8 @@ export class BrowserModeManager {
|
|
|
7
7
|
browser = null;
|
|
8
8
|
currentPage = null;
|
|
9
9
|
isHeadless = true;
|
|
10
|
+
isClosing = false;
|
|
11
|
+
launchPromise;
|
|
10
12
|
config;
|
|
11
13
|
captchaDetector;
|
|
12
14
|
launchOptions;
|
|
@@ -24,6 +26,27 @@ export class BrowserModeManager {
|
|
|
24
26
|
this.launchOptions = launchOptions;
|
|
25
27
|
}
|
|
26
28
|
async launch() {
|
|
29
|
+
if (this.browser?.isConnected()) {
|
|
30
|
+
return this.browser;
|
|
31
|
+
}
|
|
32
|
+
if (this.isClosing) {
|
|
33
|
+
throw new Error('Cannot launch browser while closing');
|
|
34
|
+
}
|
|
35
|
+
if (this.launchPromise) {
|
|
36
|
+
return this.launchPromise;
|
|
37
|
+
}
|
|
38
|
+
const launchPromise = this.doLaunch();
|
|
39
|
+
this.launchPromise = launchPromise;
|
|
40
|
+
try {
|
|
41
|
+
return await launchPromise;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
if (this.launchPromise === launchPromise) {
|
|
45
|
+
this.launchPromise = undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async doLaunch() {
|
|
27
50
|
const headlessMode = this.isHeadless;
|
|
28
51
|
const executablePath = this.resolveExecutablePath();
|
|
29
52
|
logger.info(`Launching browser (${headlessMode ? 'headless' : 'headed'} mode)...`);
|
|
@@ -43,7 +66,14 @@ export class BrowserModeManager {
|
|
|
43
66
|
if (executablePath) {
|
|
44
67
|
options.executablePath = executablePath;
|
|
45
68
|
}
|
|
46
|
-
|
|
69
|
+
const browser = await puppeteer.launch(options);
|
|
70
|
+
if (this.isClosing) {
|
|
71
|
+
await browser.close().catch(error => {
|
|
72
|
+
logger.warn('Failed to close browser launched during shutdown', error);
|
|
73
|
+
});
|
|
74
|
+
throw new Error('Browser launch aborted because close was requested');
|
|
75
|
+
}
|
|
76
|
+
this.browser = browser;
|
|
47
77
|
logger.info('Browser launched successfully');
|
|
48
78
|
return this.browser;
|
|
49
79
|
}
|
|
@@ -64,10 +94,8 @@ export class BrowserModeManager {
|
|
|
64
94
|
return undefined;
|
|
65
95
|
}
|
|
66
96
|
async newPage() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
const page = await this.browser.newPage();
|
|
97
|
+
const browser = this.browser?.isConnected() ? this.browser : await this.launch();
|
|
98
|
+
const page = await browser.newPage();
|
|
71
99
|
this.currentPage = page;
|
|
72
100
|
await this.injectAntiDetectionScripts(page);
|
|
73
101
|
if (this.sessionData.cookies && this.sessionData.cookies.length > 0) {
|
|
@@ -75,6 +103,20 @@ export class BrowserModeManager {
|
|
|
75
103
|
}
|
|
76
104
|
return page;
|
|
77
105
|
}
|
|
106
|
+
async finalizeClose() {
|
|
107
|
+
try {
|
|
108
|
+
const browser = this.browser;
|
|
109
|
+
this.browser = null;
|
|
110
|
+
this.currentPage = null;
|
|
111
|
+
if (browser) {
|
|
112
|
+
await browser.close();
|
|
113
|
+
logger.info('Browser closed');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
this.isClosing = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
78
120
|
async goto(url, page) {
|
|
79
121
|
const targetPage = page || this.currentPage;
|
|
80
122
|
if (!targetPage) {
|
|
@@ -111,6 +153,10 @@ export class BrowserModeManager {
|
|
|
111
153
|
await this.launch();
|
|
112
154
|
const newPage = await this.newPage();
|
|
113
155
|
await newPage.goto(url, { waitUntil: 'networkidle2' });
|
|
156
|
+
await this.restoreSessionData(newPage);
|
|
157
|
+
if (this.sessionData.localStorage || this.sessionData.sessionStorage) {
|
|
158
|
+
await newPage.reload({ waitUntil: 'networkidle2' });
|
|
159
|
+
}
|
|
114
160
|
this.showCaptchaPrompt(captchaInfo);
|
|
115
161
|
const completed = await this.captchaDetector.waitForCompletion(newPage, this.config.captchaTimeout);
|
|
116
162
|
if (completed) {
|
|
@@ -147,7 +193,10 @@ export class BrowserModeManager {
|
|
|
147
193
|
}
|
|
148
194
|
}
|
|
149
195
|
async saveSessionData(page) {
|
|
196
|
+
this.sessionData = {};
|
|
150
197
|
try {
|
|
198
|
+
const url = page.url();
|
|
199
|
+
this.sessionData.origin = url !== 'about:blank' ? new URL(url).origin : undefined;
|
|
151
200
|
this.sessionData.cookies = await page.cookies();
|
|
152
201
|
const storageData = await page.evaluate(() => {
|
|
153
202
|
const local = {};
|
|
@@ -174,6 +223,37 @@ export class BrowserModeManager {
|
|
|
174
223
|
logger.error('Failed to capture session data before mode switch', error);
|
|
175
224
|
}
|
|
176
225
|
}
|
|
226
|
+
async restoreSessionData(page) {
|
|
227
|
+
try {
|
|
228
|
+
const currentUrl = page.url();
|
|
229
|
+
const currentOrigin = currentUrl !== 'about:blank' ? new URL(currentUrl).origin : undefined;
|
|
230
|
+
if (this.sessionData.origin && currentOrigin && this.sessionData.origin !== currentOrigin) {
|
|
231
|
+
logger.warn(`Origin mismatch: session data from ${this.sessionData.origin} cannot be restored to ${currentOrigin}. ` +
|
|
232
|
+
'This prevents cross-origin data leakage.');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (this.sessionData.localStorage || this.sessionData.sessionStorage) {
|
|
236
|
+
await page.evaluate((data) => {
|
|
237
|
+
const restoreStorage = (storage, items) => {
|
|
238
|
+
if (items) {
|
|
239
|
+
for (const [key, value] of Object.entries(items)) {
|
|
240
|
+
storage.setItem(key, value);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
restoreStorage(localStorage, data.local);
|
|
245
|
+
restoreStorage(sessionStorage, data.session);
|
|
246
|
+
}, {
|
|
247
|
+
local: this.sessionData.localStorage,
|
|
248
|
+
session: this.sessionData.sessionStorage
|
|
249
|
+
});
|
|
250
|
+
logger.info('Session storage data restored');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
logger.error('Failed to restore session storage data', error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
177
257
|
async injectAntiDetectionScripts(page) {
|
|
178
258
|
await page.evaluateOnNewDocument(() => {
|
|
179
259
|
Object.defineProperty(navigator, 'webdriver', {
|
|
@@ -265,12 +345,18 @@ export class BrowserModeManager {
|
|
|
265
345
|
logger.info('Injected anti-detection scripts');
|
|
266
346
|
}
|
|
267
347
|
async close() {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
348
|
+
this.sessionData = {};
|
|
349
|
+
this.isClosing = true;
|
|
350
|
+
const pendingLaunch = this.launchPromise;
|
|
351
|
+
if (pendingLaunch) {
|
|
352
|
+
void pendingLaunch
|
|
353
|
+
.catch(() => undefined)
|
|
354
|
+
.finally(() => {
|
|
355
|
+
void this.finalizeClose();
|
|
356
|
+
});
|
|
357
|
+
return;
|
|
273
358
|
}
|
|
359
|
+
await this.finalizeClose();
|
|
274
360
|
}
|
|
275
361
|
getBrowser() {
|
|
276
362
|
return this.browser;
|
|
@@ -30,11 +30,15 @@ export declare class CamoufoxBrowserManager {
|
|
|
30
30
|
private browser;
|
|
31
31
|
private browserServer;
|
|
32
32
|
private config;
|
|
33
|
+
private isClosing;
|
|
34
|
+
private launchPromise?;
|
|
33
35
|
constructor(config?: CamoufoxBrowserConfig);
|
|
34
36
|
launch(): Promise<CamoufoxBrowserLike>;
|
|
37
|
+
private doLaunch;
|
|
35
38
|
newPage(): Promise<CamoufoxPageLike>;
|
|
36
39
|
goto(url: string, page?: CamoufoxPageLike): Promise<CamoufoxPageLike>;
|
|
37
40
|
close(): Promise<void>;
|
|
41
|
+
private finalizeClose;
|
|
38
42
|
launchAsServer(port?: number, ws_path?: string): Promise<string>;
|
|
39
43
|
connectToServer(wsEndpoint: string): Promise<CamoufoxBrowserLike>;
|
|
40
44
|
closeBrowserServer(): Promise<void>;
|
|
@@ -4,6 +4,8 @@ export class CamoufoxBrowserManager {
|
|
|
4
4
|
browser = null;
|
|
5
5
|
browserServer = null;
|
|
6
6
|
config;
|
|
7
|
+
isClosing = false;
|
|
8
|
+
launchPromise;
|
|
7
9
|
constructor(config = {}) {
|
|
8
10
|
this.config = {
|
|
9
11
|
os: config.os ?? 'windows',
|
|
@@ -16,6 +18,29 @@ export class CamoufoxBrowserManager {
|
|
|
16
18
|
};
|
|
17
19
|
}
|
|
18
20
|
async launch() {
|
|
21
|
+
if (this.browser?.isConnected()) {
|
|
22
|
+
return this.browser;
|
|
23
|
+
}
|
|
24
|
+
if (this.isClosing) {
|
|
25
|
+
throw new Error('Cannot launch browser while closing');
|
|
26
|
+
}
|
|
27
|
+
if (this.launchPromise) {
|
|
28
|
+
return this.launchPromise;
|
|
29
|
+
}
|
|
30
|
+
this.launchPromise = this.doLaunch();
|
|
31
|
+
try {
|
|
32
|
+
return await this.launchPromise;
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
this.launchPromise = undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async doLaunch() {
|
|
39
|
+
if (this.browser) {
|
|
40
|
+
logger.info('Closing existing Camoufox browser before relaunch');
|
|
41
|
+
await this.browser.close().catch(err => logger.warn('Failed to close previous browser:', err));
|
|
42
|
+
this.browser = null;
|
|
43
|
+
}
|
|
19
44
|
logger.info(`Launching Camoufox (Firefox) [os=${this.config.os}, headless=${this.config.headless}]...`);
|
|
20
45
|
let Camoufox;
|
|
21
46
|
try {
|
|
@@ -33,6 +58,13 @@ export class CamoufoxBrowserManager {
|
|
|
33
58
|
block_images: this.config.blockImages,
|
|
34
59
|
block_webrtc: this.config.blockWebrtc,
|
|
35
60
|
}));
|
|
61
|
+
if (this.isClosing) {
|
|
62
|
+
await this.browser.close().catch(error => {
|
|
63
|
+
logger.warn('Failed to close Camoufox browser launched during shutdown:', error);
|
|
64
|
+
});
|
|
65
|
+
this.browser = null;
|
|
66
|
+
throw new Error('Camoufox launch aborted because close was requested');
|
|
67
|
+
}
|
|
36
68
|
logger.info('Camoufox browser launched');
|
|
37
69
|
return this.browser;
|
|
38
70
|
}
|
|
@@ -51,10 +83,29 @@ export class CamoufoxBrowserManager {
|
|
|
51
83
|
return targetPage;
|
|
52
84
|
}
|
|
53
85
|
async close() {
|
|
54
|
-
|
|
55
|
-
|
|
86
|
+
this.isClosing = true;
|
|
87
|
+
const pendingLaunch = this.launchPromise;
|
|
88
|
+
if (pendingLaunch) {
|
|
89
|
+
void pendingLaunch
|
|
90
|
+
.catch(() => undefined)
|
|
91
|
+
.finally(() => {
|
|
92
|
+
void this.finalizeClose();
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await this.finalizeClose();
|
|
97
|
+
}
|
|
98
|
+
async finalizeClose() {
|
|
99
|
+
try {
|
|
100
|
+
const browser = this.browser;
|
|
56
101
|
this.browser = null;
|
|
57
|
-
|
|
102
|
+
if (browser) {
|
|
103
|
+
await browser.close();
|
|
104
|
+
logger.info('Camoufox browser closed');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
this.isClosing = false;
|
|
58
109
|
}
|
|
59
110
|
}
|
|
60
111
|
async launchAsServer(port, ws_path) {
|
|
@@ -77,6 +128,11 @@ export class CamoufoxBrowserManager {
|
|
|
77
128
|
port,
|
|
78
129
|
ws_path,
|
|
79
130
|
};
|
|
131
|
+
if (this.browserServer) {
|
|
132
|
+
logger.info('Closing existing Camoufox server before relaunch');
|
|
133
|
+
await this.browserServer.close().catch(err => logger.warn('Failed to close previous server:', err));
|
|
134
|
+
this.browserServer = null;
|
|
135
|
+
}
|
|
80
136
|
this.browserServer = await launchServer(serverOptions);
|
|
81
137
|
const endpoint = this.browserServer.wsEndpoint();
|
|
82
138
|
logger.info(`Camoufox server listening on: ${endpoint}`);
|
|
@@ -84,6 +140,11 @@ export class CamoufoxBrowserManager {
|
|
|
84
140
|
}
|
|
85
141
|
async connectToServer(wsEndpoint) {
|
|
86
142
|
logger.info(`Connecting to Camoufox server: ${wsEndpoint}`);
|
|
143
|
+
if (this.browser) {
|
|
144
|
+
logger.info('Disconnecting existing browser before new connection');
|
|
145
|
+
await this.browser.close().catch(err => logger.warn('Failed to close previous browser:', err));
|
|
146
|
+
this.browser = null;
|
|
147
|
+
}
|
|
87
148
|
const playwrightModule = await import('playwright-core');
|
|
88
149
|
const firefox = playwrightModule.firefox;
|
|
89
150
|
this.browser = (await firefox.connect(wsEndpoint));
|
|
@@ -113,7 +113,8 @@ export class TabRegistry {
|
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
115
|
setCurrentPageId(pageId) {
|
|
116
|
-
|
|
116
|
+
const entry = this.tabsById.get(pageId);
|
|
117
|
+
if (!entry || entry.stale)
|
|
117
118
|
return false;
|
|
118
119
|
this.currentPageId = pageId;
|
|
119
120
|
return true;
|
|
@@ -167,7 +168,7 @@ export class TabRegistry {
|
|
|
167
168
|
url: current?.meta.url ?? null,
|
|
168
169
|
title: current?.meta.title ?? null,
|
|
169
170
|
tabIndex: current?.meta.index ?? null,
|
|
170
|
-
pageId: this.currentPageId,
|
|
171
|
+
pageId: current ? this.currentPageId : null,
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
listTabs() {
|
|
@@ -46,10 +46,15 @@ export declare class UnifiedBrowserManager implements IBrowserManager {
|
|
|
46
46
|
private camoufoxManager;
|
|
47
47
|
private browserDiscovery;
|
|
48
48
|
private activePage;
|
|
49
|
+
private chromeLaunchPromise?;
|
|
50
|
+
private camoufoxLaunchPromise?;
|
|
51
|
+
private isClosing;
|
|
49
52
|
constructor(config?: UnifiedBrowserConfig);
|
|
50
53
|
launch(): Promise<PuppeteerBrowser | CamoufoxBrowserLike>;
|
|
51
54
|
private launchChrome;
|
|
55
|
+
private doLaunchChrome;
|
|
52
56
|
private launchCamoufox;
|
|
57
|
+
private doLaunchCamoufox;
|
|
53
58
|
connect(wsEndpoint: string): Promise<PuppeteerBrowser | CamoufoxBrowserLike>;
|
|
54
59
|
private connectChrome;
|
|
55
60
|
private connectCamoufox;
|
|
@@ -9,6 +9,9 @@ export class UnifiedBrowserManager {
|
|
|
9
9
|
camoufoxManager = null;
|
|
10
10
|
browserDiscovery;
|
|
11
11
|
activePage = null;
|
|
12
|
+
chromeLaunchPromise;
|
|
13
|
+
camoufoxLaunchPromise;
|
|
14
|
+
isClosing = false;
|
|
12
15
|
constructor(config = {}) {
|
|
13
16
|
this.config = config;
|
|
14
17
|
this.driver = config.driver ?? 'chrome';
|
|
@@ -21,6 +24,25 @@ export class UnifiedBrowserManager {
|
|
|
21
24
|
return this.launchChrome();
|
|
22
25
|
}
|
|
23
26
|
async launchChrome() {
|
|
27
|
+
if (this.isClosing) {
|
|
28
|
+
throw new Error('Cannot launch browser while closing');
|
|
29
|
+
}
|
|
30
|
+
const existingBrowser = this.chromeManager?.getBrowser();
|
|
31
|
+
if (existingBrowser?.isConnected()) {
|
|
32
|
+
return existingBrowser;
|
|
33
|
+
}
|
|
34
|
+
if (this.chromeLaunchPromise) {
|
|
35
|
+
return this.chromeLaunchPromise;
|
|
36
|
+
}
|
|
37
|
+
this.chromeLaunchPromise = this.doLaunchChrome();
|
|
38
|
+
try {
|
|
39
|
+
return await this.chromeLaunchPromise;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
this.chromeLaunchPromise = undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async doLaunchChrome() {
|
|
24
46
|
logger.info(`Launching Chrome [headless=${this.config.headless ?? true}]...`);
|
|
25
47
|
const modeConfig = {
|
|
26
48
|
autoDetectCaptcha: this.config.autoDetectCaptcha,
|
|
@@ -46,6 +68,25 @@ export class UnifiedBrowserManager {
|
|
|
46
68
|
return browser;
|
|
47
69
|
}
|
|
48
70
|
async launchCamoufox() {
|
|
71
|
+
if (this.isClosing) {
|
|
72
|
+
throw new Error('Cannot launch browser while closing');
|
|
73
|
+
}
|
|
74
|
+
const existingBrowser = this.camoufoxManager?.getBrowser();
|
|
75
|
+
if (existingBrowser?.isConnected()) {
|
|
76
|
+
return existingBrowser;
|
|
77
|
+
}
|
|
78
|
+
if (this.camoufoxLaunchPromise) {
|
|
79
|
+
return this.camoufoxLaunchPromise;
|
|
80
|
+
}
|
|
81
|
+
this.camoufoxLaunchPromise = this.doLaunchCamoufox();
|
|
82
|
+
try {
|
|
83
|
+
return await this.camoufoxLaunchPromise;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
this.camoufoxLaunchPromise = undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async doLaunchCamoufox() {
|
|
49
90
|
const headless = this.normalizeCamoufoxHeadless();
|
|
50
91
|
logger.info(`Launching Camoufox (Firefox) [os=${this.config.os ?? 'windows'}, headless=${headless}]...`);
|
|
51
92
|
const camoufoxConfig = {
|
|
@@ -114,18 +155,30 @@ export class UnifiedBrowserManager {
|
|
|
114
155
|
return this.chromeManager.goto(url, targetPage);
|
|
115
156
|
}
|
|
116
157
|
async close() {
|
|
117
|
-
|
|
118
|
-
|
|
158
|
+
this.isClosing = true;
|
|
159
|
+
try {
|
|
160
|
+
const camoufoxManager = this.camoufoxManager;
|
|
161
|
+
const chromeManager = this.chromeManager;
|
|
119
162
|
this.camoufoxManager = null;
|
|
120
|
-
this.activePage = null;
|
|
121
|
-
logger.info('Camoufox browser closed');
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (this.chromeManager) {
|
|
125
|
-
await this.chromeManager.close();
|
|
126
163
|
this.chromeManager = null;
|
|
164
|
+
this.chromeLaunchPromise = undefined;
|
|
165
|
+
this.camoufoxLaunchPromise = undefined;
|
|
127
166
|
this.activePage = null;
|
|
128
|
-
|
|
167
|
+
const closeTasks = [];
|
|
168
|
+
if (camoufoxManager) {
|
|
169
|
+
closeTasks.push(camoufoxManager.close().then(() => {
|
|
170
|
+
logger.info('Camoufox browser closed');
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
if (chromeManager) {
|
|
174
|
+
closeTasks.push(chromeManager.close().then(() => {
|
|
175
|
+
logger.info('Chrome browser closed');
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
await Promise.all(closeTasks);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
this.isClosing = false;
|
|
129
182
|
}
|
|
130
183
|
}
|
|
131
184
|
getBrowser() {
|