@opentabs-dev/mcp-server 0.0.19

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 (309) hide show
  1. package/dist/audit-disk.d.ts +23 -0
  2. package/dist/audit-disk.d.ts.map +1 -0
  3. package/dist/audit-disk.js +74 -0
  4. package/dist/audit-disk.js.map +1 -0
  5. package/dist/browser-tools/analyze-site/detect-apis.d.ts +36 -0
  6. package/dist/browser-tools/analyze-site/detect-apis.d.ts.map +1 -0
  7. package/dist/browser-tools/analyze-site/detect-apis.js +383 -0
  8. package/dist/browser-tools/analyze-site/detect-apis.js.map +1 -0
  9. package/dist/browser-tools/analyze-site/detect-auth.d.ts +72 -0
  10. package/dist/browser-tools/analyze-site/detect-auth.d.ts.map +1 -0
  11. package/dist/browser-tools/analyze-site/detect-auth.js +384 -0
  12. package/dist/browser-tools/analyze-site/detect-auth.js.map +1 -0
  13. package/dist/browser-tools/analyze-site/detect-dom.d.ts +65 -0
  14. package/dist/browser-tools/analyze-site/detect-dom.d.ts.map +1 -0
  15. package/dist/browser-tools/analyze-site/detect-dom.js +45 -0
  16. package/dist/browser-tools/analyze-site/detect-dom.js.map +1 -0
  17. package/dist/browser-tools/analyze-site/detect-framework.d.ts +48 -0
  18. package/dist/browser-tools/analyze-site/detect-framework.d.ts.map +1 -0
  19. package/dist/browser-tools/analyze-site/detect-framework.js +31 -0
  20. package/dist/browser-tools/analyze-site/detect-framework.js.map +1 -0
  21. package/dist/browser-tools/analyze-site/detect-globals.d.ts +41 -0
  22. package/dist/browser-tools/analyze-site/detect-globals.d.ts.map +1 -0
  23. package/dist/browser-tools/analyze-site/detect-globals.js +42 -0
  24. package/dist/browser-tools/analyze-site/detect-globals.js.map +1 -0
  25. package/dist/browser-tools/analyze-site/detect-storage.d.ts +39 -0
  26. package/dist/browser-tools/analyze-site/detect-storage.d.ts.map +1 -0
  27. package/dist/browser-tools/analyze-site/detect-storage.js +34 -0
  28. package/dist/browser-tools/analyze-site/detect-storage.js.map +1 -0
  29. package/dist/browser-tools/analyze-site/index.d.ts +52 -0
  30. package/dist/browser-tools/analyze-site/index.d.ts.map +1 -0
  31. package/dist/browser-tools/analyze-site/index.js +827 -0
  32. package/dist/browser-tools/analyze-site/index.js.map +1 -0
  33. package/dist/browser-tools/analyze-site.d.ts +17 -0
  34. package/dist/browser-tools/analyze-site.d.ts.map +1 -0
  35. package/dist/browser-tools/analyze-site.js +41 -0
  36. package/dist/browser-tools/analyze-site.js.map +1 -0
  37. package/dist/browser-tools/clear-console-logs.d.ts +9 -0
  38. package/dist/browser-tools/clear-console-logs.d.ts.map +1 -0
  39. package/dist/browser-tools/clear-console-logs.js +16 -0
  40. package/dist/browser-tools/clear-console-logs.js.map +1 -0
  41. package/dist/browser-tools/click-element.d.ts +10 -0
  42. package/dist/browser-tools/click-element.d.ts.map +1 -0
  43. package/dist/browser-tools/click-element.js +22 -0
  44. package/dist/browser-tools/click-element.js.map +1 -0
  45. package/dist/browser-tools/close-tab.d.ts +9 -0
  46. package/dist/browser-tools/close-tab.d.ts.map +1 -0
  47. package/dist/browser-tools/close-tab.js +16 -0
  48. package/dist/browser-tools/close-tab.js.map +1 -0
  49. package/dist/browser-tools/definition.d.ts +26 -0
  50. package/dist/browser-tools/definition.d.ts.map +1 -0
  51. package/dist/browser-tools/definition.js +16 -0
  52. package/dist/browser-tools/definition.js.map +1 -0
  53. package/dist/browser-tools/delete-cookies.d.ts +10 -0
  54. package/dist/browser-tools/delete-cookies.d.ts.map +1 -0
  55. package/dist/browser-tools/delete-cookies.js +19 -0
  56. package/dist/browser-tools/delete-cookies.js.map +1 -0
  57. package/dist/browser-tools/disable-network-capture.d.ts +9 -0
  58. package/dist/browser-tools/disable-network-capture.d.ts.map +1 -0
  59. package/dist/browser-tools/disable-network-capture.js +16 -0
  60. package/dist/browser-tools/disable-network-capture.js.map +1 -0
  61. package/dist/browser-tools/enable-network-capture.d.ts +12 -0
  62. package/dist/browser-tools/enable-network-capture.d.ts.map +1 -0
  63. package/dist/browser-tools/enable-network-capture.js +42 -0
  64. package/dist/browser-tools/enable-network-capture.js.map +1 -0
  65. package/dist/browser-tools/execute-script.d.ts +19 -0
  66. package/dist/browser-tools/execute-script.d.ts.map +1 -0
  67. package/dist/browser-tools/execute-script.js +51 -0
  68. package/dist/browser-tools/execute-script.js.map +1 -0
  69. package/dist/browser-tools/extension-check-adapter.d.ts +11 -0
  70. package/dist/browser-tools/extension-check-adapter.d.ts.map +1 -0
  71. package/dist/browser-tools/extension-check-adapter.js +22 -0
  72. package/dist/browser-tools/extension-check-adapter.js.map +1 -0
  73. package/dist/browser-tools/extension-force-reconnect.d.ts +9 -0
  74. package/dist/browser-tools/extension-force-reconnect.d.ts.map +1 -0
  75. package/dist/browser-tools/extension-force-reconnect.js +19 -0
  76. package/dist/browser-tools/extension-force-reconnect.js.map +1 -0
  77. package/dist/browser-tools/extension-get-logs.d.ts +24 -0
  78. package/dist/browser-tools/extension-get-logs.d.ts.map +1 -0
  79. package/dist/browser-tools/extension-get-logs.js +34 -0
  80. package/dist/browser-tools/extension-get-logs.js.map +1 -0
  81. package/dist/browser-tools/extension-get-side-panel.d.ts +8 -0
  82. package/dist/browser-tools/extension-get-side-panel.d.ts.map +1 -0
  83. package/dist/browser-tools/extension-get-side-panel.js +17 -0
  84. package/dist/browser-tools/extension-get-side-panel.js.map +1 -0
  85. package/dist/browser-tools/extension-get-state.d.ts +9 -0
  86. package/dist/browser-tools/extension-get-state.d.ts.map +1 -0
  87. package/dist/browser-tools/extension-get-state.js +19 -0
  88. package/dist/browser-tools/extension-get-state.js.map +1 -0
  89. package/dist/browser-tools/focus-tab.d.ts +9 -0
  90. package/dist/browser-tools/focus-tab.d.ts.map +1 -0
  91. package/dist/browser-tools/focus-tab.js +17 -0
  92. package/dist/browser-tools/focus-tab.js.map +1 -0
  93. package/dist/browser-tools/get-console-logs.d.ts +18 -0
  94. package/dist/browser-tools/get-console-logs.d.ts.map +1 -0
  95. package/dist/browser-tools/get-console-logs.js +30 -0
  96. package/dist/browser-tools/get-console-logs.js.map +1 -0
  97. package/dist/browser-tools/get-cookies.d.ts +10 -0
  98. package/dist/browser-tools/get-cookies.d.ts.map +1 -0
  99. package/dist/browser-tools/get-cookies.js +23 -0
  100. package/dist/browser-tools/get-cookies.js.map +1 -0
  101. package/dist/browser-tools/get-network-requests.d.ts +10 -0
  102. package/dist/browser-tools/get-network-requests.d.ts.map +1 -0
  103. package/dist/browser-tools/get-network-requests.js +27 -0
  104. package/dist/browser-tools/get-network-requests.js.map +1 -0
  105. package/dist/browser-tools/get-page-html.d.ts +11 -0
  106. package/dist/browser-tools/get-page-html.d.ts.map +1 -0
  107. package/dist/browser-tools/get-page-html.js +32 -0
  108. package/dist/browser-tools/get-page-html.js.map +1 -0
  109. package/dist/browser-tools/get-resource-content.d.ts +11 -0
  110. package/dist/browser-tools/get-resource-content.d.ts.map +1 -0
  111. package/dist/browser-tools/get-resource-content.js +31 -0
  112. package/dist/browser-tools/get-resource-content.js.map +1 -0
  113. package/dist/browser-tools/get-storage.d.ts +14 -0
  114. package/dist/browser-tools/get-storage.d.ts.map +1 -0
  115. package/dist/browser-tools/get-storage.js +28 -0
  116. package/dist/browser-tools/get-storage.js.map +1 -0
  117. package/dist/browser-tools/get-tab-content.d.ts +11 -0
  118. package/dist/browser-tools/get-tab-content.d.ts.map +1 -0
  119. package/dist/browser-tools/get-tab-content.js +29 -0
  120. package/dist/browser-tools/get-tab-content.js.map +1 -0
  121. package/dist/browser-tools/get-tab-info.d.ts +9 -0
  122. package/dist/browser-tools/get-tab-info.d.ts.map +1 -0
  123. package/dist/browser-tools/get-tab-info.js +17 -0
  124. package/dist/browser-tools/get-tab-info.js.map +1 -0
  125. package/dist/browser-tools/handle-dialog.d.ts +14 -0
  126. package/dist/browser-tools/handle-dialog.d.ts.map +1 -0
  127. package/dist/browser-tools/handle-dialog.js +30 -0
  128. package/dist/browser-tools/handle-dialog.js.map +1 -0
  129. package/dist/browser-tools/hover-element.d.ts +11 -0
  130. package/dist/browser-tools/hover-element.d.ts.map +1 -0
  131. package/dist/browser-tools/hover-element.js +24 -0
  132. package/dist/browser-tools/hover-element.js.map +1 -0
  133. package/dist/browser-tools/index.d.ts +7 -0
  134. package/dist/browser-tools/index.d.ts.map +1 -0
  135. package/dist/browser-tools/index.js +81 -0
  136. package/dist/browser-tools/index.js.map +1 -0
  137. package/dist/browser-tools/list-resources.d.ts +10 -0
  138. package/dist/browser-tools/list-resources.d.ts.map +1 -0
  139. package/dist/browser-tools/list-resources.js +29 -0
  140. package/dist/browser-tools/list-resources.js.map +1 -0
  141. package/dist/browser-tools/list-tabs.d.ts +7 -0
  142. package/dist/browser-tools/list-tabs.d.ts.map +1 -0
  143. package/dist/browser-tools/list-tabs.js +16 -0
  144. package/dist/browser-tools/list-tabs.js.map +1 -0
  145. package/dist/browser-tools/navigate-tab.d.ts +10 -0
  146. package/dist/browser-tools/navigate-tab.d.ts.map +1 -0
  147. package/dist/browser-tools/navigate-tab.js +18 -0
  148. package/dist/browser-tools/navigate-tab.js.map +1 -0
  149. package/dist/browser-tools/open-tab.d.ts +9 -0
  150. package/dist/browser-tools/open-tab.d.ts.map +1 -0
  151. package/dist/browser-tools/open-tab.js +18 -0
  152. package/dist/browser-tools/open-tab.js.map +1 -0
  153. package/dist/browser-tools/press-key.d.ts +17 -0
  154. package/dist/browser-tools/press-key.d.ts.map +1 -0
  155. package/dist/browser-tools/press-key.js +43 -0
  156. package/dist/browser-tools/press-key.js.map +1 -0
  157. package/dist/browser-tools/query-elements.d.ts +12 -0
  158. package/dist/browser-tools/query-elements.d.ts.map +1 -0
  159. package/dist/browser-tools/query-elements.js +30 -0
  160. package/dist/browser-tools/query-elements.js.map +1 -0
  161. package/dist/browser-tools/reload-extension.d.ts +13 -0
  162. package/dist/browser-tools/reload-extension.d.ts.map +1 -0
  163. package/dist/browser-tools/reload-extension.js +35 -0
  164. package/dist/browser-tools/reload-extension.js.map +1 -0
  165. package/dist/browser-tools/screenshot-tab.d.ts +9 -0
  166. package/dist/browser-tools/screenshot-tab.d.ts.map +1 -0
  167. package/dist/browser-tools/screenshot-tab.js +22 -0
  168. package/dist/browser-tools/screenshot-tab.js.map +1 -0
  169. package/dist/browser-tools/scroll.d.ts +23 -0
  170. package/dist/browser-tools/scroll.d.ts.map +1 -0
  171. package/dist/browser-tools/scroll.js +56 -0
  172. package/dist/browser-tools/scroll.js.map +1 -0
  173. package/dist/browser-tools/select-option.d.ts +12 -0
  174. package/dist/browser-tools/select-option.d.ts.map +1 -0
  175. package/dist/browser-tools/select-option.js +25 -0
  176. package/dist/browser-tools/select-option.js.map +1 -0
  177. package/dist/browser-tools/set-cookie.d.ts +16 -0
  178. package/dist/browser-tools/set-cookie.d.ts.map +1 -0
  179. package/dist/browser-tools/set-cookie.js +42 -0
  180. package/dist/browser-tools/set-cookie.js.map +1 -0
  181. package/dist/browser-tools/type-text.d.ts +12 -0
  182. package/dist/browser-tools/type-text.d.ts.map +1 -0
  183. package/dist/browser-tools/type-text.js +25 -0
  184. package/dist/browser-tools/type-text.js.map +1 -0
  185. package/dist/browser-tools/url-validation.d.ts +13 -0
  186. package/dist/browser-tools/url-validation.d.ts.map +1 -0
  187. package/dist/browser-tools/url-validation.js +23 -0
  188. package/dist/browser-tools/url-validation.js.map +1 -0
  189. package/dist/browser-tools/wait-for-element.d.ts +12 -0
  190. package/dist/browser-tools/wait-for-element.d.ts.map +1 -0
  191. package/dist/browser-tools/wait-for-element.js +29 -0
  192. package/dist/browser-tools/wait-for-element.js.map +1 -0
  193. package/dist/config.d.ts +99 -0
  194. package/dist/config.d.ts.map +1 -0
  195. package/dist/config.js +344 -0
  196. package/dist/config.js.map +1 -0
  197. package/dist/dev-mode.d.ts +14 -0
  198. package/dist/dev-mode.d.ts.map +1 -0
  199. package/dist/dev-mode.js +15 -0
  200. package/dist/dev-mode.js.map +1 -0
  201. package/dist/discovery-legacy.d.ts +32 -0
  202. package/dist/discovery-legacy.d.ts.map +1 -0
  203. package/dist/discovery-legacy.js +415 -0
  204. package/dist/discovery-legacy.js.map +1 -0
  205. package/dist/discovery.d.ts +28 -0
  206. package/dist/discovery.d.ts.map +1 -0
  207. package/dist/discovery.js +97 -0
  208. package/dist/discovery.js.map +1 -0
  209. package/dist/extension-install.d.ts +27 -0
  210. package/dist/extension-install.d.ts.map +1 -0
  211. package/dist/extension-install.js +75 -0
  212. package/dist/extension-install.js.map +1 -0
  213. package/dist/extension-protocol.d.ts +130 -0
  214. package/dist/extension-protocol.d.ts.map +1 -0
  215. package/dist/extension-protocol.js +869 -0
  216. package/dist/extension-protocol.js.map +1 -0
  217. package/dist/file-watcher.d.ts +75 -0
  218. package/dist/file-watcher.d.ts.map +1 -0
  219. package/dist/file-watcher.js +616 -0
  220. package/dist/file-watcher.js.map +1 -0
  221. package/dist/http-routes.d.ts +88 -0
  222. package/dist/http-routes.d.ts.map +1 -0
  223. package/dist/http-routes.js +545 -0
  224. package/dist/http-routes.js.map +1 -0
  225. package/dist/index.d.ts +45 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +187 -0
  228. package/dist/index.js.map +1 -0
  229. package/dist/loader.d.ts +100 -0
  230. package/dist/loader.d.ts.map +1 -0
  231. package/dist/loader.js +402 -0
  232. package/dist/loader.js.map +1 -0
  233. package/dist/log-buffer.d.ts +33 -0
  234. package/dist/log-buffer.d.ts.map +1 -0
  235. package/dist/log-buffer.js +64 -0
  236. package/dist/log-buffer.js.map +1 -0
  237. package/dist/logger.d.ts +34 -0
  238. package/dist/logger.d.ts.map +1 -0
  239. package/dist/logger.js +81 -0
  240. package/dist/logger.js.map +1 -0
  241. package/dist/manifest-schema.d.ts +14 -0
  242. package/dist/manifest-schema.d.ts.map +1 -0
  243. package/dist/manifest-schema.js +51 -0
  244. package/dist/manifest-schema.js.map +1 -0
  245. package/dist/mcp-setup.d.ts +131 -0
  246. package/dist/mcp-setup.d.ts.map +1 -0
  247. package/dist/mcp-setup.js +673 -0
  248. package/dist/mcp-setup.js.map +1 -0
  249. package/dist/permissions.d.ts +59 -0
  250. package/dist/permissions.d.ts.map +1 -0
  251. package/dist/permissions.js +141 -0
  252. package/dist/permissions.js.map +1 -0
  253. package/dist/registry.d.ts +78 -0
  254. package/dist/registry.d.ts.map +1 -0
  255. package/dist/registry.js +187 -0
  256. package/dist/registry.js.map +1 -0
  257. package/dist/reload.d.ts +52 -0
  258. package/dist/reload.d.ts.map +1 -0
  259. package/dist/reload.js +326 -0
  260. package/dist/reload.js.map +1 -0
  261. package/dist/resolver.d.ts +53 -0
  262. package/dist/resolver.d.ts.map +1 -0
  263. package/dist/resolver.js +272 -0
  264. package/dist/resolver.js.map +1 -0
  265. package/dist/sanitize-error.d.ts +8 -0
  266. package/dist/sanitize-error.d.ts.map +1 -0
  267. package/dist/sanitize-error.js +25 -0
  268. package/dist/sanitize-error.js.map +1 -0
  269. package/dist/sanitize-tool-output.d.ts +20 -0
  270. package/dist/sanitize-tool-output.d.ts.map +1 -0
  271. package/dist/sanitize-tool-output.js +52 -0
  272. package/dist/sanitize-tool-output.js.map +1 -0
  273. package/dist/sdk-version.d.ts +11 -0
  274. package/dist/sdk-version.d.ts.map +1 -0
  275. package/dist/sdk-version.js +23 -0
  276. package/dist/sdk-version.js.map +1 -0
  277. package/dist/shutdown.d.ts +28 -0
  278. package/dist/shutdown.d.ts.map +1 -0
  279. package/dist/shutdown.js +68 -0
  280. package/dist/shutdown.js.map +1 -0
  281. package/dist/skip-confirmation.d.ts +15 -0
  282. package/dist/skip-confirmation.d.ts.map +1 -0
  283. package/dist/skip-confirmation.js +16 -0
  284. package/dist/skip-confirmation.js.map +1 -0
  285. package/dist/skip-sanitization.d.ts +17 -0
  286. package/dist/skip-sanitization.d.ts.map +1 -0
  287. package/dist/skip-sanitization.js +18 -0
  288. package/dist/skip-sanitization.js.map +1 -0
  289. package/dist/skip-verification.d.ts +11 -0
  290. package/dist/skip-verification.d.ts.map +1 -0
  291. package/dist/skip-verification.js +12 -0
  292. package/dist/skip-verification.js.map +1 -0
  293. package/dist/state.d.ts +290 -0
  294. package/dist/state.d.ts.map +1 -0
  295. package/dist/state.js +111 -0
  296. package/dist/state.js.map +1 -0
  297. package/dist/verify-plugin.d.ts +53 -0
  298. package/dist/verify-plugin.d.ts.map +1 -0
  299. package/dist/verify-plugin.js +123 -0
  300. package/dist/verify-plugin.js.map +1 -0
  301. package/dist/version-check.d.ts +35 -0
  302. package/dist/version-check.d.ts.map +1 -0
  303. package/dist/version-check.js +111 -0
  304. package/dist/version-check.js.map +1 -0
  305. package/dist/version.d.ts +10 -0
  306. package/dist/version.d.ts.map +1 -0
  307. package/dist/version.js +22 -0
  308. package/dist/version.js.map +1 -0
  309. package/package.json +28 -0
@@ -0,0 +1,827 @@
1
+ /**
2
+ * Site analyzer orchestrator.
3
+ *
4
+ * Collects data from the page using browser tool handlers (open tab, network
5
+ * capture, execute script, get cookies, get storage, get network requests),
6
+ * then passes the collected data through each detection module to produce a
7
+ * comprehensive site analysis report.
8
+ */
9
+ import { detectApis } from './detect-apis.js';
10
+ import { detectAuth } from './detect-auth.js';
11
+ import { detectDom } from './detect-dom.js';
12
+ import { detectFramework } from './detect-framework.js';
13
+ import { detectGlobals } from './detect-globals.js';
14
+ import { detectStorage } from './detect-storage.js';
15
+ import { dispatchToExtension, writeExecFile, deleteExecFile } from '../../extension-protocol.js';
16
+ // ---------------------------------------------------------------------------
17
+ // Script execution helper
18
+ // ---------------------------------------------------------------------------
19
+ /**
20
+ * Execute JavaScript in a tab's MAIN world and return the result.
21
+ * Uses the file-based injection pattern (writeExecFile → dispatchToExtension → deleteExecFile)
22
+ * to bypass page CSP restrictions.
23
+ */
24
+ const executeInTab = async (state, tabId, code) => {
25
+ const execId = crypto.randomUUID();
26
+ const filename = await writeExecFile(execId, code);
27
+ try {
28
+ const result = (await dispatchToExtension(state, 'browser.executeScript', {
29
+ tabId,
30
+ execFile: filename,
31
+ }));
32
+ // Unwrap the nested result from browser.executeScript:
33
+ // The extension returns { value: { value: <actual>, error?: <msg> } }
34
+ const inner = result?.value;
35
+ if (inner?.error) {
36
+ throw new Error(`Script execution error: ${inner.error}`);
37
+ }
38
+ return inner?.value ?? null;
39
+ }
40
+ finally {
41
+ void deleteExecFile(filename);
42
+ }
43
+ };
44
+ // ---------------------------------------------------------------------------
45
+ // Page-context scripts
46
+ // ---------------------------------------------------------------------------
47
+ /**
48
+ * Returns JS code that collects CSRF tokens from meta tags and hidden inputs.
49
+ * Runs in the page context (MAIN world).
50
+ */
51
+ const CSRF_SCRIPT = `
52
+ const tokens = [];
53
+ // Meta tags
54
+ for (const meta of document.querySelectorAll('meta[name]')) {
55
+ const name = (meta.getAttribute('name') || '').toLowerCase();
56
+ if (name === 'csrf-token' || name === '_csrf' || name === 'csrf_token' || name === 'csrf') {
57
+ const value = meta.getAttribute('content') || '';
58
+ if (value) tokens.push({ source: 'meta', name: meta.getAttribute('name'), value });
59
+ }
60
+ }
61
+ // Hidden inputs
62
+ for (const input of document.querySelectorAll('input[type="hidden"]')) {
63
+ const name = (input.getAttribute('name') || '').toLowerCase();
64
+ if (name === 'authenticity_token' || name === '_token' || name === 'csrfmiddlewaretoken' || name === '__requestverificationtoken' || name === '_csrf' || name === 'csrf_token') {
65
+ tokens.push({ source: 'hidden-input', name: input.getAttribute('name'), value: input.value });
66
+ }
67
+ }
68
+ return tokens;
69
+ `;
70
+ /**
71
+ * Returns JS code that probes for well-known SSR globals and extracts nested auth data.
72
+ * Runs in the page context.
73
+ */
74
+ const GLOBALS_AUTH_SCRIPT = `
75
+ const paths = ['__NEXT_DATA__', '__NUXT__', '__INITIAL_STATE__', '__APP_STATE__'];
76
+ const results = [];
77
+ for (const path of paths) {
78
+ if (typeof window[path] !== 'undefined') {
79
+ results.push({ path, value: window[path] });
80
+ }
81
+ }
82
+ return results;
83
+ `;
84
+ /**
85
+ * Returns JS code that detects frameworks by probing globals and DOM markers.
86
+ */
87
+ const FRAMEWORK_PROBE_SCRIPT = `
88
+ const probes = [];
89
+
90
+ // React
91
+ if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
92
+ let version;
93
+ try {
94
+ const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
95
+ if (hook.renderers && hook.renderers.size > 0) {
96
+ const renderer = hook.renderers.values().next().value;
97
+ if (renderer && renderer.version) version = renderer.version;
98
+ }
99
+ } catch {}
100
+ probes.push({ name: 'react', version });
101
+ }
102
+
103
+ // Next.js
104
+ if (window.__NEXT_DATA__) {
105
+ let version;
106
+ try { version = window.__NEXT_DATA__.buildId; } catch {}
107
+ probes.push({ name: 'nextjs', version });
108
+ }
109
+
110
+ // Vue
111
+ if (window.__VUE__) {
112
+ probes.push({ name: 'vue', version: undefined });
113
+ }
114
+ if (window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
115
+ const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
116
+ if (hook.Vue) {
117
+ probes.push({ name: 'vue', version: hook.Vue.version });
118
+ }
119
+ }
120
+
121
+ // Nuxt
122
+ if (window.__NUXT__) {
123
+ probes.push({ name: 'nuxt', version: undefined });
124
+ }
125
+
126
+ // Angular — look for ng-version attribute
127
+ const ngEl = document.querySelector('[ng-version]');
128
+ if (ngEl) {
129
+ probes.push({ name: 'angular', version: ngEl.getAttribute('ng-version') || undefined });
130
+ }
131
+
132
+ // Svelte
133
+ if (window.__svelte_meta || document.querySelector('[data-svelte-h]')) {
134
+ probes.push({ name: 'svelte', version: undefined });
135
+ }
136
+
137
+ // jQuery
138
+ if (window.jQuery || window.$) {
139
+ const jq = window.jQuery || window.$;
140
+ probes.push({ name: 'jquery', version: typeof jq.fn === 'object' ? jq.fn.jquery : undefined });
141
+ }
142
+
143
+ // Ember
144
+ if (window.Ember) {
145
+ probes.push({ name: 'ember', version: window.Ember.VERSION || undefined });
146
+ }
147
+
148
+ // Backbone
149
+ if (window.Backbone) {
150
+ probes.push({ name: 'backbone', version: window.Backbone.VERSION || undefined });
151
+ }
152
+
153
+ // Deduplicate by name (keep first occurrence with version)
154
+ const seen = new Set();
155
+ const unique = [];
156
+ for (const p of probes) {
157
+ if (!seen.has(p.name)) {
158
+ seen.add(p.name);
159
+ unique.push(p);
160
+ }
161
+ }
162
+
163
+ return unique;
164
+ `;
165
+ /**
166
+ * Returns JS code that detects SPA/SSR signals.
167
+ */
168
+ const SPA_SSR_PROBE_SCRIPT = `
169
+ // Single root element check
170
+ const body = document.body;
171
+ const children = body ? Array.from(body.children).filter(el => {
172
+ const tag = el.tagName.toLowerCase();
173
+ return tag !== 'script' && tag !== 'style' && tag !== 'link' && tag !== 'noscript';
174
+ }) : [];
175
+ const hasSingleRootElement = children.length === 1;
176
+
177
+ // pushState evidence: check for known SPA container IDs
178
+ const spaContainerIds = ['root', 'app', '__next', '__nuxt', 'svelte'];
179
+ const hasKnownSpaContainer = spaContainerIds.some(id => document.getElementById(id) !== null);
180
+ const usesPushState = hasKnownSpaContainer;
181
+
182
+ const hasNextData = typeof window.__NEXT_DATA__ !== 'undefined';
183
+ const hasNuxtData = typeof window.__NUXT__ !== 'undefined';
184
+
185
+ // Hydration markers
186
+ const hasHydrationMarkers = !!(
187
+ document.querySelector('[data-reactroot]') ||
188
+ document.querySelector('[data-server-rendered]') ||
189
+ document.querySelector('[data-react-helmet]') ||
190
+ (window.__NEXT_DATA__ && window.__NEXT_DATA__.props)
191
+ );
192
+
193
+ return { hasSingleRootElement, usesPushState, hasNextData, hasNuxtData, hasHydrationMarkers };
194
+ `;
195
+ /**
196
+ * Returns JS code that scans window globals for non-standard properties.
197
+ */
198
+ const GLOBALS_SCAN_SCRIPT = `
199
+ const BROWSER_BUILTINS = new Set([
200
+ 'undefined','NaN','Infinity','globalThis','window','self','document','name','location',
201
+ 'customElements','history','navigation','locationbar','menubar','personalbar',
202
+ 'scrollbars','statusbar','toolbar','status','closed','frames','length','top',
203
+ 'opener','parent','frameElement','navigator','origin','external','screen',
204
+ 'visualViewport','innerWidth','innerHeight','outerWidth','outerHeight',
205
+ 'devicePixelRatio','clientInformation','screenX','screenY','screenLeft',
206
+ 'screenTop','styleMedia','onsearch','isSecureContext','crossOriginIsolated',
207
+ 'performance','caches','cookieStore','onappinstalled','onbeforeinstallprompt',
208
+ 'crypto','indexedDB','sessionStorage','localStorage','chrome','speechSynthesis',
209
+ 'webkitRequestAnimationFrame','webkitCancelAnimationFrame','fetch',
210
+ 'alert','atob','blur','btoa','cancelAnimationFrame','cancelIdleCallback',
211
+ 'captureEvents','clearInterval','clearTimeout','close','confirm',
212
+ 'createImageBitmap','find','focus','getComputedStyle','getSelection',
213
+ 'matchMedia','moveBy','moveTo','open','postMessage','print','prompt',
214
+ 'queueMicrotask','releaseEvents','reportError','requestAnimationFrame',
215
+ 'requestIdleCallback','resizeBy','resizeTo','scroll','scrollBy','scrollTo',
216
+ 'setInterval','setTimeout','stop','structuredClone','webkitRequestFileSystem',
217
+ 'webkitResolveLocalFileSystemURL','addEventListener','removeEventListener',
218
+ 'dispatchEvent','getScreenDetails','queryLocalFonts','showDirectoryPicker',
219
+ 'showOpenFilePicker','showSavePicker','originAgentCluster','trustedTypes',
220
+ 'screenIsExtended','onscreenchange','credentialless','documentPictureInPicture',
221
+ 'launchQueue','sharedStorage','onpageswap','onpagereveal','onpageshow',
222
+ 'onpagehide','onbeforeunload','onunload','onload','onerror','onmessage',
223
+ 'onmessageerror','onpopstate','onrejectionhandled','onstorage',
224
+ 'onunhandledrejection','onhashchange','onlanguagechange','onbeforetoggle',
225
+ 'oncontentvisibilityautostatechange','onscrollend','onbeforematch',
226
+ 'onauxclick','onblur','oncancel','oncanplay','oncanplaythrough','onchange',
227
+ 'onclick','onclose','oncontextlost','oncontextmenu','oncontextrestored',
228
+ 'oncuechange','ondblclick','ondrag','ondragend','ondragenter','ondragleave',
229
+ 'ondragover','ondragstart','ondrop','ondurationchange','onemptied','onended',
230
+ 'onfocus','onformdata','ongotpointercapture','oninput','oninvalid',
231
+ 'onkeydown','onkeypress','onkeyup','onloadeddata','onloadedmetadata',
232
+ 'onloadstart','onlostpointercapture','onmousedown','onmouseenter',
233
+ 'onmouseleave','onmousemove','onmouseout','onmouseover','onmouseup',
234
+ 'onmousewheel','onpause','onplay','onplaying','onpointercancel',
235
+ 'onpointerdown','onpointerenter','onpointerleave','onpointermove',
236
+ 'onpointerout','onpointerover','onpointerrawupdate','onpointerup',
237
+ 'onprogress','onratechange','onreset','onresize','onscroll',
238
+ 'onsecuritypolicyviolation','onseeked','onseeking','onselect',
239
+ 'onselectionchange','onselectstart','onslotchange','onstalled','onsubmit',
240
+ 'onsuspend','ontimeupdate','ontoggle','ontransitioncancel','ontransitionend',
241
+ 'ontransitionrun','ontransitionstart','onvolumechange','onwaiting',
242
+ 'onwebkitanimationend','onwebkitanimationiteration','onwebkitanimationstart',
243
+ 'onwebkittransitionend','onwheel','onanimationend','onanimationiteration',
244
+ 'onanimationstart','onabeforeprint','onafterprint','onbeforexrselect',
245
+ 'onabort','onbeforeinput','onbeforecopy','onbeforecut','onbeforepaste',
246
+ 'oncopy','oncut','onpaste','onfreeze','onresume','scheduler','onbeforeprint',
247
+ 'onafterprint','Array','ArrayBuffer','BigInt','BigInt64Array','BigUint64Array',
248
+ 'Boolean','DataView','Date','Error','EvalError','FinalizationRegistry',
249
+ 'Float32Array','Float64Array','Function','Int16Array','Int32Array','Int8Array',
250
+ 'Map','Number','Object','Promise','Proxy','RangeError','ReferenceError',
251
+ 'RegExp','Set','SharedArrayBuffer','String','Symbol','SyntaxError','TypeError',
252
+ 'URIError','Uint16Array','Uint32Array','Uint8Array','Uint8ClampedArray',
253
+ 'WeakMap','WeakRef','WeakSet','decodeURI','decodeURIComponent','encodeURI',
254
+ 'encodeURIComponent','escape','eval','isFinite','isNaN','parseFloat','parseInt',
255
+ 'unescape','AbortController','AbortSignal','Blob','BroadcastChannel',
256
+ 'ByteLengthQueuingStrategy','CSS','CSSAnimation','CSSConditionRule',
257
+ 'CSSFontFaceRule','CSSGroupingRule','CSSKeyframeRule','CSSKeyframesRule',
258
+ 'CSSLayerBlockRule','CSSLayerStatementRule','CSSMediaRule','CSSNamespaceRule',
259
+ 'CSSPageRule','CSSPropertyRule','CSSRule','CSSRuleList','CSSStyleDeclaration',
260
+ 'CSSStyleRule','CSSStyleSheet','CSSSupportsRule','CSSTransition',
261
+ 'Cache','CacheStorage','CanvasGradient','CanvasPattern',
262
+ 'CanvasRenderingContext2D','ClipboardEvent','CloseEvent','Comment',
263
+ 'CompositionEvent','CountQueuingStrategy','CustomElementRegistry',
264
+ 'CustomEvent','DOMException','DOMImplementation','DOMMatrix',
265
+ 'DOMMatrixReadOnly','DOMParser','DOMPoint','DOMPointReadOnly','DOMQuad',
266
+ 'DOMRect','DOMRectList','DOMRectReadOnly','DOMStringList','DOMStringMap',
267
+ 'DOMTokenList','Document','DocumentFragment','DocumentType','Element',
268
+ 'ErrorEvent','Event','EventSource','EventTarget','File','FileList',
269
+ 'FileReader','FocusEvent','FontFace','FontFaceSet','FormData',
270
+ 'FormDataEvent','HTMLAllCollection','HTMLAnchorElement','HTMLAreaElement',
271
+ 'HTMLAudioElement','HTMLBRElement','HTMLBaseElement','HTMLBodyElement',
272
+ 'HTMLButtonElement','HTMLCanvasElement','HTMLCollection','HTMLDListElement',
273
+ 'HTMLDataElement','HTMLDataListElement','HTMLDetailsElement','HTMLDialogElement',
274
+ 'HTMLDirectoryElement','HTMLDivElement','HTMLDocument','HTMLElement',
275
+ 'HTMLEmbedElement','HTMLFieldSetElement','HTMLFontElement','HTMLFormElement',
276
+ 'HTMLFrameElement','HTMLFrameSetElement','HTMLHRElement','HTMLHeadElement',
277
+ 'HTMLHeadingElement','HTMLHtmlElement','HTMLIFrameElement','HTMLImageElement',
278
+ 'HTMLInputElement','HTMLLIElement','HTMLLabelElement','HTMLLegendElement',
279
+ 'HTMLLinkElement','HTMLMapElement','HTMLMarqueeElement','HTMLMediaElement',
280
+ 'HTMLMenuElement','HTMLMetaElement','HTMLMeterElement','HTMLModElement',
281
+ 'HTMLOListElement','HTMLObjectElement','HTMLOptGroupElement','HTMLOptionElement',
282
+ 'HTMLOutputElement','HTMLParagraphElement','HTMLParamElement','HTMLPictureElement',
283
+ 'HTMLPreElement','HTMLProgressElement','HTMLQuoteElement','HTMLScriptElement',
284
+ 'HTMLSelectElement','HTMLSlotElement','HTMLSourceElement','HTMLSpanElement',
285
+ 'HTMLStyleElement','HTMLTableCaptionElement','HTMLTableCellElement',
286
+ 'HTMLTableColElement','HTMLTableElement','HTMLTableRowElement',
287
+ 'HTMLTableSectionElement','HTMLTemplateElement','HTMLTextAreaElement',
288
+ 'HTMLTimeElement','HTMLTitleElement','HTMLTrackElement','HTMLUListElement',
289
+ 'HTMLUnknownElement','HTMLVideoElement','HashChangeEvent','Headers',
290
+ 'History','IDBCursor','IDBCursorWithValue','IDBDatabase','IDBFactory',
291
+ 'IDBIndex','IDBKeyRange','IDBObjectStore','IDBOpenDBRequest','IDBRequest',
292
+ 'IDBTransaction','IDBVersionChangeEvent','Image','ImageBitmap',
293
+ 'ImageBitmapRenderingContext','ImageData','InputEvent','IntersectionObserver',
294
+ 'IntersectionObserverEntry','JSON','KeyboardEvent','Location',
295
+ 'MathMLElement','MediaEncryptedEvent','MediaError','MediaList',
296
+ 'MediaQueryList','MediaQueryListEvent','MediaSource','MessageChannel',
297
+ 'MessageEvent','MessagePort','MouseEvent','MutationEvent','MutationObserver',
298
+ 'MutationRecord','NamedNodeMap','Navigator','Node','NodeFilter',
299
+ 'NodeIterator','NodeList','Notification','OfflineAudioCompletionEvent',
300
+ 'OffscreenCanvas','OffscreenCanvasRenderingContext2D','Option',
301
+ 'PageTransitionEvent','Path2D','Performance','PerformanceEntry',
302
+ 'PerformanceMark','PerformanceMeasure','PerformanceNavigation',
303
+ 'PerformanceNavigationTiming','PerformanceObserver','PerformanceObserverEntryList',
304
+ 'PerformancePaintTiming','PerformanceResourceTiming','PerformanceServerTiming',
305
+ 'PerformanceTiming','PointerEvent','PopStateEvent','ProcessingInstruction',
306
+ 'ProgressEvent','PromiseRejectionEvent','Range','ReadableByteStreamController',
307
+ 'ReadableStream','ReadableStreamBYOBReader','ReadableStreamBYOBRequest',
308
+ 'ReadableStreamDefaultController','ReadableStreamDefaultReader','Request',
309
+ 'ResizeObserver','ResizeObserverEntry','ResizeObserverSize','Response',
310
+ 'SVGAElement','SVGAngle','SVGAnimateElement','SVGAnimateMotionElement',
311
+ 'SVGAnimateTransformElement','SVGAnimatedAngle','SVGAnimatedBoolean',
312
+ 'SVGAnimatedEnumeration','SVGAnimatedInteger','SVGAnimatedLength',
313
+ 'SVGAnimatedLengthList','SVGAnimatedNumber','SVGAnimatedNumberList',
314
+ 'SVGAnimatedPreserveAspectRatio','SVGAnimatedRect','SVGAnimatedString',
315
+ 'SVGAnimatedTransformList','SVGAnimationElement','SVGCircleElement',
316
+ 'SVGClipPathElement','SVGComponentTransferFunctionElement','SVGDefsElement',
317
+ 'SVGDescElement','SVGElement','SVGEllipseElement','SVGFEBlendElement',
318
+ 'SVGFEColorMatrixElement','SVGFEComponentTransferElement',
319
+ 'SVGFECompositeElement','SVGFEConvolveMatrixElement',
320
+ 'SVGFEDiffuseLightingElement','SVGFEDisplacementMapElement',
321
+ 'SVGFEDistantLightElement','SVGFEDropShadowElement','SVGFEFloodElement',
322
+ 'SVGFEFuncAElement','SVGFEFuncBElement','SVGFEFuncGElement',
323
+ 'SVGFEFuncRElement','SVGFEGaussianBlurElement','SVGFEImageElement',
324
+ 'SVGFEMergeElement','SVGFEMergeNodeElement','SVGFEMorphologyElement',
325
+ 'SVGFEOffsetElement','SVGFEPointLightElement',
326
+ 'SVGFESpecularLightingElement','SVGFESpotLightElement','SVGFETileElement',
327
+ 'SVGFETurbulenceElement','SVGFilterElement','SVGForeignObjectElement',
328
+ 'SVGGElement','SVGGeometryElement','SVGGradientElement','SVGGraphicsElement',
329
+ 'SVGImageElement','SVGLength','SVGLengthList','SVGLineElement',
330
+ 'SVGLinearGradientElement','SVGMPathElement','SVGMarkerElement',
331
+ 'SVGMaskElement','SVGMatrix','SVGMetadataElement','SVGNumber',
332
+ 'SVGNumberList','SVGPathElement','SVGPatternElement','SVGPoint',
333
+ 'SVGPointList','SVGPolygonElement','SVGPolylineElement',
334
+ 'SVGPreserveAspectRatio','SVGRadialGradientElement','SVGRect',
335
+ 'SVGRectElement','SVGSVGElement','SVGScriptElement','SVGSetElement',
336
+ 'SVGStopElement','SVGStringList','SVGStyleElement','SVGSwitchElement',
337
+ 'SVGSymbolElement','SVGTSpanElement','SVGTextContentElement',
338
+ 'SVGTextElement','SVGTextPathElement','SVGTextPositioningElement',
339
+ 'SVGTitleElement','SVGTransform','SVGTransformList','SVGUnitTypes',
340
+ 'SVGUseElement','SVGViewElement','Screen','SecurityPolicyViolationEvent',
341
+ 'Selection','ServiceWorker','ServiceWorkerContainer',
342
+ 'ServiceWorkerRegistration','ShadowRoot','SourceBuffer','SourceBufferList',
343
+ 'StaticRange','Storage','StorageEvent','StyleSheet','StyleSheetList',
344
+ 'SubmitEvent','Text','TextDecoder','TextEncoder','TextEvent','TextMetrics',
345
+ 'TextTrack','TextTrackCue','TextTrackCueList','TextTrackList',
346
+ 'TimeRanges','Touch','TouchEvent','TouchList','TrackEvent',
347
+ 'TransformStream','TransformStreamDefaultController','TransitionEvent',
348
+ 'TreeWalker','UIEvent','URL','URLSearchParams','VTTCue','ValidityState',
349
+ 'VisualViewport','WaveShaperNode','WebGL2RenderingContext',
350
+ 'WebGLActiveInfo','WebGLBuffer','WebGLContextEvent','WebGLFramebuffer',
351
+ 'WebGLProgram','WebGLQuery','WebGLRenderbuffer','WebGLRenderingContext',
352
+ 'WebGLSampler','WebGLShader','WebGLShaderPrecisionFormat','WebGLSync',
353
+ 'WebGLTexture','WebGLTransformFeedback','WebGLUniformLocation',
354
+ 'WebGLVertexArrayObject','WebSocket','WheelEvent','Window','Worker',
355
+ 'WritableStream','WritableStreamDefaultController',
356
+ 'WritableStreamDefaultWriter','XMLDocument','XMLHttpRequest',
357
+ 'XMLHttpRequestEventTarget','XMLHttpRequestUpload','XMLSerializer',
358
+ 'XPathEvaluator','XPathExpression','XPathResult','XSLTProcessor',
359
+ 'Audio','Atomics','Math','Reflect','console','WebAssembly',
360
+ 'FontFaceSetLoadEvent','MediaCapabilities','Scheduler','Sanitizer',
361
+ 'TrustedHTML','TrustedScript','TrustedScriptURL','TrustedTypePolicy',
362
+ 'TrustedTypePolicyFactory','DocumentTimeline','AnimationTimeline',
363
+ 'Animation','AnimationEffect','AnimationEvent','AnimationPlaybackEvent',
364
+ 'KeyframeEffect','ComputedEffectTiming','EffectTiming',
365
+ 'getComputedStyle','matchMedia',
366
+ '__REACT_DEVTOOLS_GLOBAL_HOOK__','__VUE_DEVTOOLS_GLOBAL_HOOK__',
367
+ ]);
368
+
369
+ const results = [];
370
+ const keys = Object.keys(window);
371
+ for (const key of keys) {
372
+ if (BROWSER_BUILTINS.has(key)) continue;
373
+ if (key.startsWith('_') && key.startsWith('__zone') ) continue;
374
+ try {
375
+ const val = window[key];
376
+ const type = typeof val;
377
+ let topLevelKeys;
378
+ if (type === 'object' && val !== null && !Array.isArray(val)) {
379
+ try {
380
+ topLevelKeys = Object.keys(val).slice(0, 20);
381
+ } catch {}
382
+ }
383
+ results.push({ path: key, type, topLevelKeys });
384
+ } catch {}
385
+ }
386
+ return results;
387
+ `;
388
+ /**
389
+ * Returns JS code that collects form data from the page.
390
+ */
391
+ const FORMS_SCRIPT = `
392
+ const forms = [];
393
+ for (const form of document.querySelectorAll('form')) {
394
+ const fields = [];
395
+ for (const el of form.querySelectorAll('input, select, textarea')) {
396
+ const name = el.getAttribute('name') || '';
397
+ const type = el.getAttribute('type') || el.tagName.toLowerCase();
398
+ if (name) fields.push({ name, type });
399
+ }
400
+ forms.push({
401
+ action: form.getAttribute('action') || '',
402
+ method: (form.getAttribute('method') || 'GET').toUpperCase(),
403
+ fields,
404
+ });
405
+ }
406
+ return forms;
407
+ `;
408
+ /**
409
+ * Returns JS code that collects interactive elements from the page.
410
+ */
411
+ const INTERACTIVE_ELEMENTS_SCRIPT = `
412
+ const elements = [];
413
+ const selectors = 'button, [onclick], a[href^="javascript:"], input, select, textarea, [role="button"]';
414
+ const limit = 100;
415
+ let count = 0;
416
+ for (const el of document.querySelectorAll(selectors)) {
417
+ if (count >= limit) break;
418
+ elements.push({
419
+ tag: el.tagName.toLowerCase(),
420
+ type: el.getAttribute('type') || undefined,
421
+ name: el.getAttribute('name') || undefined,
422
+ id: el.id || undefined,
423
+ text: (el.textContent || '').trim().slice(0, 100) || undefined,
424
+ });
425
+ count++;
426
+ }
427
+ return elements;
428
+ `;
429
+ /**
430
+ * Returns JS code that collects unique data-* attribute names from the page.
431
+ */
432
+ const DATA_ATTRIBUTES_SCRIPT = `
433
+ const attrs = new Set();
434
+ for (const el of document.querySelectorAll('*')) {
435
+ if (el.dataset) {
436
+ for (const key of Object.keys(el.dataset)) {
437
+ attrs.add(key);
438
+ }
439
+ }
440
+ if (attrs.size > 200) break;
441
+ }
442
+ return Array.from(attrs);
443
+ `;
444
+ /**
445
+ * Returns JS code that collects storage key names.
446
+ */
447
+ const STORAGE_KEYS_SCRIPT = `
448
+ const cookieNames = [];
449
+ try {
450
+ const cookieStr = document.cookie;
451
+ if (cookieStr) {
452
+ for (const pair of cookieStr.split(';')) {
453
+ const eqIdx = pair.indexOf('=');
454
+ if (eqIdx > 0) {
455
+ cookieNames.push(pair.slice(0, eqIdx).trim());
456
+ }
457
+ }
458
+ }
459
+ } catch {}
460
+
461
+ let localStorageKeys = [];
462
+ try { localStorageKeys = Object.keys(localStorage); } catch {}
463
+
464
+ let sessionStorageKeys = [];
465
+ try { sessionStorageKeys = Object.keys(sessionStorage); } catch {}
466
+
467
+ return { cookieNames, localStorageKeys, sessionStorageKeys };
468
+ `;
469
+ /**
470
+ * Returns JS code that reads localStorage/sessionStorage entries (keys + values)
471
+ * for auth detection (JWT detection needs values).
472
+ */
473
+ const STORAGE_ENTRIES_SCRIPT = `
474
+ const localEntries = [];
475
+ try {
476
+ for (const key of Object.keys(localStorage)) {
477
+ try {
478
+ const val = localStorage.getItem(key);
479
+ if (val !== null) localEntries.push({ key, value: val });
480
+ } catch {}
481
+ }
482
+ } catch {}
483
+
484
+ const sessionEntries = [];
485
+ try {
486
+ for (const key of Object.keys(sessionStorage)) {
487
+ try {
488
+ const val = sessionStorage.getItem(key);
489
+ if (val !== null) sessionEntries.push({ key, value: val });
490
+ } catch {}
491
+ }
492
+ } catch {}
493
+
494
+ return { localEntries, sessionEntries };
495
+ `;
496
+ // ---------------------------------------------------------------------------
497
+ // Suggestion generation
498
+ // ---------------------------------------------------------------------------
499
+ /**
500
+ * Generate tool suggestions from the analysis results.
501
+ * Produces actionable suggestions for plugin developers based on detected
502
+ * APIs, forms, and capabilities.
503
+ */
504
+ const generateSuggestions = (apis, dom, auth, _framework) => {
505
+ const suggestions = [];
506
+ // REST API suggestions
507
+ for (const endpoint of apis.endpoints) {
508
+ if (endpoint.protocol === 'rest') {
509
+ const suggestion = restEndpointSuggestion(endpoint);
510
+ if (suggestion)
511
+ suggestions.push(suggestion);
512
+ }
513
+ }
514
+ // GraphQL suggestions
515
+ const graphqlEndpoints = apis.endpoints.filter(e => e.protocol === 'graphql');
516
+ if (graphqlEndpoints.length > 0) {
517
+ suggestions.push({
518
+ toolName: 'graphql_query',
519
+ description: 'Execute GraphQL queries against the site API',
520
+ approach: `Send POST requests to ${graphqlEndpoints[0]?.url ?? '/graphql'} with { query, variables } body. Use fetchJSON from the plugin SDK with the site's auth headers.`,
521
+ complexity: 'medium',
522
+ });
523
+ // Suggest tools based on observed query body samples
524
+ for (const ep of graphqlEndpoints) {
525
+ if (ep.requestBodySample) {
526
+ const queryTools = graphqlQuerySuggestions(ep);
527
+ suggestions.push(...queryTools);
528
+ }
529
+ }
530
+ }
531
+ // JSON-RPC suggestions
532
+ const jsonrpcEndpoints = apis.endpoints.filter(e => e.protocol === 'jsonrpc');
533
+ if (jsonrpcEndpoints.length > 0) {
534
+ suggestions.push({
535
+ toolName: 'rpc_call',
536
+ description: 'Execute JSON-RPC calls against the site API',
537
+ approach: `Send POST requests to ${jsonrpcEndpoints[0]?.url ?? '/rpc'} with { jsonrpc: "2.0", method, params, id } body.`,
538
+ complexity: 'medium',
539
+ });
540
+ }
541
+ // tRPC suggestions
542
+ const trpcEndpoints = apis.endpoints.filter(e => e.protocol === 'trpc');
543
+ if (trpcEndpoints.length > 0) {
544
+ for (const ep of trpcEndpoints) {
545
+ const procedureName = extractTrpcProcedure(ep.url);
546
+ if (procedureName) {
547
+ suggestions.push({
548
+ toolName: `trpc_${procedureName.replace(/\./g, '_')}`,
549
+ description: `Call tRPC procedure ${procedureName}`,
550
+ approach: `${ep.method} ${ep.url}${ep.method === 'POST' ? ' with JSON body' : ' with query params'}. Auth: ${auth.authenticated ? 'include session credentials' : 'none detected'}.`,
551
+ complexity: 'low',
552
+ });
553
+ }
554
+ }
555
+ }
556
+ // WebSocket suggestions
557
+ const wsEndpoints = apis.endpoints.filter(e => e.protocol === 'websocket');
558
+ if (wsEndpoints.length > 0) {
559
+ suggestions.push({
560
+ toolName: 'subscribe_realtime',
561
+ description: 'Subscribe to real-time WebSocket updates',
562
+ approach: `Connect to ${wsEndpoints[0]?.url ?? 'the WebSocket endpoint'}. Monitor incoming messages for real-time data updates.`,
563
+ complexity: 'high',
564
+ });
565
+ }
566
+ // Form suggestions
567
+ for (const form of dom.forms) {
568
+ if (form.fields.length > 0) {
569
+ const formName = deriveFormName(form);
570
+ suggestions.push({
571
+ toolName: `submit_${formName}`,
572
+ description: `Submit the ${formName} form`,
573
+ approach: `POST to ${form.action || 'current page'} with fields: ${form.fields.map(f => f.name).join(', ')}. ${auth.methods.some(m => m.type === 'csrf-token') ? 'Include CSRF token from meta tag or hidden input.' : ''}`,
574
+ complexity: 'low',
575
+ });
576
+ }
577
+ }
578
+ return suggestions;
579
+ };
580
+ /** Generate a suggestion for a REST endpoint. */
581
+ const restEndpointSuggestion = (endpoint) => {
582
+ const urlPath = extractPathSegments(endpoint.url);
583
+ if (!urlPath)
584
+ return undefined;
585
+ const resourceName = extractResourceName(urlPath);
586
+ if (!resourceName)
587
+ return undefined;
588
+ const verb = httpMethodToVerb(endpoint.method);
589
+ const toolName = `${verb}_${resourceName}`;
590
+ return {
591
+ toolName,
592
+ description: `${capitalizeFirst(verb)} ${resourceName} via ${endpoint.method} ${urlPath}`,
593
+ approach: `${endpoint.method} ${endpoint.url}${endpoint.requestBodySample ? ` with JSON body (sample: ${endpoint.requestBodySample.slice(0, 100)})` : ''}. ${endpoint.authHeader ? `Include ${endpoint.authHeader} header.` : 'No auth header detected.'}`,
594
+ complexity: endpoint.method === 'GET' ? 'low' : 'medium',
595
+ };
596
+ };
597
+ /** Extract GraphQL query/mutation names from a request body sample. */
598
+ const graphqlQuerySuggestions = (endpoint) => {
599
+ if (!endpoint.requestBodySample)
600
+ return [];
601
+ try {
602
+ const body = JSON.parse(endpoint.requestBodySample);
603
+ const query = body.query;
604
+ if (typeof query !== 'string')
605
+ return [];
606
+ // Extract operation name: query OperationName { or mutation OperationName {
607
+ const match = /(?:query|mutation)\s+(\w+)/.exec(query);
608
+ if (match?.[1]) {
609
+ const opName = match[1];
610
+ const isMutation = query.trimStart().startsWith('mutation');
611
+ return [
612
+ {
613
+ toolName: `gql_${toSnakeCase(opName)}`,
614
+ description: `${isMutation ? 'Execute' : 'Query'} ${opName}`,
615
+ approach: `POST to ${endpoint.url} with query: "${query.slice(0, 100)}..."`,
616
+ complexity: 'medium',
617
+ },
618
+ ];
619
+ }
620
+ }
621
+ catch {
622
+ // Invalid JSON
623
+ }
624
+ return [];
625
+ };
626
+ /** Extract tRPC procedure name from URL. */
627
+ const extractTrpcProcedure = (url) => {
628
+ try {
629
+ const pathname = new URL(url).pathname;
630
+ const match = /\/(?:api\/)?trpc\/(.+)$/.exec(pathname);
631
+ return match?.[1];
632
+ }
633
+ catch {
634
+ return undefined;
635
+ }
636
+ };
637
+ /** Derive a form name from its fields or action URL. */
638
+ const deriveFormName = (form) => {
639
+ // Try the action URL
640
+ if (form.action) {
641
+ try {
642
+ const pathname = new URL(form.action, 'http://dummy').pathname;
643
+ const lastSegment = pathname.split('/').filter(Boolean).pop();
644
+ if (lastSegment)
645
+ return toSnakeCase(lastSegment);
646
+ }
647
+ catch {
648
+ // Not a valid URL
649
+ }
650
+ }
651
+ // Fall back to field-based name
652
+ const hasPassword = form.fields.some(f => f.name.toLowerCase().includes('password'));
653
+ const hasEmail = form.fields.some(f => f.name.toLowerCase().includes('email'));
654
+ if (hasPassword && hasEmail)
655
+ return 'login';
656
+ if (hasPassword)
657
+ return 'auth';
658
+ if (form.fields.some(f => f.name.toLowerCase().includes('search') || f.name.toLowerCase().includes('query')))
659
+ return 'search';
660
+ return 'form';
661
+ };
662
+ /** Extract the URL path from a full URL. */
663
+ const extractPathSegments = (url) => {
664
+ try {
665
+ return new URL(url).pathname;
666
+ }
667
+ catch {
668
+ return undefined;
669
+ }
670
+ };
671
+ /** Extract a resource name from a URL path (e.g., /api/items → items). */
672
+ const extractResourceName = (path) => {
673
+ const segments = path.split('/').filter(Boolean);
674
+ // Find the last non-version, non-api segment
675
+ for (let i = segments.length - 1; i >= 0; i--) {
676
+ const segment = segments[i];
677
+ if (!segment)
678
+ continue;
679
+ if (/^(api|v\d+|graphql|trpc|rpc)$/i.test(segment))
680
+ continue;
681
+ // Skip segments that look like IDs (all digits or UUIDs)
682
+ if (/^\d+$/.test(segment) || /^[0-9a-f-]{36}$/i.test(segment))
683
+ continue;
684
+ return toSnakeCase(segment);
685
+ }
686
+ return undefined;
687
+ };
688
+ /** Convert an HTTP method to a verb for tool naming. */
689
+ const httpMethodToVerb = (method) => {
690
+ switch (method.toUpperCase()) {
691
+ case 'GET':
692
+ return 'list';
693
+ case 'POST':
694
+ return 'create';
695
+ case 'PUT':
696
+ case 'PATCH':
697
+ return 'update';
698
+ case 'DELETE':
699
+ return 'delete';
700
+ default:
701
+ return method.toLowerCase();
702
+ }
703
+ };
704
+ /** Convert a string to snake_case. */
705
+ const toSnakeCase = (str) => str
706
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
707
+ .replace(/[-\s]+/g, '_')
708
+ .toLowerCase();
709
+ /** Capitalize the first letter of a string. */
710
+ const capitalizeFirst = (str) => {
711
+ if (str.length === 0)
712
+ return str;
713
+ const first = str[0];
714
+ if (!first)
715
+ return str;
716
+ return first.toUpperCase() + str.slice(1);
717
+ };
718
+ // ---------------------------------------------------------------------------
719
+ // Orchestrator
720
+ // ---------------------------------------------------------------------------
721
+ /** Default wait time (seconds) for network activity after page load. */
722
+ const DEFAULT_WAIT_SECONDS = 5;
723
+ /**
724
+ * Orchestrate a comprehensive site analysis.
725
+ *
726
+ * Flow:
727
+ * 1. Open tab (or reuse) and navigate to URL
728
+ * 2. Enable network capture
729
+ * 3. Wait for API calls (configurable wait time)
730
+ * 4. Run detection scripts in page context (parallel where possible)
731
+ * 5. Get captured network requests
732
+ * 6. Disable network capture
733
+ * 7. Pass collected data through detection modules
734
+ * 8. Generate tool suggestions
735
+ * 9. Return structured result
736
+ */
737
+ const analyzeSite = async (state, url, waitSeconds = DEFAULT_WAIT_SECONDS) => {
738
+ // Step 1: Open a new tab
739
+ const openResult = (await dispatchToExtension(state, 'browser.openTab', { url }));
740
+ const tabId = openResult.id;
741
+ try {
742
+ // Step 2: Enable network capture
743
+ await dispatchToExtension(state, 'browser.enableNetworkCapture', {
744
+ tabId,
745
+ maxRequests: 200,
746
+ });
747
+ // Step 3: Wait for page load and API calls
748
+ await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));
749
+ // Step 4: Run detection scripts in the page context sequentially.
750
+ // Sequential execution avoids the extension's per-method rate limit
751
+ // (max 10 browser.executeScript calls per second) and is reliable
752
+ // since each script completes quickly (~5-50ms in page context).
753
+ const csrfTokens = (await executeInTab(state, tabId, CSRF_SCRIPT));
754
+ const globalsAuth = (await executeInTab(state, tabId, GLOBALS_AUTH_SCRIPT));
755
+ const frameworkProbes = (await executeInTab(state, tabId, FRAMEWORK_PROBE_SCRIPT));
756
+ const spaSsrProbe = (await executeInTab(state, tabId, SPA_SSR_PROBE_SCRIPT));
757
+ const globalsScan = (await executeInTab(state, tabId, GLOBALS_SCAN_SCRIPT));
758
+ const forms = (await executeInTab(state, tabId, FORMS_SCRIPT));
759
+ const interactiveElements = (await executeInTab(state, tabId, INTERACTIVE_ELEMENTS_SCRIPT));
760
+ const dataAttributes = (await executeInTab(state, tabId, DATA_ATTRIBUTES_SCRIPT));
761
+ const storageKeys = (await executeInTab(state, tabId, STORAGE_KEYS_SCRIPT));
762
+ const storageEntries = (await executeInTab(state, tabId, STORAGE_ENTRIES_SCRIPT));
763
+ const pageTitle = (await executeInTab(state, tabId, 'return document.title'));
764
+ // Step 5: Get captured network requests
765
+ const networkResult = (await dispatchToExtension(state, 'browser.getNetworkRequests', {
766
+ tabId,
767
+ clear: true,
768
+ }));
769
+ const networkRequests = Array.isArray(networkResult)
770
+ ? networkResult
771
+ : (networkResult.requests ?? []);
772
+ // Step 6: Disable network capture
773
+ await dispatchToExtension(state, 'browser.disableNetworkCapture', { tabId });
774
+ // Step 7: Get cookies via extension API (includes HttpOnly cookies)
775
+ const cookieResult = (await dispatchToExtension(state, 'browser.getCookies', { url }));
776
+ const cookies = cookieResult?.cookies ?? [];
777
+ // Step 8: Run detection modules
778
+ const auth = detectAuth({
779
+ cookies,
780
+ localStorageEntries: storageEntries.localEntries,
781
+ sessionStorageEntries: storageEntries.sessionEntries,
782
+ networkRequests,
783
+ csrfDomTokens: csrfTokens,
784
+ windowGlobals: globalsAuth,
785
+ });
786
+ const apis = detectApis(networkRequests);
787
+ const frameworkAnalysis = detectFramework({
788
+ frameworkProbes,
789
+ hasSingleRootElement: spaSsrProbe.hasSingleRootElement,
790
+ usesPushState: spaSsrProbe.usesPushState,
791
+ hasNextData: spaSsrProbe.hasNextData,
792
+ hasNuxtData: spaSsrProbe.hasNuxtData,
793
+ hasHydrationMarkers: spaSsrProbe.hasHydrationMarkers,
794
+ });
795
+ const globals = detectGlobals({ globals: globalsScan });
796
+ const domAnalysis = detectDom({
797
+ forms,
798
+ interactiveElements,
799
+ dataAttributes,
800
+ });
801
+ const storage = detectStorage(storageKeys);
802
+ // Step 9: Generate suggestions
803
+ const suggestions = generateSuggestions(apis, domAnalysis, auth, frameworkAnalysis);
804
+ return {
805
+ url,
806
+ title: pageTitle,
807
+ auth,
808
+ apis,
809
+ framework: frameworkAnalysis,
810
+ globals,
811
+ dom: domAnalysis,
812
+ storage,
813
+ suggestions,
814
+ };
815
+ }
816
+ finally {
817
+ // Clean up: close the tab
818
+ try {
819
+ await dispatchToExtension(state, 'browser.closeTab', { tabId });
820
+ }
821
+ catch {
822
+ // Best-effort cleanup — ignore errors
823
+ }
824
+ }
825
+ };
826
+ export { analyzeSite };
827
+ //# sourceMappingURL=index.js.map