diff --git a/packages/streams/src/ChromeRuntimeStream.test.ts b/packages/streams/src/ChromeRuntimeStream.test.ts index b1e928216..eaa949691 100644 --- a/packages/streams/src/ChromeRuntimeStream.test.ts +++ b/packages/streams/src/ChromeRuntimeStream.test.ts @@ -20,8 +20,10 @@ import { const makeEnvelope = ( value: unknown, target: ChromeRuntimeStreamTarget, + source: ChromeRuntimeStreamTarget, ): MessageEnvelope => ({ target, + source, payload: value, }); @@ -34,10 +36,11 @@ const makeRuntime = (extensionId: string = EXTENSION_ID) => { const dispatchRuntimeMessage = ( message: unknown, target: ChromeRuntimeStreamTarget = ChromeRuntimeStreamTarget.Background, + source: ChromeRuntimeStreamTarget = ChromeRuntimeStreamTarget.Offscreen, senderId: string = extensionId, ): void => { listeners.forEach((listener) => - listener(makeEnvelope(message, target), { id: senderId }), + listener(makeEnvelope(message, target, source), { id: senderId }), ); }; @@ -69,6 +72,7 @@ describe('ChromeRuntimeReader', () => { const reader = new ChromeRuntimeReader( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); expect(reader).toBeInstanceOf(ChromeRuntimeReader); @@ -81,6 +85,7 @@ describe('ChromeRuntimeReader', () => { const reader = new ChromeRuntimeReader( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); const message = { foo: 'bar' }; @@ -94,12 +99,18 @@ describe('ChromeRuntimeReader', () => { const reader = new ChromeRuntimeReader( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); const nextP = reader.next(); const message1 = { foo: 'bar' }; const message2 = { fizz: 'buzz' }; - dispatchRuntimeMessage(message1, undefined, 'other-extension-id'); + dispatchRuntimeMessage( + message1, + ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, + 'other-extension-id', + ); dispatchRuntimeMessage(message2); expect(await nextP).toStrictEqual(makePendingResult(message2)); @@ -110,6 +121,7 @@ describe('ChromeRuntimeReader', () => { const reader = new ChromeRuntimeReader( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); const nextP = reader.next(); @@ -133,20 +145,18 @@ describe('ChromeRuntimeReader', () => { const reader = new ChromeRuntimeReader( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); const nextP = reader.next(); vi.spyOn(console, 'warn'); const message1 = { foo: 'bar' }; - // @ts-expect-error Intentional destructive testing - dispatchRuntimeMessage(message1, 'foo'); - - expect(console.warn).toHaveBeenCalledWith( - `ChromeRuntimeReader received message for unexpected target: ${stringify({ - target: 'foo', - payload: message1, - })}`, + dispatchRuntimeMessage( + message1, + // @ts-expect-error Intentional destructive testing + 'foo', + ChromeRuntimeStreamTarget.Offscreen, ); const message2 = { fizz: 'buzz' }; @@ -159,6 +169,7 @@ describe('ChromeRuntimeReader', () => { const reader = new ChromeRuntimeReader( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); expect(listeners).toHaveLength(1); @@ -175,6 +186,7 @@ describe('ChromeRuntimeReader', () => { const reader = new ChromeRuntimeReader( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, onEnd, ); @@ -192,6 +204,7 @@ describe.concurrent('ChromeRuntimeWriter', () => { const writer = new ChromeRuntimeWriter( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); expect(writer).toBeInstanceOf(ChromeRuntimeWriter); @@ -203,6 +216,7 @@ describe.concurrent('ChromeRuntimeWriter', () => { const writer = new ChromeRuntimeWriter( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); const message = { foo: 'bar' }; @@ -210,7 +224,11 @@ describe.concurrent('ChromeRuntimeWriter', () => { expect(await nextP).toStrictEqual(makePendingResult(undefined)); expect(runtime.sendMessage).toHaveBeenCalledWith( - makeEnvelope(message, ChromeRuntimeStreamTarget.Background), + makeEnvelope( + message, + ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, + ), ); }); @@ -220,6 +238,7 @@ describe.concurrent('ChromeRuntimeWriter', () => { const writer = new ChromeRuntimeWriter( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, onEnd, ); @@ -237,13 +256,25 @@ describe.concurrent('ChromeRuntimeDuplexStream', () => { const duplexStreamP = ChromeRuntimeDuplexStream.make( asChromeRuntime(runtime), ChromeRuntimeStreamTarget.Background, - ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Offscreen, ); dispatchRuntimeMessage(makeAck()); return [await duplexStreamP, { runtime, dispatchRuntimeMessage }] as const; }; + it('throws an error when localTarget and remoteTarget are the same', async () => { + const { runtime } = makeRuntime(); + + await expect( + ChromeRuntimeDuplexStream.make( + asChromeRuntime(runtime), + ChromeRuntimeStreamTarget.Background, + ChromeRuntimeStreamTarget.Background, + ), + ).rejects.toThrow('localTarget and remoteTarget must be different'); + }); + it('constructs a ChromeRuntimeDuplexStream', async () => { const [duplexStream] = await makeDuplexStream(); diff --git a/packages/streams/src/ChromeRuntimeStream.ts b/packages/streams/src/ChromeRuntimeStream.ts index 903c77e6c..9ad5e8f65 100644 --- a/packages/streams/src/ChromeRuntimeStream.ts +++ b/packages/streams/src/ChromeRuntimeStream.ts @@ -26,10 +26,12 @@ import type { Dispatchable } from './utils.js'; export enum ChromeRuntimeStreamTarget { Background = 'background', Offscreen = 'offscreen', + Devtools = 'devtools', } export type MessageEnvelope = { target: ChromeRuntimeStreamTarget; + source: ChromeRuntimeStreamTarget; payload: Payload; }; @@ -39,6 +41,7 @@ const isMessageEnvelope = ( typeof message === 'object' && message !== null && 'target' in message && + 'source' in message && 'payload' in message; /** @@ -56,11 +59,14 @@ export class ChromeRuntimeReader extends BaseReader { readonly #target: ChromeRuntimeStreamTarget; + readonly #source: ChromeRuntimeStreamTarget; + readonly #extensionId: string; constructor( runtime: ChromeRuntime, target: ChromeRuntimeStreamTarget, + source: ChromeRuntimeStreamTarget, onEnd?: OnEnd, ) { // eslint-disable-next-line prefer-const @@ -76,6 +82,7 @@ export class ChromeRuntimeReader extends BaseReader { this.#receiveInput = super.getReceiveInput(); this.#target = target; + this.#source = source; this.#extensionId = runtime.id; messageListener = this.#onMessage.bind(this); @@ -99,11 +106,11 @@ export class ChromeRuntimeReader extends BaseReader { return; } - if (message.target !== this.#target) { - console.warn( - `ChromeRuntimeReader received message for unexpected target: ${stringify( - message, - )}`, + if (message.target !== this.#target || message.source !== this.#source) { + console.debug( + `ChromeRuntimeReader received message with incorrect target or source: ${stringify(message)}`, + `Expected target: ${this.#target}`, + `Expected source: ${this.#source}`, ); return; } @@ -126,6 +133,7 @@ export class ChromeRuntimeWriter extends BaseWriter { constructor( runtime: ChromeRuntime, target: ChromeRuntimeStreamTarget, + source: ChromeRuntimeStreamTarget, onEnd?: OnEnd, ) { super( @@ -133,6 +141,7 @@ export class ChromeRuntimeWriter extends BaseWriter { async (value: Dispatchable) => { await runtime.sendMessage({ target, + source, payload: value, }); }, @@ -172,13 +181,19 @@ export class ChromeRuntimeDuplexStream< const reader = new ChromeRuntimeReader( runtime, localTarget, + remoteTarget, async () => { await writer.return(); }, ); - writer = new ChromeRuntimeWriter(runtime, remoteTarget, async () => { - await reader.return(); - }); + writer = new ChromeRuntimeWriter( + runtime, + remoteTarget, + localTarget, + async () => { + await reader.return(); + }, + ); super(reader, writer); harden(this); } @@ -188,6 +203,10 @@ export class ChromeRuntimeDuplexStream< localTarget: ChromeRuntimeStreamTarget, remoteTarget: ChromeRuntimeStreamTarget, ): Promise> { + if (localTarget === remoteTarget) { + throw new Error('localTarget and remoteTarget must be different'); + } + const stream = new ChromeRuntimeDuplexStream( runtime, localTarget,