MV3 extension that captures the rendered active tab (via the shared snapshot-extractor contract) and imports it into OpenPencil. Logic lives in the new op-chrome-extension-core crate (wasm): endpoint rules, chunked-transfer integrity, /mcp envelope + reply classification, download-name sanitisation, SSO/account session parsing, and hub snapshot-inbox delivery. JS is glue only (chrome.* APIs, fetch, popup DOM, injected page functions). Capture: full page + element pick. Delivery: local ingress (POST /api/import/web-snapshot) with /mcp fallback, JSON download, and — when signed in to OP Hub — the account snapshot inbox. Flat popup UI, 15-locale strings with an in-popup language switcher, store packaging. Why a crate: keeps the security-sensitive logic in tested Rust rather than glue JS, and the SW/popup split keeps dynamic import() out of the service-worker graph (guarded by check-sw-imports).
70 lines
2.8 KiB
JavaScript
70 lines
2.8 KiB
JavaScript
/**
|
|
* The **popup's** loader for the Rust logic core.
|
|
*
|
|
* `wasm/` is a build product and is not checked in, so a fresh clone has no
|
|
* core until `scripts/build-wasm.sh` has run. The import is therefore dynamic
|
|
* and guarded: a static `import` of a missing module aborts the whole popup
|
|
* script before any handler is attached, which would present as a blank popup
|
|
* with no explanation. A dynamic one lets the popup come up and say what to
|
|
* run.
|
|
*
|
|
* **This file is for the popup only.** `import()` is disallowed on
|
|
* `ServiceWorkerGlobalScope` by the HTML specification (w3c/ServiceWorker
|
|
* issue 1356), so the service worker must not reach the core through here —
|
|
* it imports the shim with a static declaration instead. See
|
|
* `core-registry.js` for the split, and `scripts/check-sw-imports.mjs` for the
|
|
* guard that keeps this file out of the worker's import graph.
|
|
*
|
|
* The shim is loaded from the extension package by its own origin, so MV3's
|
|
* `script-src 'self'` covers it. Instantiating the module needs the
|
|
* `'wasm-unsafe-eval'` CSP keyword, which `manifest.json` declares — that
|
|
* keyword permits `WebAssembly.compile` on bundled bytes; it does not permit
|
|
* `eval`, and no code is fetched from anywhere but this package.
|
|
*/
|
|
|
|
import { coreError, getCore, hasCore, setCore } from './core-registry.js';
|
|
|
|
/** Generated by `wasm-bindgen --target web`; named after the crate's lib name. */
|
|
const CORE_MODULE = './wasm/op_chrome_extension_core.js';
|
|
|
|
/** In-flight load, so concurrent callers share one instantiation. */
|
|
let loading = null;
|
|
|
|
/**
|
|
* Load and instantiate the core. Idempotent; the result is cached.
|
|
*
|
|
* @returns {Promise<object>} the wasm module's exports.
|
|
* @throws {Error} with `code` `wasmMissing` when the build product is absent,
|
|
* or `wasmInit` when it is present but will not instantiate.
|
|
*/
|
|
export function loadCore() {
|
|
if (hasCore()) return Promise.resolve(getCore());
|
|
if (!loading) {
|
|
loading = (async () => {
|
|
let module;
|
|
try {
|
|
module = await import(CORE_MODULE);
|
|
} catch (cause) {
|
|
// The import is the only step that can tell "not built" apart from
|
|
// "built but broken". Everything after it is an init failure, and
|
|
// saying "run the build script" about one of those is a misdiagnosis.
|
|
throw coreError('wasmMissing', cause);
|
|
}
|
|
try {
|
|
// `--target web` exports its initializer as the default binding; with
|
|
// no argument it resolves the `.wasm` sitting next to the shim.
|
|
await module.default();
|
|
} catch (cause) {
|
|
throw coreError('wasmInit', cause);
|
|
}
|
|
return setCore(module);
|
|
})().catch((cause) => {
|
|
// Drop the rejected promise so a later attempt can retry rather than
|
|
// replaying the same failure forever.
|
|
loading = null;
|
|
throw cause;
|
|
});
|
|
}
|
|
return loading;
|
|
}
|