diff --git a/packages/extension/src/background.ts b/packages/extension/src/background.ts index 82d3a4adb..0ff526dcd 100644 --- a/packages/extension/src/background.ts +++ b/packages/extension/src/background.ts @@ -32,6 +32,11 @@ Object.defineProperties(globalThis.kernel, { value: async (message: CommandMessage) => await streams.writer.next(message), }, + helloThere: { + value: async () => { + await streams.writer.next({ type: Command.HelloThere, data: null }); + } + } }); harden(globalThis.kernel); @@ -54,6 +59,7 @@ async function handleMessages(): Promise { case Command.CapTpCall: case Command.CapTpInit: case Command.Ping: + case Command.HelloThere: console.log(message.data); break; default: diff --git a/packages/extension/src/manifest.json b/packages/extension/src/manifest.json index 8d1d99109..9ddb680b6 100644 --- a/packages/extension/src/manifest.json +++ b/packages/extension/src/manifest.json @@ -10,7 +10,7 @@ "action": {}, "permissions": ["offscreen"], "sandbox": { - "pages": ["iframe.html"] + "pages": ["iframe.html", "poc-iframe-angel.html", "poc-iframe-shadow.html"] }, "content_security_policy": { "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-ancestors 'none';", diff --git a/packages/extension/src/message.ts b/packages/extension/src/message.ts index 96acc6a3a..61d637c68 100644 --- a/packages/extension/src/message.ts +++ b/packages/extension/src/message.ts @@ -5,6 +5,7 @@ export type MessageId = string; type DataObject = | Primitive + | Transferable | Promise | DataObject[] | { [key: string]: DataObject }; @@ -29,6 +30,7 @@ export enum Command { CapTpInit = 'makeCapTp', Evaluate = 'evaluate', Ping = 'ping', + HelloThere = 'helloThere' } export type CapTpPayload = { @@ -40,7 +42,8 @@ export type CommandMessage = | CommandLike | CommandLike | CommandLike - | CommandLike; + | CommandLike + | CommandLike; export type ExtensionMessage = CommandMessage; export type IframeMessage = CommandMessage; diff --git a/packages/extension/src/offscreen.ts b/packages/extension/src/offscreen.ts index 4f828733f..b5d97e7d7 100644 --- a/packages/extension/src/offscreen.ts +++ b/packages/extension/src/offscreen.ts @@ -1,7 +1,10 @@ +import { createWindow } from '@metamask/snaps-utils'; import { makeOffscreenBackgroundStreamPair } from './extension-stream-pairs.js'; import { IframeManager } from './iframe-manager.js'; import type { CommandMessage } from './message.js'; import { Command } from './message.js'; +import { performIntroduction } from '@ocap/streams'; +import { HELLO_THERE } from './poc-constants.js'; main().catch(console.error); @@ -37,6 +40,12 @@ async function main(): Promise { case Command.Ping: await reply(Command.Ping, 'pong'); break; + case Command.HelloThere: + console.log(HELLO_THERE); + await performIntroduction( + await createWindow('poc-iframe-angel.html', 'poc-iframe-angel'), + await createWindow('poc-iframe-shadow.html', 'poc-iframe-shadow')); + break; default: console.error( // @ts-expect-error The type of `message` is `never`, but this could happen at runtime. diff --git a/packages/extension/src/poc-constants.ts b/packages/extension/src/poc-constants.ts new file mode 100644 index 000000000..c5db32832 --- /dev/null +++ b/packages/extension/src/poc-constants.ts @@ -0,0 +1,3 @@ +export const HELLO_THERE = 'Hello there...'; +export const THE_ANGEL = 'The angel from my nightmare...'; +export const THE_SHADOW = 'The shadow in the background of the morgue.'; diff --git a/packages/extension/src/poc-iframe-angel.html b/packages/extension/src/poc-iframe-angel.html new file mode 100644 index 000000000..5ca47ee81 --- /dev/null +++ b/packages/extension/src/poc-iframe-angel.html @@ -0,0 +1,9 @@ + + + + + Poc Iframe Angel + + + + diff --git a/packages/extension/src/poc-iframe-angel.ts b/packages/extension/src/poc-iframe-angel.ts new file mode 100644 index 000000000..212c8fa9a --- /dev/null +++ b/packages/extension/src/poc-iframe-angel.ts @@ -0,0 +1,14 @@ +import { receiveMessagePort } from '@ocap/streams'; +import { THE_ANGEL } from './poc-constants.js'; + +main().catch(console.error); + +/** + * The main function for the iframe. + */ +async function main(): Promise { + const port = await receiveMessagePort(); + + console.log(THE_ANGEL); + port.postMessage(THE_ANGEL); +} diff --git a/packages/extension/src/poc-iframe-shadow.html b/packages/extension/src/poc-iframe-shadow.html new file mode 100644 index 000000000..063b86d2a --- /dev/null +++ b/packages/extension/src/poc-iframe-shadow.html @@ -0,0 +1,9 @@ + + + + + Poc Iframe Shadow + + + + diff --git a/packages/extension/src/poc-iframe-shadow.ts b/packages/extension/src/poc-iframe-shadow.ts new file mode 100644 index 000000000..9cac29c79 --- /dev/null +++ b/packages/extension/src/poc-iframe-shadow.ts @@ -0,0 +1,16 @@ +import { receiveMessagePort } from '@ocap/streams'; +import { THE_ANGEL, THE_SHADOW } from './poc-constants.js'; + +main().catch(console.error); + +/** + * The main function for the iframe. + */ +async function main(): Promise { + (await receiveMessagePort()).onmessage = (message: MessageEvent): void => { + if (message.data !== THE_ANGEL) { + return; + } + console.log(THE_SHADOW); + } +} diff --git a/packages/extension/vite.config.ts b/packages/extension/vite.config.ts index 09d5a97d5..60de62854 100644 --- a/packages/extension/vite.config.ts +++ b/packages/extension/vite.config.ts @@ -41,6 +41,8 @@ export default defineConfig(({ mode }) => ({ background: path.resolve(projectRoot, 'background.ts'), offscreen: path.resolve(projectRoot, 'offscreen.html'), iframe: path.resolve(projectRoot, 'iframe.html'), + iframe1: path.resolve(projectRoot, 'poc-iframe-angel.html'), + iframe2: path.resolve(projectRoot, 'poc-iframe-shadow.html'), }, output: { entryFileNames: '[name].js', diff --git a/packages/streams/src/index.test.ts b/packages/streams/src/index.test.ts index 0a66aa6ee..40736dfd1 100644 --- a/packages/streams/src/index.test.ts +++ b/packages/streams/src/index.test.ts @@ -12,6 +12,7 @@ describe('index', () => { 'makeMessagePortWriter', 'initializeMessageChannel', 'receiveMessagePort', + 'performIntroduction', ]), ); }); diff --git a/packages/streams/src/index.ts b/packages/streams/src/index.ts index e7e93db4b..50dde45c1 100644 --- a/packages/streams/src/index.ts +++ b/packages/streams/src/index.ts @@ -1,6 +1,7 @@ export { initializeMessageChannel, receiveMessagePort, + performIntroduction, } from './message-channel.js'; export type { StreamPair } from './stream-pair.js'; export type { Connection } from './connection.js'; diff --git a/packages/streams/src/message-channel.ts b/packages/streams/src/message-channel.ts index 42ce3f1cd..b7929358b 100644 --- a/packages/streams/src/message-channel.ts +++ b/packages/streams/src/message-channel.ts @@ -110,3 +110,47 @@ export async function receiveMessagePort(): Promise { window.addEventListener('message', listener); return promise; } + +/** + * Expects both target windows are awaiting a message port with receiveMessagePort(); + * + * @param targetWindow1 + * @param targetWindow2 + * @returns + */ +export async function performIntroduction( + targetWindow1: Window, + targetWindow2: Window, +): Promise { + const { port1, port2 } = new MessageChannel(); + + const { promise, resolve, reject } = makePromiseKit(); + // Assigning to the `onmessage` property initializes the port's message queue. + port1.onmessage = (message: MessageEvent): void => { + if (!isAckMessage(message.data)) { + reject( + new Error( + `Received unexpected message via message port:\n${stringify( + message.data, + )}`, + ), + ); + return; + } + + resolve(port1); + }; + + const initMessage: InitializeMessage = { + type: MessageType.Initialize, + }; + targetWindow1.postMessage(initMessage, '*', [port2]); + + await promise.catch((error) => { + port1.close(); + throw error; + }) + .finally(() => (port1.onmessage = null)); + + targetWindow2.postMessage(initMessage, '*', [port1]); +} \ No newline at end of file