Give each provider scope its own message channel - #3606
Draft
reneaaron wants to merge 1 commit into
Draft
Conversation
The inpage providers and their content scripts talked to each other with `window.postMessage` on the page window. Every message that went over it — request ids, arguments and replies alike — was delivered to all the other message listeners in the frame, and the first reply carrying a matching id settled the call. Each scope now gets a `MessageChannel`. The content script keeps one end and transfers the other to the inpage world, and provider requests, replies and events travel over that channel instead of the page window. - `messagePortServer.js` creates the channel and hands over the port. It runs at module top, ahead of the asynchronous should-inject decision, and buffers requests that arrive before the scope registers its handler. - `postMessage.ts` asks for its port, retries until it arrives (either world may start first) and routes replies back to the caller by id. - replies for an id that is already settled are reported rather than applied. - `accountChanged` is delivered over the channel instead of the page window. The handover is negotiated with window messages, so the channel is private only from scripts that are not yet running when the port is transferred. On MV2 the inpage script is injected inline at document_start and that ordering holds; on MV3 the main-world script is registered separately and it is not guaranteed. Tests cover both sides of the transport. JSDOM has no MessageChannel, so `tests/unit/helpers/fakeMessageChannel.ts` provides a stand-in that is installed per test file — msw relies on the real one, so it is not replaced globally.
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Describe the changes you have made in this PR
The inpage providers and their content scripts communicated over
window.postMessageon the page window. Everything sent that way — the correlation id, the arguments and the reply — is delivered to every othermessagelistener in the frame, and the first reply carrying a matching id settles the call.Each scope now gets its own
MessageChannel. The content script keeps one end and transfers the other to the inpage world; requests, replies and events travel over that channel rather than the page window.What changed
content-script/messagePortServer.js(new) creates the channel and hands the port over. It is called at module top, ahead of the asynchronous should-inject/blocklist decision, which now only gates whether requests get serviced. Requests that arrive before the scope registers a handler are buffered and flushed.providers/postMessage.tsrequests its port and retries until it arrives — either world can initialise first — then routes replies to the waiting caller by id.accountChangednow reaches the providers over the channel instead of being read off the page window.webln,nostr,webbtc,liquid,alby) move to the shared helper, which removes a fair amount of duplicated listener code.Scope of the change, and what it does not do
The port handover is negotiated with window messages, so the channel is private only from scripts that were not already running when the port was transferred. On MV2 the inpage script is injected inline at
document_startand that ordering holds. On MV3 the main-world script is registered separately and the ordering is not guaranteed, so a script that is already running in the frame can still take part in the handover.Treat this as a narrowing of the shared page-window surface, not as a guarantee that provider results are authenticated to the page. The security boundary remains the extension-rendered confirmation UI, which the page cannot draw. Worth deciding separately (not in this PR):
sendPaymentpreimage can be checked against the BOLT11 payment hash by the integratorwindow.Object.freeze()on the instances is not viable as-is:providerBaseassignsthis._isEnabledafter construction and these are ES modules, so freezing would makeenable()/isEnabled()throw. A non-configurablewindow.webln/window.nostrwould prevent replacement but risks breaking coexistence with other WebLN/NIP-07 extensions.Tests
providers/__tests__/postMessage.test.ts— a request goes over the port and its reply resolves the call; a reply of the old shape replayed on the page window does not resolve it and the genuine reply still does; a second reply for a settled id does not overwrite the result; errors reject; events arrive over the port and are not picked up from the window.content-script/__tests__/messagePortServer.test.ts— a request over the port is serviced and answered on it, requests before a handler are buffered, the port is transferred once, another scope's handshake is ignored, events go over the port.MessageChannel.tests/unit/helpers/fakeMessageChannel.tssupplies a minimal stand-in, installed per test file rather than globally, because msw depends on the real one.yarn lint,yarn tsc:compileandyarn test:unitpass (77 suites, 179 passed, 2 pre-existing skips), andyarn build:chromesucceeds.