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.
- package/LICENSE +21 -0
- package/README.md +87 -2
- package/dist/bytes.js +37 -0
- package/dist/channel-placeholder.js +5 -0
- package/dist/collection-scan.js +17 -0
- package/dist/collections.html +353 -0
- package/dist/compare.html +358 -0
- package/dist/composed-source.js +205 -0
- package/dist/connect-prompt.html +47 -0
- package/dist/debug/icons.html +211 -0
- package/dist/debug/storage.html +163 -0
- package/dist/debug-log.js +75 -0
- package/dist/diagnostic-location.js +64 -0
- package/dist/eval-hook.js +308 -0
- package/dist/extension/background.js +5263 -0
- package/dist/extension/channel.js +133 -0
- package/dist/extension/code-links.js +187 -0
- package/dist/extension/code-tabs.js +460 -0
- package/dist/extension/collection-dialog.js +619 -0
- package/dist/extension/collection-display.js +322 -0
- package/dist/extension/collections.js +2905 -0
- package/dist/extension/compare.js +867 -0
- package/dist/extension/concurrency.js +186 -0
- package/dist/extension/connect-grants.js +109 -0
- package/dist/extension/connect-prompt-page.js +70 -0
- package/dist/extension/connect-prompt.js +266 -0
- package/dist/extension/copy-button.js +63 -0
- package/dist/extension/copy-dialog.js +502 -0
- package/dist/extension/copy-preference.js +66 -0
- package/dist/extension/debug/icons.js +203 -0
- package/dist/extension/debug/storage.js +365 -0
- package/dist/extension/deep-link.js +77 -0
- package/dist/extension/delete-dialog.js +432 -0
- package/dist/extension/delete-icon.js +40 -0
- package/dist/extension/details-tooltips.js +66 -0
- package/dist/extension/directories.js +679 -0
- package/dist/extension/export-backup.js +407 -0
- package/dist/extension/export-dialog.js +142 -0
- package/dist/extension/file-access.js +74 -0
- package/dist/extension/file-changes.js +346 -0
- package/dist/extension/frame-info.js +85 -0
- package/dist/extension/github-provider.js +336 -0
- package/dist/extension/glyph.js +84 -0
- package/dist/extension/gm-bindings.js +601 -0
- package/dist/extension/gm-download.js +419 -0
- package/dist/extension/gm-menu.js +228 -0
- package/dist/extension/gm-notification.js +141 -0
- package/dist/extension/gm-originator-map.js +23 -0
- package/dist/extension/gm-tab-data.js +121 -0
- package/dist/extension/gm-tab.js +160 -0
- package/dist/extension/gm-url-change.js +111 -0
- package/dist/extension/gm-url-scheme.js +27 -0
- package/dist/extension/gm-values.js +176 -0
- package/dist/extension/gm-window-control.js +104 -0
- package/dist/extension/gm-xhr-headers.js +333 -0
- package/dist/extension/gm-xhr.js +584 -0
- package/dist/extension/injector.js +3145 -0
- package/dist/extension/install-capture.js +277 -0
- package/dist/extension/line-diff.js +233 -0
- package/dist/extension/local-copy.js +560 -0
- package/dist/extension/local-delete.js +276 -0
- package/dist/extension/location-kinds.js +108 -0
- package/dist/extension/log-format.js +158 -0
- package/dist/extension/managed-changes.js +361 -0
- package/dist/extension/managed-items.js +57 -0
- package/dist/extension/match-index.js +375 -0
- package/dist/extension/missing-scripts.js +66 -0
- package/dist/extension/more-menu.js +109 -0
- package/dist/extension/move-dialog.js +197 -0
- package/dist/extension/offscreen.js +37 -0
- package/dist/extension/options.js +504 -0
- package/dist/extension/page-heading.js +170 -0
- package/dist/extension/page-links.js +199 -0
- package/dist/extension/parse-check-client.js +70 -0
- package/dist/extension/parse-check.js +161 -0
- package/dist/extension/parse-sandbox-host.js +172 -0
- package/dist/extension/parse-sandbox.js +175 -0
- package/dist/extension/popup-commands-open.js +41 -0
- package/dist/extension/popup-view.js +387 -0
- package/dist/extension/popup.js +703 -0
- package/dist/extension/prism-userscript.js +53 -0
- package/dist/extension/refresh-error.js +136 -0
- package/dist/extension/refresh-targets.js +160 -0
- package/dist/extension/related-versions.js +1144 -0
- package/dist/extension/resize-observer-loop.js +19 -0
- package/dist/extension/row-cache.js +387 -0
- package/dist/extension/script-blobs.js +413 -0
- package/dist/extension/script-details.js +658 -0
- package/dist/extension/script-enablement.js +285 -0
- package/dist/extension/script-identity.js +310 -0
- package/dist/extension/script-keys.js +54 -0
- package/dist/extension/script-log-id.js +155 -0
- package/dist/extension/script-log.js +290 -0
- package/dist/extension/script-rejection.js +104 -0
- package/dist/extension/script-sites.js +158 -0
- package/dist/extension/script-store.js +3507 -0
- package/dist/extension/script-updater.js +991 -0
- package/dist/extension/script-url-hash.js +61 -0
- package/dist/extension/select-all.js +37 -0
- package/dist/extension/select-open-guard.js +90 -0
- package/dist/extension/site-icon.js +1261 -0
- package/dist/extension/sm-dev-link.js +302 -0
- package/dist/extension/solo-click.js +58 -0
- package/dist/extension/sticky-numbers.js +188 -0
- package/dist/extension/storage-breakdown.js +253 -0
- package/dist/extension/storage-scan.js +86 -0
- package/dist/extension/storage-usage.js +194 -0
- package/dist/extension/structural-keys.js +29 -0
- package/dist/extension/sw-teardown.js +36 -0
- package/dist/extension/tab-status.js +403 -0
- package/dist/extension/toolbar-summary.js +105 -0
- package/dist/extension/user-scripts-access.js +43 -0
- package/dist/extension/userscript.js +3672 -0
- package/dist/extension/value-store.js +204 -0
- package/dist/extension/version-probe-fetch.js +68 -0
- package/dist/extension/worker-client.js +109 -0
- package/dist/extension/worker-ops.js +171 -0
- package/dist/extension/zip-writer.js +24 -0
- package/dist/external-files.js +878 -0
- package/dist/file-loader.js +436 -0
- package/dist/file-locations.js +439 -0
- package/dist/generated-wrappers.js +5 -0
- package/dist/github-auth.js +148 -0
- package/dist/github-urls.js +46 -0
- package/dist/gm-api-catalog.js +217 -0
- package/dist/gm-connect.js +273 -0
- package/dist/gm-xhr-types.js +5 -0
- package/dist/http-read.js +142 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/local-file-overrides.js +558 -0
- package/dist/manifest.json +38 -0
- package/dist/markup-response.js +126 -0
- package/dist/match-targets.js +305 -0
- package/dist/menu-captions.js +22 -0
- package/dist/offscreen.html +39 -0
- package/dist/options.html +197 -0
- package/dist/parse-check-protocol.js +51 -0
- package/dist/parse-location.js +171 -0
- package/dist/parse-sandbox.html +56 -0
- package/dist/phase-timing.js +155 -0
- package/dist/popup.html +197 -0
- package/dist/public-suffixes.js +6957 -0
- package/dist/run-context.js +31 -0
- package/dist/runtime-messages.js +34 -0
- package/dist/sandboxed-page.js +35 -0
- package/dist/script-build.js +787 -0
- package/dist/script-display.js +383 -0
- package/dist/script-icon.js +43 -0
- package/dist/script-links.js +204 -0
- package/dist/script-source-url.js +214 -0
- package/dist/script-world.js +65 -0
- package/dist/sm-dev-protocol.js +84 -0
- package/dist/source-map.js +177 -0
- package/dist/stack-link.js +138 -0
- package/dist/styles.css +825 -0
- package/dist/unsafe-window-use.js +229 -0
- package/dist/url-match.js +734 -0
- package/dist/userscript-header.js +248 -0
- package/dist/userscript.html +1165 -0
- package/dist/vendor/fflate.LICENSE +21 -0
- package/dist/vendor/fflate.js +2692 -0
- package/dist/vendor/prism/prism-line-numbers.css +1 -0
- package/dist/vendor/prism/prism-line-numbers.js +1 -0
- package/dist/vendor/prism/prism.css +1 -0
- package/dist/vendor/prism/prism.js +1946 -0
- package/dist/version-compare.js +141 -0
- package/dist/version-probe.js +120 -0
- package/lib/build-info.json +1 -0
- package/lib/bytes.d.ts +3 -0
- package/lib/bytes.js +37 -0
- package/lib/channel-placeholder.d.ts +1 -0
- package/lib/channel-placeholder.js +5 -0
- package/lib/collection-scan.d.ts +8 -0
- package/lib/collection-scan.js +17 -0
- package/lib/composed-source.d.ts +28 -0
- package/lib/composed-source.js +205 -0
- package/lib/debug-log.d.ts +9 -0
- package/lib/debug-log.js +75 -0
- package/lib/diagnostic-location.d.ts +8 -0
- package/lib/diagnostic-location.js +64 -0
- package/lib/eval-hook.d.ts +34 -0
- package/lib/eval-hook.js +308 -0
- package/lib/external-files.d.ts +111 -0
- package/lib/external-files.js +878 -0
- package/lib/file-loader.d.ts +48 -0
- package/lib/file-loader.js +436 -0
- package/lib/file-locations.d.ts +44 -0
- package/lib/file-locations.js +439 -0
- package/lib/generated-wrappers.d.ts +11 -0
- package/lib/generated-wrappers.js +5 -0
- package/lib/github-auth.d.ts +12 -0
- package/lib/github-auth.js +148 -0
- package/lib/github-urls.d.ts +1 -0
- package/lib/github-urls.js +46 -0
- package/lib/gm-api-catalog.d.ts +24 -0
- package/lib/gm-api-catalog.js +217 -0
- package/lib/gm-connect.d.ts +34 -0
- package/lib/gm-connect.js +273 -0
- package/lib/gm-xhr-types.d.ts +68 -0
- package/lib/gm-xhr-types.js +5 -0
- package/lib/harness/browser-extensions.d.ts +73 -0
- package/lib/harness/browser-extensions.js +285 -0
- package/lib/harness/browser-lang.d.ts +20 -0
- package/lib/harness/browser-lang.js +86 -0
- package/lib/harness/browser-launch.d.ts +14 -0
- package/lib/harness/browser-launch.js +184 -0
- package/lib/harness/checks.d.ts +110 -0
- package/lib/harness/checks.js +321 -0
- package/lib/harness/cli.d.ts +2 -0
- package/lib/harness/cli.js +2259 -0
- package/lib/harness/command-args.d.ts +91 -0
- package/lib/harness/command-args.js +369 -0
- package/lib/harness/command-lines.d.ts +80 -0
- package/lib/harness/command-lines.js +180 -0
- package/lib/harness/command-list.d.ts +206 -0
- package/lib/harness/command-list.js +1475 -0
- package/lib/harness/console-commands.d.ts +463 -0
- package/lib/harness/console-commands.js +3216 -0
- package/lib/harness/download-watch.d.ts +21 -0
- package/lib/harness/download-watch.js +156 -0
- package/lib/harness/extension-backend.d.ts +41 -0
- package/lib/harness/extension-backend.js +100 -0
- package/lib/harness/extension-host.d.ts +112 -0
- package/lib/harness/extension-host.js +552 -0
- package/lib/harness/extension-launch.d.ts +67 -0
- package/lib/harness/extension-launch.js +285 -0
- package/lib/harness/extension-link.d.ts +315 -0
- package/lib/harness/extension-link.js +607 -0
- package/lib/harness/external-manager.d.ts +146 -0
- package/lib/harness/external-manager.js +414 -0
- package/lib/harness/fixture.d.ts +165 -0
- package/lib/harness/fixture.js +461 -0
- package/lib/harness/frame-sessions.d.ts +71 -0
- package/lib/harness/frame-sessions.js +248 -0
- package/lib/harness/frames.d.ts +68 -0
- package/lib/harness/frames.js +216 -0
- package/lib/harness/help-format.d.ts +17 -0
- package/lib/harness/help-format.js +127 -0
- package/lib/harness/host.d.ts +352 -0
- package/lib/harness/host.js +423 -0
- package/lib/harness/index.d.ts +8 -0
- package/lib/harness/index.js +10 -0
- package/lib/harness/input-file.d.ts +18 -0
- package/lib/harness/input-file.js +61 -0
- package/lib/harness/json-output.d.ts +565 -0
- package/lib/harness/json-output.js +71 -0
- package/lib/harness/manager-actions.d.ts +21 -0
- package/lib/harness/manager-actions.js +129 -0
- package/lib/harness/manager-commands.d.ts +142 -0
- package/lib/harness/manager-commands.js +1196 -0
- package/lib/harness/manager-fetch.d.ts +33 -0
- package/lib/harness/manager-fetch.js +239 -0
- package/lib/harness/managers.d.ts +24 -0
- package/lib/harness/managers.js +27 -0
- package/lib/harness/output-style.d.ts +125 -0
- package/lib/harness/output-style.js +322 -0
- package/lib/harness/page-commands.d.ts +134 -0
- package/lib/harness/page-commands.js +1141 -0
- package/lib/harness/page-output.d.ts +67 -0
- package/lib/harness/page-output.js +139 -0
- package/lib/harness/page-watch.d.ts +171 -0
- package/lib/harness/page-watch.js +607 -0
- package/lib/harness/parse-check-node.d.ts +3 -0
- package/lib/harness/parse-check-node.js +132 -0
- package/lib/harness/path-args.d.ts +22 -0
- package/lib/harness/path-args.js +148 -0
- package/lib/harness/playwright-chromium.d.ts +16 -0
- package/lib/harness/playwright-chromium.js +86 -0
- package/lib/harness/prepare.d.ts +168 -0
- package/lib/harness/prepare.js +364 -0
- package/lib/harness/prompt-editor.d.ts +17 -0
- package/lib/harness/prompt-editor.js +354 -0
- package/lib/harness/prompt-history.d.ts +13 -0
- package/lib/harness/prompt-history.js +89 -0
- package/lib/harness/rebuild-advice.d.ts +6 -0
- package/lib/harness/rebuild-advice.js +39 -0
- package/lib/harness/run-log.d.ts +47 -0
- package/lib/harness/run-log.js +140 -0
- package/lib/harness/run-logs.d.ts +132 -0
- package/lib/harness/run-logs.js +241 -0
- package/lib/harness/run.d.ts +194 -0
- package/lib/harness/run.js +1973 -0
- package/lib/harness/runs.d.ts +229 -0
- package/lib/harness/runs.js +726 -0
- package/lib/harness/save-html.d.ts +39 -0
- package/lib/harness/save-html.js +89 -0
- package/lib/harness/saved-pages.d.ts +120 -0
- package/lib/harness/saved-pages.js +275 -0
- package/lib/harness/script-mark.d.ts +17 -0
- package/lib/harness/script-mark.js +56 -0
- package/lib/harness/snapshot.d.ts +143 -0
- package/lib/harness/snapshot.js +474 -0
- package/lib/harness/stuck-tabs.d.ts +19 -0
- package/lib/harness/stuck-tabs.js +131 -0
- package/lib/harness/tab-refs.d.ts +20 -0
- package/lib/harness/tab-refs.js +80 -0
- package/lib/harness/tampermonkey.d.ts +71 -0
- package/lib/harness/tampermonkey.js +356 -0
- package/lib/harness/tm-ids.d.ts +18 -0
- package/lib/harness/tm-ids.js +88 -0
- package/lib/harness/violentmonkey.d.ts +34 -0
- package/lib/harness/violentmonkey.js +162 -0
- package/lib/harness/wait.d.ts +77 -0
- package/lib/harness/wait.js +458 -0
- package/lib/http-read.d.ts +30 -0
- package/lib/http-read.js +142 -0
- package/lib/local-file-overrides.d.ts +35 -0
- package/lib/local-file-overrides.js +558 -0
- package/lib/manifest.json +38 -0
- package/lib/markup-response.d.ts +11 -0
- package/lib/markup-response.js +126 -0
- package/lib/match-targets.d.ts +18 -0
- package/lib/match-targets.js +305 -0
- package/lib/menu-captions.d.ts +1 -0
- package/lib/menu-captions.js +22 -0
- package/lib/parse-check-protocol.d.ts +27 -0
- package/lib/parse-check-protocol.js +51 -0
- package/lib/parse-location.d.ts +12 -0
- package/lib/parse-location.js +171 -0
- package/lib/phase-timing.d.ts +12 -0
- package/lib/phase-timing.js +155 -0
- package/lib/public-suffixes.d.ts +1 -0
- package/lib/public-suffixes.js +6957 -0
- package/lib/run-context.d.ts +2 -0
- package/lib/run-context.js +31 -0
- package/lib/runtime-messages.d.ts +10 -0
- package/lib/runtime-messages.js +34 -0
- package/lib/sandboxed-page.d.ts +5 -0
- package/lib/sandboxed-page.js +35 -0
- package/lib/script-build.d.ts +111 -0
- package/lib/script-build.js +787 -0
- package/lib/script-display.d.ts +51 -0
- package/lib/script-display.js +383 -0
- package/lib/script-icon.d.ts +16 -0
- package/lib/script-icon.js +43 -0
- package/lib/script-links.d.ts +24 -0
- package/lib/script-links.js +204 -0
- package/lib/script-source-url.d.ts +8 -0
- package/lib/script-source-url.js +214 -0
- package/lib/script-world.d.ts +6 -0
- package/lib/script-world.js +65 -0
- package/lib/sm-dev-protocol.d.ts +251 -0
- package/lib/sm-dev-protocol.js +84 -0
- package/lib/source-map.d.ts +32 -0
- package/lib/source-map.js +177 -0
- package/lib/stack-link.d.ts +15 -0
- package/lib/stack-link.js +138 -0
- package/lib/unsafe-window-use.d.ts +1 -0
- package/lib/unsafe-window-use.js +229 -0
- package/lib/url-match.d.ts +40 -0
- package/lib/url-match.js +734 -0
- package/lib/userscript-header.d.ts +33 -0
- package/lib/userscript-header.js +248 -0
- package/lib/version-compare.d.ts +6 -0
- package/lib/version-compare.js +141 -0
- package/lib/version-probe.d.ts +17 -0
- package/lib/version-probe.js +120 -0
- package/package.json +55 -3
- package/skills/SourceMonkey-dev/SKILL.md +868 -0
- package/skills/SourceMonkey-dev/references/cdp.md +293 -0
- package/skills/SourceMonkey-dev/references/commands.md +297 -0
- package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
- package/skills/SourceMonkey-dev/references/specs.md +46 -0
- package/skills/SourceMonkey-friction/SKILL.md +103 -0
- package/skills/SourceMonkey-friction/references/template.md +59 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// The service worker's side of sm-dev, the development tool: which tabs
|
|
2
|
+
// an sm-dev run controls (tab claims), and the events it streams to the
|
|
3
|
+
// runs. Design: docs/sm-dev-design.md.
|
|
4
|
+
//
|
|
5
|
+
// Claims:
|
|
6
|
+
// - A claim names the run that controls a tab: only the run's own
|
|
7
|
+
// scripts run there, the ones in its test collection, or none when
|
|
8
|
+
// the tab has its scripts off. `handleCheckPending` (background.ts)
|
|
9
|
+
// answers `skip` for the rest.
|
|
10
|
+
// - Per tab, not per document: it holds across reloads and navigations.
|
|
11
|
+
// - A lease: the run renews it, and an expired claim counts as gone, so
|
|
12
|
+
// a killed run can't leave the person's tab running no scripts.
|
|
13
|
+
// - Kept in `chrome.storage.session` as well as memory, so a worker that
|
|
14
|
+
// Chrome stopped while idle answers the same after it starts again.
|
|
15
|
+
//
|
|
16
|
+
// Runs:
|
|
17
|
+
// - Each run has a lease of its own, renewed with its claims, whether
|
|
18
|
+
// or not it holds a tab. A run's test collection outlives the lease
|
|
19
|
+
// by a grace period (`staleRuns`), then the worker removes it: the
|
|
20
|
+
// run crashed or lost its browser connection.
|
|
21
|
+
// - Kept in session storage too, like the claims.
|
|
22
|
+
//
|
|
23
|
+
// Events:
|
|
24
|
+
// - Each live run subscribes; while any does, events are written to the
|
|
25
|
+
// worker's console as `console.debug(SM_DEV_EVENT, json)`, where
|
|
26
|
+
// Playwright reports them to the run. Without a subscriber nothing is
|
|
27
|
+
// written.
|
|
28
|
+
// - A subscription is a lease too, renewed with the run's claims. It
|
|
29
|
+
// lives in memory only: a restarted worker has none, and the run
|
|
30
|
+
// subscribes again when it sees the new worker.
|
|
31
|
+
//
|
|
32
|
+
// Wrappers that skip asking:
|
|
33
|
+
// - A `document-start` wrapper runs its body without asking where the
|
|
34
|
+
// answer can only be `run` (see `canSkipDecision` in
|
|
35
|
+
// userscript-prelude.js). A claim can `skip` on any navigation, so in
|
|
36
|
+
// a browser sm-dev is connected to every wrapper asks, unless its tab
|
|
37
|
+
// vouches for its script. sm-dev marks the tabs for that; see
|
|
38
|
+
// docs/sm-dev-design.md#making-wrappers-ask.
|
|
39
|
+
import { SM_DEV_EVENT } from '../sm-dev-protocol.js';
|
|
40
|
+
const CLAIMS_KEY = 'smDevClaims';
|
|
41
|
+
const RUNS_KEY = 'smDevRuns';
|
|
42
|
+
const LAST_SWEEP_KEY = 'smDevLastSweep';
|
|
43
|
+
/**
|
|
44
|
+
* How long a run's test collection is kept after its lease runs out,
|
|
45
|
+
* in case the run was only stalled.
|
|
46
|
+
*/
|
|
47
|
+
export const RUN_GRACE_MS = 5 * 60_000;
|
|
48
|
+
/**
|
|
49
|
+
* Sweeps come every minute while any test collection exists. A longer
|
|
50
|
+
* gap means the computer slept, or the browser stalled, and sm-dev with
|
|
51
|
+
* it, so the leases that ran out meanwhile say nothing about its runs.
|
|
52
|
+
*/
|
|
53
|
+
const SWEEP_GAP_MS = 3 * 60_000;
|
|
54
|
+
const claims = new Map();
|
|
55
|
+
// Run id → when its lease runs out.
|
|
56
|
+
const runs = new Map();
|
|
57
|
+
// When `staleRuns` last ran, or 0.
|
|
58
|
+
let lastSweep = 0;
|
|
59
|
+
// Run id → when its event subscription runs out.
|
|
60
|
+
const subscribers = new Map();
|
|
61
|
+
let loading = null;
|
|
62
|
+
/**
|
|
63
|
+
* Resolves once the claims kept in session storage are in memory. Called
|
|
64
|
+
* before any answer that depends on them, so a worker that just started
|
|
65
|
+
* can't answer `run` for a claimed tab.
|
|
66
|
+
*/
|
|
67
|
+
export function claimsReady() {
|
|
68
|
+
loading ??= (async () => {
|
|
69
|
+
try {
|
|
70
|
+
const got = await chrome.storage.session.get([CLAIMS_KEY, RUNS_KEY, LAST_SWEEP_KEY]);
|
|
71
|
+
const stored = got[CLAIMS_KEY];
|
|
72
|
+
for (const [tabId, claim] of Object.entries(stored ?? {})) {
|
|
73
|
+
// Ones set in memory meanwhile are newer.
|
|
74
|
+
if (!claims.has(Number(tabId)))
|
|
75
|
+
claims.set(Number(tabId), claim);
|
|
76
|
+
}
|
|
77
|
+
const storedRuns = got[RUNS_KEY];
|
|
78
|
+
for (const [runId, expires] of Object.entries(storedRuns ?? {})) {
|
|
79
|
+
if (!runs.has(runId))
|
|
80
|
+
runs.set(runId, expires);
|
|
81
|
+
}
|
|
82
|
+
if (lastSweep === 0 && typeof got[LAST_SWEEP_KEY] === 'number')
|
|
83
|
+
lastSweep = got[LAST_SWEEP_KEY];
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.info('[SourceMonkey] could not read the sm-dev tab claims:', err);
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
return loading;
|
|
90
|
+
}
|
|
91
|
+
function persist() {
|
|
92
|
+
const stored = {};
|
|
93
|
+
for (const [tabId, claim] of claims)
|
|
94
|
+
stored[tabId] = claim;
|
|
95
|
+
chrome.storage.session.set({ [CLAIMS_KEY]: stored, [RUNS_KEY]: Object.fromEntries(runs), [LAST_SWEEP_KEY]: lastSweep }).catch((err) => {
|
|
96
|
+
console.info('[SourceMonkey] could not save the sm-dev tab claims:', err);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** The tab's claim, if one is in force. Call after `claimsReady`. */
|
|
100
|
+
export function activeClaim(tabId) {
|
|
101
|
+
const claim = claims.get(tabId);
|
|
102
|
+
if (!claim)
|
|
103
|
+
return null;
|
|
104
|
+
if (claim.expires < Date.now()) {
|
|
105
|
+
claims.delete(tabId);
|
|
106
|
+
persist();
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return claim;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Whether a script may run in a tab with this claim. `scriptRun`: the
|
|
113
|
+
* run a scratch script belongs to (`scratchRunOf`), null for any other.
|
|
114
|
+
*/
|
|
115
|
+
export function claimAllows(claim, scriptRun) {
|
|
116
|
+
return !claim.scriptsOff && scriptRun === claim.runId;
|
|
117
|
+
}
|
|
118
|
+
export async function claimTab(tabId, runId, ttlMs) {
|
|
119
|
+
await claimsReady();
|
|
120
|
+
const expires = Date.now() + ttlMs;
|
|
121
|
+
claims.set(tabId, { runId, expires });
|
|
122
|
+
runs.set(runId, Math.max(runs.get(runId) ?? 0, expires));
|
|
123
|
+
persist();
|
|
124
|
+
}
|
|
125
|
+
// The navigations sm-dev names a tab by, by their fragment: each a
|
|
126
|
+
// promise of the tab's id, null once it timed out.
|
|
127
|
+
const namingNavigations = new Map();
|
|
128
|
+
/**
|
|
129
|
+
* Watches for a top frame navigating to a URL ending in `#<fragment>`,
|
|
130
|
+
* for `namedTab` to say which tab it was. sm-dev knows a tab by its CDP
|
|
131
|
+
* target and the extension by its `chrome.tabs` id; neither sees the
|
|
132
|
+
* other's, so sm-dev sends the tab to `about:blank#<fragment>` between
|
|
133
|
+
* the two calls. Stops watching after `timeoutMs`.
|
|
134
|
+
*/
|
|
135
|
+
export function watchForNamingNavigation(fragment, timeoutMs) {
|
|
136
|
+
const wanted = `#${fragment}`;
|
|
137
|
+
namingNavigations.set(fragment, new Promise((resolve) => {
|
|
138
|
+
const heard = (details) => {
|
|
139
|
+
if (details.frameId !== 0 || !details.url.endsWith(wanted))
|
|
140
|
+
return;
|
|
141
|
+
stop();
|
|
142
|
+
resolve(details.tabId);
|
|
143
|
+
};
|
|
144
|
+
const stop = () => {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
chrome.webNavigation.onCommitted.removeListener(heard);
|
|
147
|
+
chrome.webNavigation.onReferenceFragmentUpdated.removeListener(heard);
|
|
148
|
+
};
|
|
149
|
+
// Gone from the table too, should nobody ask for it.
|
|
150
|
+
const timer = setTimeout(() => {
|
|
151
|
+
stop();
|
|
152
|
+
namingNavigations.delete(fragment);
|
|
153
|
+
resolve(null);
|
|
154
|
+
}, timeoutMs);
|
|
155
|
+
chrome.webNavigation.onCommitted.addListener(heard);
|
|
156
|
+
chrome.webNavigation.onReferenceFragmentUpdated.addListener(heard);
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
/** The tab that made the navigation `fragment` names, or null if none did in time. */
|
|
160
|
+
export async function namedTab(fragment) {
|
|
161
|
+
const found = namingNavigations.get(fragment);
|
|
162
|
+
namingNavigations.delete(fragment);
|
|
163
|
+
return found ?? null;
|
|
164
|
+
}
|
|
165
|
+
/** Turns a claimed tab's scripts off, or back on (`open --noscript` / `--script`). */
|
|
166
|
+
export async function setTabScriptsOff(tabId, off) {
|
|
167
|
+
await claimsReady();
|
|
168
|
+
const claim = claims.get(tabId);
|
|
169
|
+
if (!claim)
|
|
170
|
+
return;
|
|
171
|
+
if (off)
|
|
172
|
+
claim.scriptsOff = true;
|
|
173
|
+
else
|
|
174
|
+
delete claim.scriptsOff;
|
|
175
|
+
persist();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Extends a run's lease, claims and event subscription by `ttlMs`.
|
|
179
|
+
*
|
|
180
|
+
* `tabs` are the tabs the run holds. One whose claim is gone (it ran
|
|
181
|
+
* out while the worker was stopped, or an extension reload cleared the
|
|
182
|
+
* session storage) is claimed again, if the tab is still open. Returns
|
|
183
|
+
* how many were.
|
|
184
|
+
*/
|
|
185
|
+
export async function renewRun(runId, ttlMs, tabs = []) {
|
|
186
|
+
await claimsReady();
|
|
187
|
+
const expires = Date.now() + ttlMs;
|
|
188
|
+
runs.set(runId, expires);
|
|
189
|
+
for (const claim of claims.values()) {
|
|
190
|
+
if (claim.runId === runId)
|
|
191
|
+
claim.expires = expires;
|
|
192
|
+
}
|
|
193
|
+
let restored = 0;
|
|
194
|
+
for (const tab of tabs) {
|
|
195
|
+
if (claims.has(tab.tabId))
|
|
196
|
+
continue;
|
|
197
|
+
const open = await chrome.tabs.get(tab.tabId).then(() => true, () => false);
|
|
198
|
+
if (!open)
|
|
199
|
+
continue;
|
|
200
|
+
claims.set(tab.tabId, { runId, expires, ...(tab.scriptsOff ? { scriptsOff: true } : {}) });
|
|
201
|
+
restored++;
|
|
202
|
+
}
|
|
203
|
+
subscribers.set(runId, expires);
|
|
204
|
+
persist();
|
|
205
|
+
return restored;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Ends a run's lease, claims and event subscription. The caller removes
|
|
209
|
+
* its test collection.
|
|
210
|
+
*/
|
|
211
|
+
export async function releaseRun(runId) {
|
|
212
|
+
await claimsReady();
|
|
213
|
+
for (const [tabId, claim] of claims) {
|
|
214
|
+
if (claim.runId === runId)
|
|
215
|
+
claims.delete(tabId);
|
|
216
|
+
}
|
|
217
|
+
runs.delete(runId);
|
|
218
|
+
subscribers.delete(runId);
|
|
219
|
+
persist();
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Of the runs that have a test collection, the ones whose collection
|
|
223
|
+
* should go: their lease ran out more than `RUN_GRACE_MS` ago.
|
|
224
|
+
*
|
|
225
|
+
* - A run with no lease on record (one from before the browser
|
|
226
|
+
* restarted) starts its grace period now.
|
|
227
|
+
* - After a gap since the last sweep (`SWEEP_GAP_MS`), every lease
|
|
228
|
+
* starts its grace period now instead, and none is stale: a run that
|
|
229
|
+
* slept with the computer renews again within seconds.
|
|
230
|
+
*/
|
|
231
|
+
export async function staleRuns(withCollections, now = Date.now()) {
|
|
232
|
+
await claimsReady();
|
|
233
|
+
const stale = [];
|
|
234
|
+
const gap = lastSweep > 0 && now - lastSweep > SWEEP_GAP_MS;
|
|
235
|
+
lastSweep = now;
|
|
236
|
+
for (const runId of withCollections) {
|
|
237
|
+
if (!runs.has(runId))
|
|
238
|
+
runs.set(runId, now);
|
|
239
|
+
}
|
|
240
|
+
if (gap) {
|
|
241
|
+
for (const [runId, expires] of runs)
|
|
242
|
+
runs.set(runId, Math.max(expires, now));
|
|
243
|
+
persist();
|
|
244
|
+
return stale;
|
|
245
|
+
}
|
|
246
|
+
// Every long-gone run is forgotten, one without a collection too (a
|
|
247
|
+
// run killed before it pushed a script).
|
|
248
|
+
for (const [runId, expires] of runs) {
|
|
249
|
+
if (expires + RUN_GRACE_MS >= now)
|
|
250
|
+
continue;
|
|
251
|
+
runs.delete(runId);
|
|
252
|
+
if (withCollections.includes(runId))
|
|
253
|
+
stale.push(runId);
|
|
254
|
+
}
|
|
255
|
+
persist();
|
|
256
|
+
return stale;
|
|
257
|
+
}
|
|
258
|
+
/** Forgets every claim, lease and subscription, for the e2e tests' reset. */
|
|
259
|
+
export function resetSmDevLinkForTests() {
|
|
260
|
+
claims.clear();
|
|
261
|
+
runs.clear();
|
|
262
|
+
subscribers.clear();
|
|
263
|
+
lastSweep = 0;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* A tab a claimed tab opened (`window.open`, a link to a new tab) gets
|
|
267
|
+
* its opener's claim. Called from `onCreatedNavigationTarget`, which
|
|
268
|
+
* comes before the new tab's scripts ask: they wait on `claimsReady`
|
|
269
|
+
* too, behind this.
|
|
270
|
+
*/
|
|
271
|
+
export async function inheritClaim(sourceTabId, tabId) {
|
|
272
|
+
await claimsReady();
|
|
273
|
+
const claim = activeClaim(sourceTabId);
|
|
274
|
+
if (!claim)
|
|
275
|
+
return;
|
|
276
|
+
claims.set(tabId, { ...claim });
|
|
277
|
+
persist();
|
|
278
|
+
}
|
|
279
|
+
export function dropClaim(tabId) {
|
|
280
|
+
if (!claims.delete(tabId))
|
|
281
|
+
return;
|
|
282
|
+
persist();
|
|
283
|
+
}
|
|
284
|
+
/** Sends an event to the subscribed runs, if any is live. */
|
|
285
|
+
export function emitSmDevEvent(event) {
|
|
286
|
+
const now = Date.now();
|
|
287
|
+
let live = false;
|
|
288
|
+
for (const [runId, expires] of subscribers) {
|
|
289
|
+
if (expires < now)
|
|
290
|
+
subscribers.delete(runId);
|
|
291
|
+
else
|
|
292
|
+
live = true;
|
|
293
|
+
}
|
|
294
|
+
if (!live)
|
|
295
|
+
return;
|
|
296
|
+
try {
|
|
297
|
+
console.debug(SM_DEV_EVENT, JSON.stringify({ ...event, at: now }));
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// An event that can't be serialized is dropped.
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// What an enable checkbox says, and the Ctrl/Cmd-click that opts it out
|
|
2
|
+
// of turning a script's other copies off. Shared by every surface with
|
|
3
|
+
// an enable checkbox: the viewer, the collections page, the toolbar
|
|
4
|
+
// popup. See docs/script-enablement.md.
|
|
5
|
+
import { ENABLE_TITLE } from '../script-display.js';
|
|
6
|
+
// What every enable checkbox says, and the tail it adds when clicking
|
|
7
|
+
// it would sweep other copies. Shared so every surface says exactly the
|
|
8
|
+
// same thing: the hint is the only part of them that varies.
|
|
9
|
+
//
|
|
10
|
+
// Deliberately short, and deliberately fixed. Naming the copy would
|
|
11
|
+
// repeat the Collection and Location cells an inch to the right, and
|
|
12
|
+
// swapping to "Disable" when checked would make one control read two
|
|
13
|
+
// ways for no gain.
|
|
14
|
+
//
|
|
15
|
+
// The hint names ONE key — the one this machine has. The handler
|
|
16
|
+
// accepts either, but only one is usable here (on a Mac ctrl-click opens
|
|
17
|
+
// the context menu, so Cmd is the one that works), and a message
|
|
18
|
+
// offering both would be telling half its readers about a gesture their
|
|
19
|
+
// keyboard doesn't have. The docs say "Ctrl/Cmd" because
|
|
20
|
+
// they describe every platform at once; the app knows which one it is
|
|
21
|
+
// running on.
|
|
22
|
+
const SOLO_HINT = ` (${isApplePlatform() ? 'Cmd' : 'Ctrl'}-click for multi-enable)`;
|
|
23
|
+
// Resolved once, at module load — the platform can't change under us.
|
|
24
|
+
// `userAgentData` is the supported spelling and is present in every
|
|
25
|
+
// Chrome that can run this extension; `platform` is the fallback for a
|
|
26
|
+
// non-Chrome host and for the tests' DOM stubs.
|
|
27
|
+
function isApplePlatform() {
|
|
28
|
+
const data = navigator.userAgentData;
|
|
29
|
+
if (data?.platform)
|
|
30
|
+
return data.platform === 'macOS';
|
|
31
|
+
return /mac|iphone|ipad/i.test(navigator.platform || '');
|
|
32
|
+
}
|
|
33
|
+
// Wording for an enable checkbox, hint included only when clicking it
|
|
34
|
+
// would sweep other copies.
|
|
35
|
+
export function enableTitle(sweepsOthers) {
|
|
36
|
+
return sweepsOthers ? `${ENABLE_TITLE}${SOLO_HINT}` : ENABLE_TITLE;
|
|
37
|
+
}
|
|
38
|
+
// Wire a checkbox so its `change` handler can find out whether the
|
|
39
|
+
// click that produced it carried the exclusive-opt-out modifier.
|
|
40
|
+
// Returns a consume-once reader.
|
|
41
|
+
//
|
|
42
|
+
// Read from `click`, not `change`: `change` carries no modifier state.
|
|
43
|
+
// Keyboard activation (space) fires a click too, so nothing is lost —
|
|
44
|
+
// and `ctrlKey` is simply false there, which is the right default.
|
|
45
|
+
// `metaKey` covers Cmd-click on a Mac, where ctrl-click is the context
|
|
46
|
+
// menu. Consumed on read, so a later `change` that arrives without a
|
|
47
|
+
// click in front of it can't inherit a modifier pressed once.
|
|
48
|
+
export function watchSoloModifier(box) {
|
|
49
|
+
let solo = false;
|
|
50
|
+
box.addEventListener('click', (ev) => {
|
|
51
|
+
solo = ev.ctrlKey || ev.metaKey;
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
const was = solo;
|
|
55
|
+
solo = false;
|
|
56
|
+
return was;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Sticky numbers: a short number for each installed script and each
|
|
2
|
+
// collection, which sm-dev names them by (`E12`, `C3`). Log IDs carry the
|
|
3
|
+
// script's (`foo.user.js#E12`). See docs/storage.md#sticky-numbers.
|
|
4
|
+
//
|
|
5
|
+
// - Each kind has one row (`scriptNumbers`, `collectionNumbers`), rather
|
|
6
|
+
// than a field on each script or collection row: those rows are
|
|
7
|
+
// rewritten from many places, and a number lost in one of those
|
|
8
|
+
// rewrites would be handed out again as a new one.
|
|
9
|
+
// - Only the service worker writes the rows (`assignNumbers`, run before
|
|
10
|
+
// each registration sync), so two installs can't take the same number.
|
|
11
|
+
// Pages only read them.
|
|
12
|
+
// - A removed item's number is held back for a while before reuse, so an
|
|
13
|
+
// id someone saw a moment ago never names a different item.
|
|
14
|
+
import { sharedRows } from './row-cache.js';
|
|
15
|
+
import { assertWorkerContext } from '../run-context.js';
|
|
16
|
+
import { isSmDevCollection } from './directories.js';
|
|
17
|
+
import { ensureCollectionKeysMigrated } from './script-store.js';
|
|
18
|
+
export const SCRIPT_NUMBERS_KEY = 'scriptNumbers';
|
|
19
|
+
export const COLLECTION_NUMBERS_KEY = 'collectionNumbers';
|
|
20
|
+
/** How long a removed item's number waits before it can be reused. */
|
|
21
|
+
export const NUMBER_HOLD_MS = 7 * 24 * 60 * 60 * 1000;
|
|
22
|
+
/** The stored row, or an empty one; tolerates a malformed row. */
|
|
23
|
+
export function numbersFrom(value) {
|
|
24
|
+
const row = value;
|
|
25
|
+
return {
|
|
26
|
+
byId: row?.byId && typeof row.byId === 'object' ? row.byId : {},
|
|
27
|
+
retired: row?.retired && typeof row.retired === 'object' ? row.retired : {},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The numbers by id, for log IDs. */
|
|
31
|
+
export function numberMap(numbers) {
|
|
32
|
+
return new Map(Object.entries(numbers.byId));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The numbers after reconciling them with the items that should have
|
|
36
|
+
* one (`wanted`, in the order new numbers are handed out):
|
|
37
|
+
*
|
|
38
|
+
* - a wanted item keeps its number; if two share one (a restored
|
|
39
|
+
* backup, say), the first keeps it and the other gets a new one;
|
|
40
|
+
* - a number whose item is gone, or no longer wanted, is retired from
|
|
41
|
+
* `now`, unless it was already;
|
|
42
|
+
* - a retired number held for `holdMs` is free again;
|
|
43
|
+
* - a wanted item without a number gets the lowest free one.
|
|
44
|
+
*
|
|
45
|
+
* Pure. `changed` is false when nothing moved, so the caller can skip
|
|
46
|
+
* the write.
|
|
47
|
+
*/
|
|
48
|
+
export function reconcileNumbers(prior, wanted, now, holdMs = NUMBER_HOLD_MS) {
|
|
49
|
+
const byId = {};
|
|
50
|
+
const retired = { ...prior.retired };
|
|
51
|
+
const used = new Set();
|
|
52
|
+
for (const id of wanted) {
|
|
53
|
+
const n = prior.byId[id];
|
|
54
|
+
if (!Number.isInteger(n) || n < 1 || used.has(n))
|
|
55
|
+
continue;
|
|
56
|
+
byId[id] = n;
|
|
57
|
+
used.add(n);
|
|
58
|
+
// In use again (a reconcile that went wrong earlier): not retired.
|
|
59
|
+
delete retired[n];
|
|
60
|
+
}
|
|
61
|
+
for (const [id, n] of Object.entries(prior.byId)) {
|
|
62
|
+
if (byId[id] === n || used.has(n) || String(n) in retired)
|
|
63
|
+
continue;
|
|
64
|
+
retired[n] = now;
|
|
65
|
+
}
|
|
66
|
+
for (const [n, at] of Object.entries(retired)) {
|
|
67
|
+
if (now - at >= holdMs)
|
|
68
|
+
delete retired[n];
|
|
69
|
+
}
|
|
70
|
+
let next = 1;
|
|
71
|
+
for (const id of wanted) {
|
|
72
|
+
if (id in byId)
|
|
73
|
+
continue;
|
|
74
|
+
while (used.has(next) || String(next) in retired)
|
|
75
|
+
next++;
|
|
76
|
+
byId[id] = next;
|
|
77
|
+
used.add(next);
|
|
78
|
+
}
|
|
79
|
+
const numbers = { byId, retired };
|
|
80
|
+
return { numbers, changed: !sameNumbers(prior, numbers) };
|
|
81
|
+
}
|
|
82
|
+
function sameNumbers(a, b) {
|
|
83
|
+
const same = (x, y) => {
|
|
84
|
+
const keys = Object.keys(x);
|
|
85
|
+
return keys.length === Object.keys(y).length && keys.every((k) => x[k] === y[k]);
|
|
86
|
+
};
|
|
87
|
+
return same(a.byId, b.byId) && same(a.retired, b.retired);
|
|
88
|
+
}
|
|
89
|
+
function byPath(collections) {
|
|
90
|
+
return [...collections].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The collections that get a number, by path. Not sm-dev's test
|
|
94
|
+
* collections, which a run names itself, and adds and drops too often
|
|
95
|
+
* to number.
|
|
96
|
+
*/
|
|
97
|
+
export function numberedCollectionIds(collections) {
|
|
98
|
+
return byPath(collections).filter((c) => !isSmDevCollection(c.path)).map((c) => c.id);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The scripts that get a number, in the order new ones are handed out:
|
|
102
|
+
* collections by path, then each collection's own order. Not the
|
|
103
|
+
* scripts in sm-dev's test collections, which a run names `1`, `2`
|
|
104
|
+
* itself, and pushes and removes too often to number.
|
|
105
|
+
*/
|
|
106
|
+
export function numberedScriptIds(scriptIds, collections) {
|
|
107
|
+
const present = new Set(scriptIds);
|
|
108
|
+
const ids = [];
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
for (const collection of byPath(collections)) {
|
|
111
|
+
if (isSmDevCollection(collection.path)) {
|
|
112
|
+
for (const id of collection.scriptIds)
|
|
113
|
+
seen.add(id);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
for (const id of collection.scriptIds) {
|
|
117
|
+
if (!present.has(id) || seen.has(id))
|
|
118
|
+
continue;
|
|
119
|
+
seen.add(id);
|
|
120
|
+
ids.push(id);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// A script row whose collection doesn't list it still gets a number.
|
|
124
|
+
for (const id of scriptIds)
|
|
125
|
+
if (!seen.has(id))
|
|
126
|
+
ids.push(id);
|
|
127
|
+
return ids;
|
|
128
|
+
}
|
|
129
|
+
let assigning = Promise.resolve(null);
|
|
130
|
+
/**
|
|
131
|
+
* Reconciles the stored numbers with the stored scripts and collections,
|
|
132
|
+
* writes the rows that moved, and resolves to the numbers (null when
|
|
133
|
+
* they couldn't be read). Serialized, so two calls never write over each
|
|
134
|
+
* other. Service worker only: it is the rows' one writer.
|
|
135
|
+
*
|
|
136
|
+
* Runs before every registration sync, so it reads only what it needs:
|
|
137
|
+
* which scripts and collections exist (from the worker's row cache,
|
|
138
|
+
* else the keys and the collection rows) and the numbers rows.
|
|
139
|
+
*/
|
|
140
|
+
export function assignNumbers() {
|
|
141
|
+
const run = assigning.then(async () => {
|
|
142
|
+
// Collection rows may still be under their old key spelling.
|
|
143
|
+
await ensureCollectionKeysMigrated();
|
|
144
|
+
// Which scripts and collections exist: from the worker's row cache
|
|
145
|
+
// when it's on (frozen rows, read only), else from storage.
|
|
146
|
+
const isRow = (k) => k.startsWith('script:') || k.startsWith('collection:');
|
|
147
|
+
const cached = await sharedRows(isRow);
|
|
148
|
+
const keys = cached ? Object.keys(cached) : await chrome.storage.local.getKeys();
|
|
149
|
+
const scriptIds = keys.filter((k) => k.startsWith('script:')).map((k) => k.slice('script:'.length));
|
|
150
|
+
const got = {
|
|
151
|
+
...(cached ? Object.fromEntries(Object.entries(cached).filter(([k]) => k.startsWith('collection:'))) : {}),
|
|
152
|
+
...await chrome.storage.local.get([
|
|
153
|
+
...(cached ? [] : keys.filter((k) => k.startsWith('collection:'))), SCRIPT_NUMBERS_KEY, COLLECTION_NUMBERS_KEY,
|
|
154
|
+
]),
|
|
155
|
+
};
|
|
156
|
+
const collections = Object.entries(got)
|
|
157
|
+
.filter(([k]) => k.startsWith('collection:'))
|
|
158
|
+
.map(([, v]) => v);
|
|
159
|
+
const now = Date.now();
|
|
160
|
+
const scripts = reconcileNumbers(numbersFrom(got[SCRIPT_NUMBERS_KEY]), numberedScriptIds(scriptIds, collections), now);
|
|
161
|
+
const colls = reconcileNumbers(numbersFrom(got[COLLECTION_NUMBERS_KEY]), numberedCollectionIds(collections), now);
|
|
162
|
+
const writes = {};
|
|
163
|
+
if (scripts.changed)
|
|
164
|
+
writes[SCRIPT_NUMBERS_KEY] = scripts.numbers;
|
|
165
|
+
if (colls.changed)
|
|
166
|
+
writes[COLLECTION_NUMBERS_KEY] = colls.numbers;
|
|
167
|
+
assertWorkerContext('write storage');
|
|
168
|
+
if (Object.keys(writes).length)
|
|
169
|
+
await chrome.storage.local.set(writes);
|
|
170
|
+
return { scripts: scripts.numbers, collections: colls.numbers };
|
|
171
|
+
});
|
|
172
|
+
// A failure is logged, and doesn't block the next attempt.
|
|
173
|
+
assigning = run.catch((err) => {
|
|
174
|
+
console.info('[SourceMonkey] script and collection numbers could not be updated', err);
|
|
175
|
+
return null;
|
|
176
|
+
});
|
|
177
|
+
return assigning;
|
|
178
|
+
}
|
|
179
|
+
/** The stored numbers, read fresh. */
|
|
180
|
+
export async function readNumbers() {
|
|
181
|
+
const got = await chrome.storage.local.get([SCRIPT_NUMBERS_KEY, COLLECTION_NUMBERS_KEY]);
|
|
182
|
+
return { scripts: numbersFrom(got[SCRIPT_NUMBERS_KEY]), collections: numbersFrom(got[COLLECTION_NUMBERS_KEY]) };
|
|
183
|
+
}
|
|
184
|
+
/** The stored script numbers, read fresh. */
|
|
185
|
+
export async function readScriptNumbers() {
|
|
186
|
+
const got = await chrome.storage.local.get(SCRIPT_NUMBERS_KEY);
|
|
187
|
+
return numbersFrom(got[SCRIPT_NUMBERS_KEY]);
|
|
188
|
+
}
|