sourcemonkey 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. package/skills/SourceMonkey-friction/references/template.md +59 -0
@@ -0,0 +1,141 @@
1
+ // `@version` ordering.
2
+ //
3
+ // Userscript versions are not semver — managers accept whatever the
4
+ // author writes. Tampermonkey compares them as dot-separated segments
5
+ // with an optional `-prerelease` tail, and that's what we implement:
6
+ // enough structure to order the versions real scripts use, with a
7
+ // deterministic answer for the ones that don't fit.
8
+ //
9
+ // Pure module — no DOM, no chrome.*, no fetching. The only consumer
10
+ // that needs a verdict rather than an ordering is the UI, which uses
11
+ // `versionRelation`.
12
+ // Splits `1.2.3-beta.4` into `{ main: ['1','2','3'], pre: ['beta','4'] }`.
13
+ // Only the FIRST `-` starts the prerelease tail, so `1.0-rc-2` has a
14
+ // two-segment-free prerelease of `rc-2` rather than nesting further.
15
+ //
16
+ // A `+build` tail is dropped first, per semver: build metadata carries
17
+ // no precedence. Without this, `1.0+b2` vs `1.0` would compare its last
18
+ // main segment as `0+b2` vs `0` and report the metadata-only change as
19
+ // a newer version — a false OUTDATED.
20
+ function splitVersion(v) {
21
+ const plus = v.indexOf('+');
22
+ const trimmed = (plus < 0 ? v : v.slice(0, plus)).trim();
23
+ const dash = trimmed.indexOf('-');
24
+ const mainPart = dash < 0 ? trimmed : trimmed.slice(0, dash);
25
+ const prePart = dash < 0 ? '' : trimmed.slice(dash + 1);
26
+ return {
27
+ main: mainPart.split('.'),
28
+ pre: prePart ? prePart.split('.') : [],
29
+ };
30
+ }
31
+ const NUMERIC_RE = /^\d+$/;
32
+ // Compare one segment pair. Numeric-vs-numeric compares as numbers so
33
+ // `10` beats `9`; anything else falls back to code-unit order, which
34
+ // happens to put digits below letters — the same direction semver
35
+ // specifies for a numeric-vs-alphanumeric identifier.
36
+ //
37
+ // A consequence worth knowing: `1.0-beta` sorts *below* `1.0` (the
38
+ // prerelease rule) but `1.0.beta` sorts *above* it (padded `'0'` vs
39
+ // `'b'` here). Both follow from the rules as stated; only the `-` form
40
+ // is a prerelease.
41
+ function compareSegment(a, b) {
42
+ if (NUMERIC_RE.test(a) && NUMERIC_RE.test(b)) {
43
+ const x = Number(a);
44
+ const y = Number(b);
45
+ return x < y ? -1 : x > y ? 1 : 0;
46
+ }
47
+ return a < b ? -1 : a > b ? 1 : 0;
48
+ }
49
+ // Missing trailing segments read as `0`, so `1.2` and `1.2.0` are the
50
+ // same version. Authors bump `1.2` → `1.2.1` all the time and expect
51
+ // exactly that.
52
+ function compareMain(a, b) {
53
+ const len = Math.max(a.length, b.length);
54
+ for (let i = 0; i < len; i++) {
55
+ const cmp = compareSegment(a[i] ?? '0', b[i] ?? '0');
56
+ if (cmp !== 0)
57
+ return cmp;
58
+ }
59
+ return 0;
60
+ }
61
+ // Prerelease tails follow semver's precedence rules: a release outranks
62
+ // any prerelease of the same main version (`1.0` > `1.0-rc1`), and
63
+ // between two prereleases the one that runs out of segments first is
64
+ // lower (`1.0-rc` < `1.0-rc.2`).
65
+ function comparePre(a, b) {
66
+ if (a.length === 0 && b.length === 0)
67
+ return 0;
68
+ if (a.length === 0)
69
+ return 1;
70
+ if (b.length === 0)
71
+ return -1;
72
+ const len = Math.max(a.length, b.length);
73
+ for (let i = 0; i < len; i++) {
74
+ const sa = a[i];
75
+ const sb = b[i];
76
+ if (sa === undefined)
77
+ return -1;
78
+ if (sb === undefined)
79
+ return 1;
80
+ const cmp = compareSegment(sa, sb);
81
+ if (cmp !== 0)
82
+ return cmp;
83
+ }
84
+ return 0;
85
+ }
86
+ // Total order over version strings. Always returns an ordering — there
87
+ // is no "incomparable" verdict, because every caller ultimately has to
88
+ // pick newer / older / same and a null would just push the arbitrary
89
+ // choice up a layer. An empty string sorts below every non-empty one.
90
+ export function compareVersions(a, b) {
91
+ const ta = a.trim();
92
+ const tb = b.trim();
93
+ if (!ta || !tb) {
94
+ if (ta === tb)
95
+ return 0;
96
+ return ta ? 1 : -1;
97
+ }
98
+ const va = splitVersion(ta);
99
+ const vb = splitVersion(tb);
100
+ const main = compareMain(va.main, vb.main);
101
+ if (main !== 0)
102
+ return main;
103
+ return comparePre(va.pre, vb.pre);
104
+ }
105
+ // The prerelease tag a local copy's `@version` carries: `13.2` copied
106
+ // for editing becomes `13.2-local`. Lives here, next to the comparison
107
+ // that has to recognise it — see `versionRelation`.
108
+ export const LOCAL_EDIT_TAG = 'local';
109
+ // The `@version` a local copy is given: the original with `-local`
110
+ // appended. Always a plain append — `splitVersion` keys on the FIRST
111
+ // `-`, so `1.0-beta` becomes `1.0-beta-local`, whose single prerelease
112
+ // segment `beta-local` string-compares above `beta` and stays below
113
+ // the `1.0` release. That copy therefore reads as newer than its
114
+ // source, the ordinary "local edit running ahead" case; only a source
115
+ // with no tail needs the `edited` relation below.
116
+ export function localEditVersion(version) {
117
+ return `${version.trim()}-${LOCAL_EDIT_TAG}`;
118
+ }
119
+ // True when `version` is a local edit per `localEditVersion`: any
120
+ // prerelease segment that is, or ends in, `-local`.
121
+ export function isLocalEditVersion(version) {
122
+ const { pre } = splitVersion(version.trim());
123
+ return pre.some((seg) => seg === LOCAL_EDIT_TAG || seg.endsWith(`-${LOCAL_EDIT_TAG}`));
124
+ }
125
+ export function versionRelation(loaded, other) {
126
+ if (!loaded?.trim() || !other?.trim())
127
+ return 'unknown';
128
+ const cmp = compareVersions(other, loaded);
129
+ if (cmp > 0)
130
+ return isEditOf(loaded, other) ? 'edited' : 'newer';
131
+ if (cmp < 0)
132
+ return 'older';
133
+ return 'same';
134
+ }
135
+ // `loaded` is a local edit of `other`: same main segments, `other` has
136
+ // no prerelease tail, and ours is a `-local` one.
137
+ function isEditOf(loaded, other) {
138
+ const vl = splitVersion(loaded.trim());
139
+ const vo = splitVersion(other.trim());
140
+ return vo.pre.length === 0 && compareMain(vl.main, vo.main) === 0 && isLocalEditVersion(loaded);
141
+ }
@@ -0,0 +1,120 @@
1
+ // Update probes: "what `@version` does the script's upstream claim?"
2
+ //
3
+ // A probe is deliberately NOT a refresh. A refresh fetches the bytes we
4
+ // actually run, from the URL the user installed from, and can change a
5
+ // script's status. A probe fetches somebody else's copy, reads one
6
+ // header line out of it, and changes nothing but a display annotation.
7
+ // Keeping them apart is the whole design: a `@downloadURL` pointing at
8
+ // an origin the user never approved must never become an update
9
+ // channel, and an unreachable probe host must never disable a working
10
+ // script.
11
+ //
12
+ // What gets probed: only `@updateURL` / `@downloadURL` values that
13
+ // differ from the URL we load the script from. We already auto-update
14
+ // from our own collection — local files via the scan's size+mtime check,
15
+ // http(s) via a conditional GET — so probing it would be a slower way
16
+ // to learn what the refresh that just ran already established.
17
+ //
18
+ // That leaves exactly the interesting case: a copy whose official home
19
+ // is somewhere we don't fetch from. We can't update it automatically,
20
+ // so all we can do is say so. See [docs/updates.md].
21
+ import { getValue } from './userscript-header.js';
22
+ import { versionRelation } from './version-compare.js';
23
+ // How long a recorded probe stays fresh. Probes hit third-party hosts
24
+ // on somebody else's page load, and an upstream release is a
25
+ // once-in-weeks event, so this is deliberately hours rather than the
26
+ // seconds the collection-refresh gate uses.
27
+ export const PROBE_MIN_INTERVAL_MS = 6 * 60 * 60 * 1000;
28
+ // Canonical form for comparing a probe URL against the script's own
29
+ // location. Returns null for anything that isn't an absolute http(s)
30
+ // URL: a relative `@updateURL` has no meaning (updates come from a
31
+ // fixed home, not from wherever this copy happens to sit), and a
32
+ // `file://` one would turn a header line into a host-file read.
33
+ function canonicalProbeUrl(raw) {
34
+ if (!raw?.trim())
35
+ return null;
36
+ try {
37
+ const u = new URL(raw.trim());
38
+ return u.protocol === 'http:' || u.protocol === 'https:' ? u.toString() : null;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ // The distinct upstream URLs worth asking about, in `update`-first
45
+ // order. Deduped by canonical URL, so the common GreasyFork shape
46
+ // (`@downloadURL` = the `.user.js` we installed from, `@updateURL` =
47
+ // the sibling `.meta.js`) yields one target, not two.
48
+ export function probeTargets(header, fileUrl) {
49
+ if (!header)
50
+ return [];
51
+ // A `file://` script canonicalizes to null and simply never matches
52
+ // an http(s) target, which is the right answer.
53
+ const own = canonicalProbeUrl(fileUrl);
54
+ const byUrl = new Map();
55
+ for (const kind of ['update', 'download']) {
56
+ const url = canonicalProbeUrl(getValue(header, `${kind}URL`));
57
+ if (!url || url === own)
58
+ continue;
59
+ const existing = byUrl.get(url);
60
+ if (existing)
61
+ existing.kinds.push(kind);
62
+ else
63
+ byUrl.set(url, { url, kinds: [kind] });
64
+ }
65
+ return [...byUrl.values()];
66
+ }
67
+ // Whether the stored probes still stand in for a fresh check. Stale in
68
+ // either direction, because both mean the stored set no longer
69
+ // describes what the script declares:
70
+ //
71
+ // - a target with no row, or a row aged past the interval — the
72
+ // normal case, and what makes a newly-added `@updateURL` get
73
+ // probed on the next check rather than in six hours;
74
+ // - a row whose URL is no longer a target — the script dropped that
75
+ // header, and leaving the row would keep reporting OUTDATED
76
+ // against a URL it no longer names.
77
+ export function probesAreFresh(probes, targets, now) {
78
+ const stored = probes ?? [];
79
+ if (targets.length === 0)
80
+ return stored.length === 0;
81
+ const targetUrls = new Set(targets.map((t) => t.url));
82
+ if (stored.some((p) => !targetUrls.has(p.url)))
83
+ return false;
84
+ const byUrl = new Map(stored.map((p) => [p.url, p]));
85
+ return targets.every((t) => {
86
+ const probe = byUrl.get(t.url);
87
+ return probe !== undefined && now - probe.checkedAt < PROBE_MIN_INTERVAL_MS;
88
+ });
89
+ }
90
+ // The probe that says our copy is behind, or null when none does.
91
+ // Picks the highest version among those ahead of us, so a script whose
92
+ // update and download URLs disagree is judged against the further-ahead
93
+ // one — being told about the smaller gap would understate it.
94
+ export function outdatedProbe(loadedVersion, probes) {
95
+ let furthestAhead = null;
96
+ for (const probe of probes ?? []) {
97
+ if (versionRelation(loadedVersion, probe.version) !== 'newer')
98
+ continue;
99
+ if (!furthestAhead || versionRelation(furthestAhead.version, probe.version) === 'newer') {
100
+ furthestAhead = probe;
101
+ }
102
+ }
103
+ return furthestAhead;
104
+ }
105
+ // Row label for a probe: "update URL" or "download URL".
106
+ //
107
+ // When ONE url is named by both headers the label says "update URL"
108
+ // rather than spelling out both. `@updateURL` is the header that means
109
+ // "check here for a new version", so it is the accurate name for what
110
+ // we did; `@downloadURL` naming the same file is a detail of the
111
+ // script's headers, not of the check. Spelling out both made the
112
+ // longest label in the panel, for a distinction the reader can't act
113
+ // on — the row's tooltip carries the URL either way.
114
+ //
115
+ // No ambiguity with a plain update probe: the combined form only
116
+ // exists when the two headers resolve to the same URL, and two probes
117
+ // for one URL is exactly what it's collapsing.
118
+ export function probeLabel(kinds) {
119
+ return kinds.includes('update') ? 'update URL' : 'download URL';
120
+ }
@@ -0,0 +1 @@
1
+ {"source":"/home/jshute/dev/SourceMonkey3"}
package/lib/bytes.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function sha256Hex(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>): Promise<string>;
2
+ export declare function base64ToBytes(base64: string): ArrayBuffer;
3
+ export declare function bytesToBase64(bytes: Uint8Array): string;
package/lib/bytes.js ADDED
@@ -0,0 +1,37 @@
1
+ // Small byte / crypto helpers shared across modules so the SHA-256 and
2
+ // binary→base64 logic lives in one place (script content hashes in
3
+ // script-store, @require/@resource byte hashes + transport in
4
+ // external-files, GM_xmlhttpRequest response transport in gm-xhr).
5
+ // SHA-256 → lowercase hex. Accepts a string (UTF-8 encoded first) or
6
+ // bytes. Async because SubtleCrypto has no sync form on the web
7
+ // platform.
8
+ export async function sha256Hex(data) {
9
+ const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
10
+ const digest = await crypto.subtle.digest('SHA-256', bytes);
11
+ let out = '';
12
+ for (const b of new Uint8Array(digest))
13
+ out += b.toString(16).padStart(2, '0');
14
+ return out;
15
+ }
16
+ // base64→binary. The inverse of `bytesToBase64`, used on the receiving
17
+ // end of message hops that can't carry an ArrayBuffer, and to rebuild a
18
+ // stored `@resource` body when a conditional GET answers 304 and the
19
+ // server sends no bytes of its own.
20
+ export function base64ToBytes(base64) {
21
+ const binary = atob(base64);
22
+ const bytes = new Uint8Array(binary.length);
23
+ for (let i = 0; i < binary.length; i++)
24
+ bytes[i] = binary.charCodeAt(i);
25
+ return bytes.buffer;
26
+ }
27
+ // Chunked binary→base64. `String.fromCharCode(...hugeArray)` throws
28
+ // "Maximum call stack size exceeded" past ~125k args in V8, so we walk
29
+ // the buffer in 32 KiB slices and concatenate. Linear and bounded.
30
+ export function bytesToBase64(bytes) {
31
+ let binary = '';
32
+ const CHUNK = 0x8000;
33
+ for (let i = 0; i < bytes.length; i += CHUNK) {
34
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
35
+ }
36
+ return btoa(binary);
37
+ }
@@ -0,0 +1 @@
1
+ export declare const CHANNEL_KEY_PLACEHOLDER = "__SM_KEY__";
@@ -0,0 +1,5 @@
1
+ // The stand-in for the channel key in the static wrapper files
2
+ // (src/extension/channel.ts swaps the real key in when it registers
3
+ // them). Its own module so the harness, which loads a wrapper under
4
+ // Node, can call the registry gate with it.
5
+ export const CHANNEL_KEY_PLACEHOLDER = '__SM_KEY__';
@@ -0,0 +1,8 @@
1
+ export interface FoundScript {
2
+ name: string;
3
+ relativePath: string;
4
+ size?: number;
5
+ mtime?: number;
6
+ }
7
+ export declare const MAX_SCAN_DEPTH = 2;
8
+ export declare const MANIFEST_FILENAME = "script_manifest.json";
@@ -0,0 +1,17 @@
1
+ // Shared scan primitives used by every collection-kind scanner.
2
+ //
3
+ // Lives in its own module because both `directories.ts` (local
4
+ // directory / manifest scanner) and `github-provider.ts` (Contents-API
5
+ // scanner) need them — importing them directly between the two
6
+ // scanner modules would create a runtime ES-module cycle, since
7
+ // `directories.ts` also imports the github dispatcher from
8
+ // `github-provider.ts`.
9
+ // How many subdirectory levels below the root a scanner walks before
10
+ // stopping. Applies to local directory collections AND GitHub directory
11
+ // collections (the cost model is different but the cap keeps both
12
+ // bounded).
13
+ export const MAX_SCAN_DEPTH = 2;
14
+ // The one and only filename SourceMonkey recognises as a userscript
15
+ // manifest. Lives here (not in `directories.ts`) so `github-provider.ts`
16
+ // can import it without forming a runtime cycle with `directories.ts`.
17
+ export const MANIFEST_FILENAME = 'script_manifest.json';
@@ -0,0 +1,28 @@
1
+ import type { StoredScript } from './script-build.js';
2
+ import type { MappedSegment } from './source-map.js';
3
+ export interface ComposedSource {
4
+ text: string;
5
+ segments: MappedSegment[];
6
+ lineCount: number;
7
+ mainStart: number;
8
+ }
9
+ export interface ComposeOptions {
10
+ requireUrls?: readonly string[];
11
+ mainUrl?: string;
12
+ startLine?: number;
13
+ }
14
+ export declare const USER_SOURCE_START_LINE = 1;
15
+ export declare function composeSource(script: StoredScript, opts?: ComposeOptions): ComposedSource;
16
+ export interface StackLayout {
17
+ entryUrl: string;
18
+ segments: readonly MappedSegment[];
19
+ lineCount: number;
20
+ }
21
+ export declare function stackLayout(script: StoredScript, logLabel: string): StackLayout;
22
+ export declare function composeRequiredBody(script: StoredScript): string;
23
+ export interface ParseCheckSource {
24
+ text: string;
25
+ segments: MappedSegment[];
26
+ lineCount: number;
27
+ }
28
+ export declare function buildParseCheckSource(script: StoredScript): ParseCheckSource;
@@ -0,0 +1,205 @@
1
+ // ---------- Concatenated user source (requires + body) ----------
2
+ //
3
+ // Every prefetched `@require` body, in declaration order, in front of
4
+ // the **full on-disk script source** (leadingText + headerText +
5
+ // bodyText). This is the single arrangement every run path uses:
6
+ //
7
+ // - the default registration (`wrapCombinedEntry` → one js[] entry);
8
+ // - the reload-fast-path swap eval (`buildSwapEvalBody`), where
9
+ // `handleCheckPending` in background.ts sends the same text as the
10
+ // run-new body.
11
+ //
12
+ // Concatenation IS the feature, not an implementation detail: one
13
+ // script means one lexical scope, so a library's top-level
14
+ // `const`/`let`/`class` is simply visible to the main body — the same
15
+ // thing TM/VM get by concatenating into one IIFE. See
16
+ // docs/injection-modes.md for what the separate-entries arrangement
17
+ // cost us instead.
18
+ //
19
+ // A `;\n` separator between segments guards the seam against ASI
20
+ // (Automatic Semicolon Insertion). ASI won't fire when the next line
21
+ // could continue the previous statement, so a library ending in an
22
+ // expression followed by a body opening with `(`, `[`, `` ` ``, `+`,
23
+ // `-` or `/` would parse as a single statement. Each file was fine on
24
+ // its own; concatenation is what creates the hazard.
25
+ //
26
+ // The leading `// @require <url>` per require makes the concatenated
27
+ // code easier to scan when no source map is applied. Line comment (not
28
+ // block) so URLs with `*/` in their path don't prematurely close it.
29
+ //
30
+ // ---------- Why this lives in its own module ----------
31
+ //
32
+ // Several callers need to agree on the layout, and they live in
33
+ // different extension contexts:
34
+ //
35
+ // - `src/extension/injector.ts` (service worker) emits the registered entry
36
+ // and its source map.
37
+ // - `background.ts`'s swap path emits the same text for `(0,eval)`.
38
+ // - `src/extension/userscript.ts` (the viewer page) re-derives the SAME
39
+ // segment list to map `Error.stack` line numbers in the Log tab
40
+ // back to the original file — V8 leaves `err.stack` in generated
41
+ // coordinates even when it has the map. See docs/script-log.md.
42
+ //
43
+ // The viewer recomputes rather than reading a persisted map because
44
+ // the inputs are already on the `StoredScript` row it has loaded, and
45
+ // a persisted copy would be one more thing to invalidate. Sharing this
46
+ // one function is what keeps "where line N came from" a single answer
47
+ // instead of two that can drift.
48
+ import { joinFullSource } from './userscript-header.js';
49
+ import { buildRequireSourceUrl, buildSourceUrl } from './script-source-url.js';
50
+ // ---------- The two layouts, and why the viewer can tell them apart ----------
51
+ //
52
+ // **With `@require`s.** The preamble (GM bindings, prelude, wrap head)
53
+ // occupies generated line 0 on its own and the concatenated user
54
+ // source starts at `USER_SOURCE_START_LINE`. Line numbers shift, so
55
+ // the entry carries a source map and the viewer remaps stack refs
56
+ // through the segment list.
57
+ //
58
+ // **Without `@require`s.** Nothing is concatenated, so nothing may
59
+ // shift: the body's first line SHARES generated line 0 with the
60
+ // preamble, making generated line N equal original line N. No map is
61
+ // emitted and the viewer passes refs through untouched.
62
+ //
63
+ // The viewer distinguishes the two by whether there are segments at
64
+ // all — `composeSource` returns none for a script without requires —
65
+ // so it never has to know which run path produced the code. That only
66
+ // holds while `wrapCombinedEntry` and `buildSwapEvalBody` agree on
67
+ // BOTH layouts. They are the only two producers; keep them in step.
68
+ //
69
+ // The no-require layout costs one thing, deliberately: the body's
70
+ // first line shares a physical line with the preamble, so a frame
71
+ // reported at line 1 carries a column offset by the preamble's length.
72
+ // Line 1 of a userscript is its `// ==UserScript==` marker, so this
73
+ // has never been observed; the code view suppresses the column accent
74
+ // there rather than showing a wrong one.
75
+ export const USER_SOURCE_START_LINE = 1;
76
+ export function composeSource(script, opts = {}) {
77
+ const mainSource = joinFullSource(script.leadingText ?? '', script.headerText ?? '', script.bodyText);
78
+ const requires = script.requires ?? [];
79
+ const startLine = opts.startLine ?? 0;
80
+ if (requires.length === 0) {
81
+ return {
82
+ text: mainSource,
83
+ segments: [],
84
+ lineCount: countLines(mainSource),
85
+ mainStart: 0,
86
+ };
87
+ }
88
+ // Build the text incrementally and derive every line number from
89
+ // what has ACTUALLY been emitted, rather than adding up expected
90
+ // line counts alongside a `join()`. The two drift: a require whose
91
+ // source ends in a lone `\r` fuses with a joining `\n` into a single
92
+ // CRLF, so one terminator appears where the arithmetic assumed two
93
+ // and every mapping from that seam onward — including the whole user
94
+ // body — shifts by a line. CR-only line endings are exactly the
95
+ // population `\r` is in `LINE_TERMINATORS` for, and nothing
96
+ // normalizes newlines on the way in (file-loader stores the decoded
97
+ // bytes verbatim).
98
+ const segments = [];
99
+ let text = '';
100
+ // 0-based generated line that the NEXT character appended will land
101
+ // on. `countLines('')` is 1, so an empty buffer is line 0.
102
+ const nextLine = () => startLine + countLines(text) - 1;
103
+ for (const [i, r] of requires.entries()) {
104
+ text += `// @require ${r.url}\n`;
105
+ const url = opts.requireUrls?.[i];
106
+ if (url) {
107
+ segments.push({
108
+ url,
109
+ content: r.source,
110
+ generatedLine: nextLine(),
111
+ lineCount: countLines(r.source),
112
+ });
113
+ }
114
+ // The trailing-`\r` guard: append the extra `\n` here so the
115
+ // source's own last line stays a line of its own, instead of being
116
+ // swallowed by the separator that follows.
117
+ text += r.source.endsWith('\r') ? `${r.source}\n` : r.source;
118
+ // Terminate the source's last line, then the `;` seam guard on a
119
+ // line of its own.
120
+ text += '\n;\n';
121
+ }
122
+ if (opts.mainUrl) {
123
+ segments.push({
124
+ url: opts.mainUrl,
125
+ content: mainSource,
126
+ generatedLine: nextLine(),
127
+ lineCount: countLines(mainSource),
128
+ });
129
+ }
130
+ const mainStart = text.length;
131
+ text += mainSource;
132
+ return { text, segments, lineCount: countLines(text), mainStart };
133
+ }
134
+ export function stackLayout(script, logLabel) {
135
+ const entryUrl = buildSourceUrl(logLabel);
136
+ const { segments, lineCount } = composeSource(script, {
137
+ requireUrls: (script.requires ?? []).map((r) => buildRequireSourceUrl(logLabel, r.url)),
138
+ mainUrl: entryUrl,
139
+ startLine: USER_SOURCE_START_LINE,
140
+ });
141
+ return { entryUrl, segments, lineCount };
142
+ }
143
+ // Count lines the way V8 does, which is what the source map has to
144
+ // agree with. Counting only `\n` silently shifts every mapping after
145
+ // the offending character:
146
+ //
147
+ // - a lone `\r` is a line terminator (files with mangled endings);
148
+ // - so are U+2028 / U+2029, and V8 counts a RAW U+2028 as a break
149
+ // even inside a string literal (legal since ES2019, and minified
150
+ // CDN bundles do embed them).
151
+ //
152
+ // `\r\n` must count as one, hence the alternation order.
153
+ const LINE_TERMINATORS = /\r\n|[\n\r\u2028\u2029]/g;
154
+ // `buildParseCheckSource` below measures its own preamble with it: a
155
+ // parse-check line offset that counted terminators differently from the
156
+ // segment list it is compared against would misattribute every error in
157
+ // the file.
158
+ function countLines(text) {
159
+ if (text === '')
160
+ return 1;
161
+ const matches = text.match(LINE_TERMINATORS);
162
+ return matches ? matches.length + 1 : 1;
163
+ }
164
+ // Public entry point for the swap-eval path, which needs the text only.
165
+ export function composeRequiredBody(script) {
166
+ return composeSource(script).text;
167
+ }
168
+ export function buildParseCheckSource(script) {
169
+ // A blank first line of our own, so the user's first line is never at
170
+ // offset 0: a `#!` hashbang is legal only there, so it stays the
171
+ // syntax error it also is in the real injected entry (which wraps the
172
+ // source), and the location probes can't be handed a source that
173
+ // parses as a shebang script. Blank, not a statement, so a leading
174
+ // `"use strict"` still applies, as in the injected entry (see
175
+ // USER_SCOPE_OPEN in injector.ts). The offset below is derived from it rather than
176
+ // assumed to be 1 — the same discipline `composeSource` applies
177
+ // internally, and for the same reason: assumed line counts drift
178
+ // from emitted ones.
179
+ const preamble = '\n';
180
+ const startLine = countLines(preamble) - 1;
181
+ const requires = script.requires ?? [];
182
+ const composed = composeSource(script, {
183
+ requireUrls: requires.map((r) => r.url),
184
+ mainUrl: script.relativePath,
185
+ startLine,
186
+ });
187
+ return {
188
+ text: preamble + composed.text,
189
+ // `composeSource` emits no segments for a script with no
190
+ // `@require`s — the viewer keys "pass stack refs through unmapped"
191
+ // off exactly that, so it stays as it is. Here the opposite is
192
+ // wanted: one segment for the body means every reported line
193
+ // resolves through the same lookup, with no second code path for
194
+ // the majority of scripts.
195
+ segments: composed.segments.length > 0
196
+ ? composed.segments
197
+ : [{
198
+ url: script.relativePath,
199
+ content: composed.text,
200
+ generatedLine: startLine,
201
+ lineCount: composed.lineCount,
202
+ }],
203
+ lineCount: startLine + composed.lineCount,
204
+ };
205
+ }
@@ -0,0 +1,9 @@
1
+ export declare const DEBUG_LOGGING_KEY = "smDebugLogging";
2
+ export declare function isDebugLoggingEnabled(): boolean;
3
+ export declare function _setDebugLoggingForTests(value: boolean): void;
4
+ export declare function loadDebugLoggingFlag(): Promise<void>;
5
+ export declare function debugLoggingPending(): boolean;
6
+ export declare function debugLoggingSettled(): Promise<boolean>;
7
+ export declare function installDebugLoggingListener(): void;
8
+ export declare function dlog(...args: unknown[]): void;
9
+ export declare function dwarn(...args: unknown[]): void;
@@ -0,0 +1,75 @@
1
+ // Debug-only console logging gate.
2
+ //
3
+ // Several lifecycle messages (per-navigation match list, per-refresh
4
+ // "update check" / "no change") are useful when diagnosing matcher or
5
+ // updater behavior but far too noisy for normal use — they fire on
6
+ // every click. They're off by default and gated through `dlog` /
7
+ // `dwarn`. Flip on by setting `chrome.storage.local`'s
8
+ // `smDebugLogging` key to `true` (e.g. from the SW DevTools console:
9
+ // `chrome.storage.local.set({ smDebugLogging: true })`).
10
+ export const DEBUG_LOGGING_KEY = 'smDebugLogging';
11
+ let enabled = false;
12
+ export function isDebugLoggingEnabled() {
13
+ return enabled;
14
+ }
15
+ // Test/debug hook. Production callers should toggle the storage key
16
+ // instead so the change persists and is observed via onChanged.
17
+ export function _setDebugLoggingForTests(value) {
18
+ enabled = value;
19
+ }
20
+ // Settles once the setting has been read in this context.
21
+ let loading = null;
22
+ let loaded = false;
23
+ export function loadDebugLoggingFlag() {
24
+ loading = (async () => {
25
+ try {
26
+ const got = await chrome.storage.local.get(DEBUG_LOGGING_KEY);
27
+ enabled = got[DEBUG_LOGGING_KEY] === true;
28
+ }
29
+ catch {
30
+ enabled = false;
31
+ }
32
+ loaded = true;
33
+ })();
34
+ return loading;
35
+ }
36
+ // Whether the setting is still being read: a line about work done before
37
+ // then can wait for `debugLoggingSettled` rather than be dropped.
38
+ export function debugLoggingPending() {
39
+ return loading !== null && !loaded;
40
+ }
41
+ // Whether debug logging is on, once the setting has been read (at once
42
+ // in a context that never reads it).
43
+ export async function debugLoggingSettled() {
44
+ await loading;
45
+ return enabled;
46
+ }
47
+ // Idempotent, matching `match-index.ts`'s storage listener. Several
48
+ // contexts install this (the worker and the extension pages), and a
49
+ // context that called twice would double every flag update.
50
+ let listenerInstalled = false;
51
+ export function installDebugLoggingListener() {
52
+ if (listenerInstalled)
53
+ return;
54
+ listenerInstalled = true;
55
+ chrome.storage.onChanged.addListener((changes, areaName) => {
56
+ if (areaName !== 'local')
57
+ return;
58
+ const change = changes[DEBUG_LOGGING_KEY];
59
+ if (!change)
60
+ return;
61
+ enabled = change.newValue === true;
62
+ });
63
+ }
64
+ export function dlog(...args) {
65
+ if (enabled)
66
+ console.log(...args);
67
+ }
68
+ export function dwarn(...args) {
69
+ // console.info (not console.warn / .error) — see background.ts sm:error
70
+ // handler for the rationale: Chrome surfaces SW warn+error into the
71
+ // chrome://extensions/?errors panel, which should be reserved for
72
+ // actual extension bugs, not SourceMonkey's own diagnostic chatter.
73
+ if (enabled)
74
+ console.info(...args);
75
+ }