Skip to content
49 changes: 28 additions & 21 deletions packages/extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
import './background-trusted-prelude.js';
import type { Json } from '@metamask/utils';
import { CommandMethod } from '@ocap/utils';
import { CommandMethod, isCommandReply } from '@ocap/utils';
import type { Command, CommandFunction } from '@ocap/utils';

import {
ExtensionMessageTarget,
isExtensionRuntimeMessage,
makeHandledCallback,
} from './shared.js';

/**
* Send a message to the offscreen document.
*
* @param method - The message type.
* @param params - The message data.
* @param params.name - The name to include in the message.
*/
const sendCommand: CommandFunction<Promise<void>> = async (
method: CommandMethod,
params?: Command['params'],
) => {
await provideOffScreenDocument();

await chrome.runtime.sendMessage({
target: ExtensionMessageTarget.Offscreen,
payload: {
method,
params: params ?? null,
},
});
};

// globalThis.kernel will exist due to dev-console.js in background-trusted-prelude.js
Object.defineProperties(globalThis.kernel, {
capTpCall: {
Expand Down Expand Up @@ -44,25 +67,6 @@ chrome.action.onClicked.addListener(() => {
sendCommand(CommandMethod.Ping).catch(console.error);
});

/**
* Send a message to the offscreen document.
*
* @param method - The message type.
* @param params - The message data.
* @param params.name - The name to include in the message.
*/
async function sendCommand(method: string, params?: Json): Promise<void> {
await provideOffScreenDocument();

await chrome.runtime.sendMessage({
target: ExtensionMessageTarget.Offscreen,
payload: {
method,
params: params ?? null,
},
});
}

/**
* Create the offscreen document if it doesn't already exist.
*/
Expand All @@ -79,7 +83,10 @@ async function provideOffScreenDocument(): Promise<void> {
// Handle replies from the offscreen document
chrome.runtime.onMessage.addListener(
makeHandledCallback(async (message: unknown) => {
if (!isExtensionRuntimeMessage(message)) {
if (
!isExtensionRuntimeMessage(message) ||
!isCommandReply(message.payload)
) {
console.error('Background received unexpected message', message);
return;
}
Expand Down
7 changes: 5 additions & 2 deletions packages/extension/src/iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { makeExo } from '@endo/exo';
import { M } from '@endo/patterns';
import { Supervisor } from '@ocap/kernel';
import { makeMessagePortStreamPair, receiveMessagePort } from '@ocap/streams';
import type { StreamEnvelope } from '@ocap/utils';
import type { StreamEnvelope, StreamEnvelopeReply } from '@ocap/utils';

main().catch(console.error);

Expand All @@ -11,7 +11,10 @@ main().catch(console.error);
*/
async function main(): Promise<void> {
const port = await receiveMessagePort();
const streams = makeMessagePortStreamPair<StreamEnvelope>(port);
const streams = makeMessagePortStreamPair<
StreamEnvelope,
StreamEnvelopeReply
>(port);

const bootstrap = makeExo(
'TheGreatFrangooly',
Expand Down
34 changes: 20 additions & 14 deletions packages/extension/src/kernel-worker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './kernel-worker-trusted-prelude.js';
import type { Command } from '@ocap/utils';
import { CommandMethod } from '@ocap/utils';
import { CommandMethod, isCommand } from '@ocap/utils';
import type { CommandReply, CommandReplyFunction } from '@ocap/utils';
import type { Database } from '@sqlite.org/sqlite-wasm';
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';

Expand Down Expand Up @@ -158,11 +158,27 @@ async function main(): Promise<void> {
sqlKVSet.reset();
}

/**
* Reply to the background script.
*
* @param method - The message method.
* @param params - The message params.
*/
const reply: CommandReplyFunction = (
method: CommandMethod,
params?: CommandReply['params'],
) => {
postMessage({ method, params });
};

// Handle messages from the console service worker
onmessage = async (event) => {
const message = event.data;
const { method, params } = message as Command;
if (!isCommand(event.data)) {
console.log('received unexpected message', event.data);
}
const { method, params } = event.data;
console.log('received message: ', method, params);

switch (method) {
case CommandMethod.Evaluate:
reply(CommandMethod.Evaluate, await evaluate(params));
Expand Down Expand Up @@ -206,16 +222,6 @@ async function main(): Promise<void> {
}
};

/**
* Reply to the background script.
*
* @param method - The message method.
* @param params - The message params.
*/
function reply(method: CommandMethod, params?: string): void {
postMessage({ method, params });
}

/**
* Evaluate a string in the default iframe.
*
Expand Down
7 changes: 5 additions & 2 deletions packages/extension/src/makeIframeVatWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createWindow } from '@metamask/snaps-utils';
import type { VatId, VatWorker } from '@ocap/kernel';
import type { initializeMessageChannel } from '@ocap/streams';
import { makeMessagePortStreamPair } from '@ocap/streams';
import type { StreamEnvelope } from '@ocap/utils';
import type { StreamEnvelopeReply, StreamEnvelope } from '@ocap/utils';

const IFRAME_URI = 'iframe.html';

Expand All @@ -22,7 +22,10 @@ export const makeIframeVatWorker = (
init: async () => {
const newWindow = await createWindow(IFRAME_URI, getHtmlId(id));
const port = await getPort(newWindow);
const streams = makeMessagePortStreamPair<StreamEnvelope>(port);
const streams = makeMessagePortStreamPair<
StreamEnvelopeReply,
StreamEnvelope
>(port);

return [streams, newWindow];
},
Expand Down
54 changes: 28 additions & 26 deletions packages/extension/src/offscreen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Kernel } from '@ocap/kernel';
import { initializeMessageChannel } from '@ocap/streams';
import { CommandMethod } from '@ocap/utils';
import type { Command, CapTpPayload } from '@ocap/utils';
import { CommandMethod, isCommand } from '@ocap/utils';
import type { CommandReply, Command, CommandReplyFunction } from '@ocap/utils';

import { makeIframeVatWorker } from './makeIframeVatWorker.js';
import {
Expand All @@ -22,6 +22,25 @@ async function main(): Promise<void> {
worker: makeIframeVatWorker('default', initializeMessageChannel),
});

/**
* Reply to a command from the background script.
*
* @param method - The command method.
* @param params - The command parameters.
*/
const replyToCommand: CommandReplyFunction<Promise<void>> = async (
method: CommandMethod,
params?: CommandReply['params'],
) => {
await chrome.runtime.sendMessage({
target: ExtensionMessageTarget.Background,
payload: {
method,
params: params ?? null,
},
});
};

const receiveFromKernel = async (event: MessageEvent): Promise<void> => {
// For the time being, the only messages that come from the kernel worker are replies to actions
// initiated from the console, so just forward these replies to the console. This will need to
Expand All @@ -32,8 +51,10 @@ async function main(): Promise<void> {
// XXX TODO: Using the IframeMessage type here assumes that the set of response messages is the
// same as (and aligns perfectly with) the set of command messages, which is horribly, terribly,
// awfully wrong. Need to add types to account for the replies.
const message = event.data as Command;
const { method, params } = message;
if (!isCommand(event.data)) {
console.error('kernel received unexpected message', event.data);
}
const { method, params } = event.data;
let result: string;
const possibleError = params as unknown as Error;
if (possibleError?.message && possibleError?.stack) {
Expand All @@ -59,7 +80,7 @@ async function main(): Promise<void> {
// user console.
chrome.runtime.onMessage.addListener(
makeHandledCallback(async (message: unknown) => {
if (!isExtensionRuntimeMessage(message)) {
if (!isExtensionRuntimeMessage(message) || !isCommand(message.payload)) {
console.error('Offscreen received unexpected message', message);
return;
}
Expand Down Expand Up @@ -101,7 +122,7 @@ async function main(): Promise<void> {
break;
case CommandMethod.KVGet:
case CommandMethod.KVSet:
sendKernelMessage(payload as unknown as CapTpPayload);
sendKernelMessage(payload);
break;
default:
console.error(
Expand All @@ -113,25 +134,6 @@ async function main(): Promise<void> {
}),
);

/**
* Reply to a command from the background script.
*
* @param method - The command method.
* @param params - The command parameters.
*/
async function replyToCommand(
method: CommandMethod,
params?: string,
): Promise<void> {
await chrome.runtime.sendMessage({
target: ExtensionMessageTarget.Background,
payload: {
method,
params: params ?? null,
},
});
}

/**
* Evaluate a string in the default iframe.
*
Expand Down Expand Up @@ -159,7 +161,7 @@ async function main(): Promise<void> {
*
* @param payload - The message to send.
*/
function sendKernelMessage(payload: CapTpPayload): void {
function sendKernelMessage(payload: Command): void {
kernelWorker.postMessage(payload);
}
}
10 changes: 6 additions & 4 deletions packages/extension/src/shared.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isObject } from '@metamask/utils';
import type { Command } from '@ocap/utils';
import { isCommand } from '@ocap/utils';
import type { Command, CommandReply } from '@ocap/utils';
import { isCommand, isCommandReply } from '@ocap/utils';

export type VatId = string;

Expand All @@ -10,7 +10,9 @@ export enum ExtensionMessageTarget {
}

export type ExtensionRuntimeMessage = {
payload: Command;
// On some systems, including CI, ESLint complains of overlap between the union operands.
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
payload: Command | CommandReply;
target: ExtensionMessageTarget;
};

Expand All @@ -22,7 +24,7 @@ export const isExtensionRuntimeMessage = (
Object.values(ExtensionMessageTarget).includes(
message.target as ExtensionMessageTarget,
) &&
isCommand(message.payload);
(isCommand(message.payload) || isCommandReply(message.payload));

/**
* Wrap an async callback to ensure any errors are at least logged.
Expand Down
11 changes: 6 additions & 5 deletions packages/kernel/src/Supervisor.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import '@ocap/shims/endoify';
import { makeMessagePortStreamPair, MessagePortWriter } from '@ocap/streams';
import { delay } from '@ocap/test-utils';
import type { StreamEnvelope } from '@ocap/utils';
import type { StreamEnvelope, StreamEnvelopeReply } from '@ocap/utils';
import * as ocapUtils from '@ocap/utils';
import { CommandMethod } from '@ocap/utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
Expand All @@ -17,9 +17,10 @@ describe('Supervisor', () => {

messageChannel = new MessageChannel();

const streams = makeMessagePortStreamPair<StreamEnvelope>(
messageChannel.port1,
);
const streams = makeMessagePortStreamPair<
StreamEnvelope,
StreamEnvelopeReply
>(messageChannel.port1);
supervisor = new Supervisor({ id: 'test-id', streams });
});

Expand Down Expand Up @@ -78,7 +79,7 @@ describe('Supervisor', () => {

expect(replySpy).toHaveBeenCalledWith('message-id', {
method: CommandMethod.CapTpInit,
params: null,
params: '~~~ CapTP Initialized ~~~',
});
});

Expand Down
Loading