@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.
Files changed (81) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +72 -40
  3. package/README.zh.md +77 -40
  4. package/dist/constants.d.ts +1 -0
  5. package/dist/constants.js +13 -1
  6. package/dist/modules/analyzer/IntelligentAnalyzer.js +19 -11
  7. package/dist/modules/browser/BrowserModeManager.d.ts +5 -0
  8. package/dist/modules/browser/BrowserModeManager.js +96 -10
  9. package/dist/modules/browser/CamoufoxBrowserManager.d.ts +4 -0
  10. package/dist/modules/browser/CamoufoxBrowserManager.js +64 -3
  11. package/dist/modules/browser/TabRegistry.js +3 -2
  12. package/dist/modules/browser/UnifiedBrowserManager.d.ts +5 -0
  13. package/dist/modules/browser/UnifiedBrowserManager.js +62 -9
  14. package/dist/modules/captcha/AICaptchaDetector.js +185 -185
  15. package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
  16. package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
  17. package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
  18. package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
  19. package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
  20. package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
  21. package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
  22. package/dist/modules/monitor/PerformanceMonitor.js +64 -32
  23. package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
  24. package/dist/modules/process/LinuxProcessManager.js +7 -3
  25. package/dist/modules/process/MacProcessManager.d.ts +3 -1
  26. package/dist/modules/process/MacProcessManager.js +32 -28
  27. package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
  28. package/dist/modules/process/ProcessManager.impl.js +54 -13
  29. package/dist/modules/process/index.d.ts +3 -1
  30. package/dist/modules/process/index.js +2 -2
  31. package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
  32. package/dist/modules/process/memory/AuditTrail.js +44 -0
  33. package/dist/modules/process/memory/availability.js +49 -49
  34. package/dist/modules/process/memory/injector.js +185 -185
  35. package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
  36. package/dist/modules/process/memory/linux/mapsParser.js +28 -0
  37. package/dist/modules/process/memory/reader.js +50 -50
  38. package/dist/modules/process/memory/regions.enumerate.js +45 -1
  39. package/dist/modules/process/memory/regions.protection.js +48 -2
  40. package/dist/modules/process/memory/scanner.d.ts +4 -1
  41. package/dist/modules/process/memory/scanner.js +383 -182
  42. package/dist/modules/process/memory/writer.js +54 -54
  43. package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
  44. package/dist/native/NativeMemoryManager.impl.js +72 -24
  45. package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
  46. package/dist/native/NativeMemoryManager.utils.js +44 -1
  47. package/dist/native/scripts/linux/enum-windows.sh +12 -12
  48. package/dist/native/scripts/macos/enum-windows.applescript +22 -22
  49. package/dist/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
  50. package/dist/native/scripts/windows/enum-windows.ps1 +44 -44
  51. package/dist/native/scripts/windows/inject-dll.ps1 +21 -21
  52. package/dist/server/MCPServer.search.d.ts +3 -0
  53. package/dist/server/MCPServer.search.js +21 -2
  54. package/dist/server/ToolCallContextGuard.d.ts +2 -0
  55. package/dist/server/ToolCallContextGuard.js +29 -14
  56. package/dist/server/ToolSearch.js +11 -5
  57. package/dist/server/domains/browser/definitions.tools.page-core.js +53 -53
  58. package/dist/server/domains/browser/definitions.tools.runtime.js +40 -40
  59. package/dist/server/domains/browser/definitions.tools.security.js +76 -76
  60. package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
  61. package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
  62. package/dist/server/domains/process/definitions.js +20 -7
  63. package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
  64. package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
  65. package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
  66. package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
  67. package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
  68. package/dist/server/domains/process/manifest.js +1 -0
  69. package/dist/server/domains/transform/handlers.impl.transform-base.js +102 -102
  70. package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
  71. package/dist/server/domains/workflow/handlers.impl.workflow-base.js +51 -51
  72. package/dist/server/registry/discovery.js +17 -12
  73. package/dist/server/registry/index.js +10 -2
  74. package/dist/utils/TokenBudgetManager.d.ts +1 -0
  75. package/dist/utils/TokenBudgetManager.js +22 -0
  76. package/package.json +5 -1
  77. package/src/native/scripts/linux/enum-windows.sh +12 -12
  78. package/src/native/scripts/macos/enum-windows.applescript +22 -22
  79. package/src/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
  80. package/src/native/scripts/windows/enum-windows.ps1 +44 -44
  81. 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
- this.browser = await puppeteer.launch(options);
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
- if (!this.browser) {
68
- await this.launch();
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
- if (this.browser) {
269
- await this.browser.close();
270
- this.browser = null;
271
- this.currentPage = null;
272
- logger.info('Browser closed');
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
- if (this.browser) {
55
- await this.browser.close();
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
- logger.info('Camoufox browser closed');
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
- if (!this.tabsById.has(pageId))
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
- if (this.driver === 'camoufox' && this.camoufoxManager) {
118
- await this.camoufoxManager.close();
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
- logger.info('Chrome browser closed');
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() {