Skip to content

Replace extension RPC with the SDK shared session - #197

Open
callumalpass wants to merge 1 commit into
mainfrom
feat/extension-shared-session
Open

callumalpass wants to merge 1 commit into
mainfrom
feat/extension-shared-session

Conversation

@callumalpass

Copy link
Copy Markdown
Contributor

Reader's extension no longer serializes Mdbase and Reader repository calls through its own offscreen RPC bridge. Every extension-origin context creates an SDK entry with one build identity and calls it directly; the SDK owns the shared Worker, replica, sign-in and transport.

Context Final responsibility
Toolbar / popup There is no popup; the toolbar opens the capture side panel.
Capture side panel, options, welcome Each creates its own SDK with session: { build, host: false }, follows visibility, opens sign-in through chrome.tabs.create, and borrows Reader repositories from its own current connection.
Service worker Uses a non-hosting SDK entry for saved-page URLs and annotations. Explicitly selects the last collection remembered by a ready page. SDK startup remains on demand.
Offscreen document Starts the hosting SDK entry and reports setForeground(false). It serves no application RPC.
Injected content scripts Capture DOM/selection, observe post changes and draw highlights. They never used the SDK RPC bridge, and retain only those existing scripting/runtime messages.

A UUID generated once per Vite build is passed to every page and the separately built background bundle. Rebuilt artifacts cannot accidentally join an old build. The service worker is now an ES module: the SDK validates import.meta.url, which a classic IIFE cannot provide. The obsolete importScripts page-status loader is deleted. Reader's fetch overrides remain in both page mounting and the page-status worker.

Deleted the app RPC channel and lifetime lock, command allowlists, method proxies, snapshot/error/argument serialization, callback/abort dispatch, host/client implementations, memory bus and bridge tests. Regenerated catalogs to remove the five deleted bridge messages. The one remaining host-lifecycle message only asks the service worker to create the offscreen document; visible entries ask again when the SDK re-enters starting after host loss. No SDK calls or record data cross that message.

The SDK's tab group elects a host with Web Locks and tunnels its ports over BroadcastChannel; its entry binds module/current/declaration origins. BroadcastChannel is same-origin and storage-partition scoped; content-script isolation does not move the web document onto the extension origin. Content scripts therefore do not join this session and need no repository message path. The service worker does join: host: false skips the Worker requirement. Chrome supports static module service-worker imports, but not dynamic imports.

Added a Chromium test that builds two separate SDK bundles, starts the non-hosting context first, proves only one original SDK Worker runs, opens the same local unsynced replica, reads writes in both directions and verifies closing the follower leaves the host usable. Only registration is intercepted; original pinned Worker/WASM/SQLite bytes run unchanged, and every other external request is refused. CI runs it after installing Chromium. Unit fixtures additionally cover independent adapter generations and two clients of one memory replica.

Validation on 794d9b85 (Node 22.22.0, pnpm 10.7.0; finite checks/builds through rpnpm):

Command Result
pnpm install --frozen-lockfile PASS; SDK pins unchanged. Lockfile diff is only the extension's three-line Playwright importer entry, reusing the existing pinned version.
rpnpm check PASS: lint, formatting, architecture, every workspace typecheck, 11 architecture tests, 175 publisher tests, all workspace tests, and i18n checks.
rpnpm test:workspaces PASS separately, also included in the final full check.
rpnpm --pull apps/reader/dist build:production PASS; runs MDBASE_ENV=production pnpm build.
rpnpm --pull apps/extension/dist --filter @mdbase-reader/extension package PASS; production ZIP. Local packaging also passed; archive contains module background/offscreen host and excludes source maps.
MDBASE_ENV=production pnpm --filter @mdbase-reader/app manifest; MDBASE_ENV=production pnpm --filter @mdbase-reader/extension manifest:mdbase; test -z "$(git status --porcelain)" PASS; generated manifests remain clean.
pnpm --filter @mdbase-reader/extension test:browser PASS with isolated Chromium; actual shared Worker/replica, bidirectional writes and follower closure.
pnpm --filter @mdbase-reader/app test:pdf-touch PASS with isolated Chromium; touch handles and mobile persisted highlights.
pnpm --filter @mdbase-reader/app test:html-svg PASS.
rpnpm --pull apps/reader/dist --filter @mdbase-reader/app build:feedback-test; node .scratch/feedback-suite.mjs (invokes pnpm --filter @mdbase-reader/app test:feedback against an owned loopback preview) PASS; desktop/mobile variants, all delivery intercepted.
rpnpm check:release-downloads; rpnpm test:release-downloads PASS; download audit and all 8 tests.

Packaged-extension smoke: node .scratch/extension-smoke.mjs uses Playwright chromium.launchPersistentContext with --disable-extensions-except and --load-extension against the packaged production dist. Options, welcome and capture render; one offscreen document is created; the module service worker exposes Web Locks, BroadcastChannel and OPFS, and has no Worker constructor. No uncaught page errors or external requests. The owned profile/browser was removed/closed.

Limits: the production package declares the store origin chrome-extension://kimdfjefhbfgfecconmaiaaindjccidp, while local Chromium assigns the unpacked artifact chrome-extension://ciielciajpkmeignjfaicgpnlpilogai. The SDK refuses that registration binding before sign-in. Authenticated sign-in, connected-library opening and a real capture save were not verified. This smoke and the intercepted local-replica test do not establish authenticated extension or log-durability acceptance. No trust policy, app origin or SDK pin was changed to bypass it.

Behavior to review: selections now belong to each context; saved-page lookup follows the last ready page selection. The module background parses its SDK code on wake-up instead of importing it later, while SDK construction/startup remain on demand. Capture-create follow-through and its in-memory journal now last for the originating context's lifetime; pending replica writes still belong to the shared Worker, but automatic renaming after a later path refusal only follows while that context stays open. These changes are documented in the topic page and changelog fragment. No other product or SDK gaps were found. packages/connect is untouched for the concurrent legacy-removal lane.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant