Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .depcheckrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@ ignores:
- 'simple-git-hooks'
- 'typedoc'
- 'typescript'

# These are peer dependencies of various modules we actually do
# depend on, which have been elevated to full dependencies (even
# though we don't actually depend on them) in order to work around a
# bug in the bundler wherein it gets confused by absent peer
# dependencies. These should be removed (both from here and from
# the declared dependencies in package.json) once KK fixes the
# bundler, which should be RSN.
- '@metamask/approval-controller'
- 'ava'
- 'webextension-polyfill'
- '@types/webextension-polyfill'
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"build:docs": "yarn workspaces foreach --all --exclude @ocap/monorepo --exclude @ocap/extension --parallel --interlaced --verbose run build:docs",
"build:source": "ts-bridge --project tsconfig.build.json --verbose && yarn build:special",
"build:special": "yarn workspace @ocap/shims run build && yarn workspace @ocap/extension run build",
"bundle": "node ./scripts/bundle-vat.js",
"changelog:update": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run changelog:update",
"changelog:validate": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run changelog:validate",
"clean": "rimraf --glob './*.tsbuildinfo' && yarn workspaces foreach --all --parallel --interlaced --verbose run clean",
Expand Down Expand Up @@ -49,6 +50,8 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.16.4",
"@endo/bundle-source": "^3.5.0",
"@endo/init": "^1.1.6",
"@lavamoat/allow-scripts": "^3.0.4",
"@lavamoat/preinstall-always-fail": "^2.0.0",
"@metamask/auto-changelog": "^3.4.4",
Expand All @@ -60,6 +63,7 @@
"@ts-bridge/shims": "^0.1.1",
"@types/lodash": "^4.17.7",
"@types/node": "^18.18.14",
"@types/webextension-polyfill": "^0",
"@typescript-eslint/eslint-plugin": "^8.8.1",
"@typescript-eslint/parser": "^8.8.1",
"@typescript-eslint/utils": "^8.8.1",
Expand Down Expand Up @@ -107,5 +111,10 @@
},
"resolutions": {
"cookie": "^0.7.0"
},
"dependencies": {
"@metamask/approval-controller": "^7.1.1",
"ava": "^6.2.0",
"webextension-polyfill": "^0.12.0"
}
}
4 changes: 4 additions & 0 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@
"files": [
"dist/"
],
"workspaces": [
"vats"
],
"scripts": {
"build": "yarn build:vite && yarn test:build",
"build:dev": "yarn build:vite:dev && yarn test:build",
"build:vite": "vite build --config vite.config.ts",
"build:vite:dev": "yarn build:vite --mode development",
"bundle": "node ../../scripts/bundle-vat.js",
"changelog:validate": "../../scripts/validate-changelog.sh @ocap/extension",
"clean": "rimraf --glob ./dist './*.tsbuildinfo'",
"lint": "yarn lint:ts && yarn lint:eslint && yarn lint:misc --check && yarn constraints && yarn lint:dependencies",
Expand Down
9 changes: 7 additions & 2 deletions packages/extension/src/kernel/VatWorkerClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import '@ocap/shims/endoify';
import type { VatId, VatWorkerServiceCommandReply } from '@ocap/kernel';
import type {
VatId,
VatWorkerServiceCommandReply,
VatConfig,
} from '@ocap/kernel';
import { VatWorkerServiceCommandMethod } from '@ocap/kernel';
import { delay } from '@ocap/test-utils';
import type { Logger } from '@ocap/utils';
Expand Down Expand Up @@ -66,8 +70,9 @@ describe('ExtensionVatWorkerClient', () => {
it(`calls logger.error when receiving a ${VatWorkerServiceCommandMethod.launch} reply without a port`, async () => {
const errorSpy = vi.spyOn(clientLogger, 'error');
const vatId: VatId = 'v0';
const vatConfig: VatConfig = { sourceSpec: 'not-really-there.js' };
// eslint-disable-next-line @typescript-eslint/no-floating-promises
client.launch(vatId);
client.launch(vatId, vatConfig);
const reply = {
id: 'm1',
payload: {
Expand Down
4 changes: 3 additions & 1 deletion packages/extension/src/kernel/VatWorkerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
VatWorkerService,
VatId,
VatWorkerServiceCommand,
VatConfig,
} from '@ocap/kernel';
import type { DuplexStream, MultiplexEnvelope } from '@ocap/streams';
import { isMultiplexEnvelope, MessagePortDuplexStream } from '@ocap/streams';
Expand Down Expand Up @@ -75,10 +76,11 @@ export class ExtensionVatWorkerClient implements VatWorkerService {

async launch(
vatId: VatId,
vatConfig: VatConfig,
): Promise<DuplexStream<MultiplexEnvelope, MultiplexEnvelope>> {
return this.#sendMessage({
method: VatWorkerServiceCommandMethod.launch,
params: { vatId },
params: { vatId, vatConfig },
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/extension/src/kernel/VatWorkerServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('ExtensionVatWorkerServer', () => {
id: 'm0',
payload: {
method: VatWorkerServiceCommandMethod.launch,
params: { vatId },
params: { vatId, vatConfig: { sourceSpec: 'bogus.js' } },
},
});
clientPort.postMessage({
Expand Down
24 changes: 17 additions & 7 deletions packages/extension/src/kernel/VatWorkerServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import {
isVatWorkerServiceCommand,
VatWorkerServiceCommandMethod,
} from '@ocap/kernel';
import type { VatWorkerServiceCommandReply, VatId } from '@ocap/kernel';
import type {
VatWorkerServiceCommandReply,
VatId,
VatConfig,
} from '@ocap/kernel';
import type { Logger } from '@ocap/utils';
import { makeHandledCallback, makeLogger } from '@ocap/utils';

Expand Down Expand Up @@ -86,11 +90,17 @@ export class ExtensionVatWorkerServer {
};

switch (method) {
case VatWorkerServiceCommandMethod.launch:
await this.#launch(params.vatId)
.then((port) => this.#postMessage({ id, payload }, [port]))
.catch(async (error) => handleError(error, params.vatId));
case VatWorkerServiceCommandMethod.launch: {
const { vatId, vatConfig } = params;
const replyParams = { vatId };
const replyPayload = { method, params: replyParams };
await this.#launch(vatId, vatConfig)
.then((port) =>
this.#postMessage({ id, payload: replyPayload }, [port]),
)
.catch(async (error) => handleError(error, vatId));
break;
}
case VatWorkerServiceCommandMethod.terminate:
await this.#terminate(params.vatId)
.then(() => this.#postMessage({ id, payload }))
Expand All @@ -113,12 +123,12 @@ export class ExtensionVatWorkerServer {
}
}

async #launch(vatId: VatId): Promise<MessagePort> {
async #launch(vatId: VatId, vatConfig: VatConfig): Promise<MessagePort> {
if (this.#vatWorkers.has(vatId)) {
throw new VatAlreadyExistsError(vatId);
}
const vatWorker = this.#makeWorker(vatId);
const [port] = await vatWorker.launch();
const [port] = await vatWorker.launch(vatConfig);
this.#vatWorkers.set(vatId, vatWorker);
return port;
}
Expand Down
22 changes: 13 additions & 9 deletions packages/extension/src/kernel/handle-panel-message.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '../../../test-utils/src/env/mock-endo.ts';
import { define, literal, object } from '@metamask/superstruct';
import type { Kernel, KernelCommand, VatId } from '@ocap/kernel';
import type { Kernel, KernelCommand, VatId, VatConfig } from '@ocap/kernel';
import { describe, it, expect, vi, beforeEach } from 'vitest';

import type { KernelControlCommand } from './messages.js';
Expand All @@ -16,7 +16,9 @@ vi.mock('@ocap/utils', () => ({
vi.mock('@ocap/kernel', () => ({
isKernelCommand: () => true,
isVatId: () => true,
isVatConfig: () => true,
VatIdStruct: define<VatId>('VatId', () => true),
VatConfigStruct: define<VatConfig>('VatConfig', () => true),
KernelSendMessageStruct: object({
id: literal('v0'),
payload: object({
Expand Down Expand Up @@ -60,34 +62,36 @@ describe('handlePanelMessage', () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'launchVat',
params: { id: 'v0' },
params: { sourceSpec: 'bogus.js' },
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.launchVat).toHaveBeenCalledWith({ id: 'v0' });
expect(mockKernel.launchVat).toHaveBeenCalledWith({
sourceSpec: 'bogus.js',
});
expect(response).toStrictEqual({
method: 'launchVat',
params: null,
});
});

it('should handle invalid vat ID', async () => {
it('should handle invalid vat configuration', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const kernel = await import('@ocap/kernel');
const isVatIdSpy = vi.spyOn(kernel, 'isVatId');
isVatIdSpy.mockReturnValue(false);
const isVatConfigSpy = vi.spyOn(kernel, 'isVatConfig');
isVatConfigSpy.mockReturnValue(false);

const message: KernelControlCommand = {
method: 'launchVat',
params: { id: 'invalid' as VatId },
params: { bogus: 'bogus.js' } as unknown as VatConfig,
};

const response = await handlePanelMessage(mockKernel, message);

expect(response).toStrictEqual({
method: 'launchVat',
params: { error: 'Valid vat id required' },
params: { error: 'Valid vat config required' },
});
});

Expand Down Expand Up @@ -347,7 +351,7 @@ describe('handlePanelMessage', () => {

const message: KernelControlCommand = {
method: 'launchVat',
params: { id: 'v0' },
params: { sourceSpec: 'bogus.js' },
};

const response = await handlePanelMessage(mockKernel, message);
Expand Down
7 changes: 4 additions & 3 deletions packages/extension/src/kernel/handle-panel-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isKernelCommand,
KernelSendMessageStruct,
isVatId,
isVatConfig,
} from '@ocap/kernel';
import { makeLogger } from '@ocap/utils';

Expand All @@ -27,10 +28,10 @@ export async function handlePanelMessage(
try {
switch (message.method) {
case KernelControlMethod.launchVat: {
if (!isVatId(message.params.id)) {
throw new Error('Valid vat id required');
if (!isVatConfig(message.params)) {
throw new Error('Valid vat config required');
}
await kernel.launchVat({ id: message.params.id });
await kernel.launchVat(message.params);
return { method: KernelControlMethod.launchVat, params: null };
}

Expand Down
4 changes: 2 additions & 2 deletions packages/extension/src/kernel/iframe-vat-worker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createWindow } from '@metamask/snaps-utils';
import type { VatId } from '@ocap/kernel';
import type { VatId, VatConfig } from '@ocap/kernel';
import type { initializeMessageChannel } from '@ocap/streams';

import type { VatWorker } from './vat-worker-service.js';
Expand All @@ -12,7 +12,7 @@ export const makeIframeVatWorker = (
): VatWorker => {
const vatHtmlId = `ocap-iframe-${id}`;
return {
launch: async () => {
launch: async (_vatConfig: VatConfig) => {
const newWindow = await createWindow({
uri: IFRAME_URI,
id: vatHtmlId,
Expand Down
38 changes: 34 additions & 4 deletions packages/extension/src/kernel/kernel-worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { KernelCommand, KernelCommandReply } from '@ocap/kernel';
import type {
KernelCommand,
KernelCommandReply,
ClusterConfig,
} from '@ocap/kernel';
import { Kernel } from '@ocap/kernel';
import {
MessagePortDuplexStream,
Expand All @@ -10,10 +14,37 @@ import { makeLogger } from '@ocap/utils';

import { handlePanelMessage } from './handle-panel-message.js';
import type { KernelControlCommand, KernelControlReply } from './messages.js';
import { runVatLifecycle } from './run-vat-lifecycle.js';
import { makeSQLKVStore } from './sqlite-kv-store.js';
import { ExtensionVatWorkerClient } from './VatWorkerClient.js';

const bundleHost = 'http://localhost:3000'; // XXX placeholder
const sampleBundle = 'sample-vat.bundle';
const bundleURL = `${bundleHost}/${sampleBundle}`;

const defaultSubcluster: ClusterConfig = {
bootstrap: 'alice',
vats: {
alice: {
bundleSpec: bundleURL,
parameters: {
name: 'Alice',
},
},
bob: {
bundleSpec: bundleURL,
parameters: {
name: 'Bob',
},
},
carol: {
bundleSpec: bundleURL,
parameters: {
name: 'Carol',
},
},
},
};

const logger = makeLogger('[kernel worker]');

main().catch(logger.error);
Expand Down Expand Up @@ -66,8 +97,7 @@ async function main(): Promise<void> {
});

// Run default kernel lifecycle
await runVatLifecycle(kernel, ['v1', 'v2', 'v3']);
await kernel.launchVat({ id: 'v0' });
await kernel.launchSubcluster(defaultSubcluster);

// Start multiplexer
await multiplexer.drainAll();
Expand Down
4 changes: 2 additions & 2 deletions packages/extension/src/kernel/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { Infer } from '@metamask/superstruct';
import type { Json } from '@metamask/utils';
import { UnsafeJsonStruct } from '@metamask/utils';
import type { VatId } from '@ocap/kernel';
import { VatIdStruct } from '@ocap/kernel';
import { VatConfigStruct, VatIdStruct } from '@ocap/kernel';
import type { TypeGuard } from '@ocap/utils';

export const KernelControlMethod = {
Expand Down Expand Up @@ -41,7 +41,7 @@ export const isKernelStatus: TypeGuard<KernelStatus> = (
const KernelControlCommandStruct = union([
object({
method: literal(KernelControlMethod.launchVat),
params: object({ id: VatIdStruct }),
params: VatConfigStruct,
}),
object({
method: literal(KernelControlMethod.restartVat),
Expand Down
13 changes: 8 additions & 5 deletions packages/extension/src/kernel/run-vat-lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import '../../../test-utils/src/env/mock-endo.ts';
import { define } from '@metamask/superstruct';
import type { NonEmptyArray } from '@metamask/utils';
import { Kernel, VatCommandMethod } from '@ocap/kernel';
import type { Vat, VatId } from '@ocap/kernel';
import type { Vat, VatId, VatConfig } from '@ocap/kernel';
import { describe, it, expect, vi, beforeEach } from 'vitest';

import { runVatLifecycle } from './run-vat-lifecycle';
Expand All @@ -26,16 +26,19 @@ describe('runVatLifecycle', () => {
getVatIds: vi.fn(() => ['v1', 'v2']),
} as unknown as Kernel;

// Define test vats with correct VatId format
const testVats: NonEmptyArray<VatId> = ['v1', 'v2'];
// Define test vats with correct VatConfig format
const testVats: NonEmptyArray<VatConfig> = [
{ sourceSpec: 'bogus1.js' },
{ sourceSpec: 'bogus2.js' },
];

beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(console, 'time').mockImplementation(() => undefined);
vi.spyOn(console, 'timeEnd').mockImplementation(() => undefined);
});

it('should execute the complete vat lifecycle', async () => {
it.todo('should execute the complete vat lifecycle', async () => {
const consoleSpy = vi.spyOn(console, 'log');
// Make Math.random return 0 for predictable vat selection
vi.spyOn(Math, 'random').mockReturnValue(0);
Expand Down Expand Up @@ -64,7 +67,7 @@ describe('runVatLifecycle', () => {
expect(consoleSpy).toHaveBeenCalledWith('Kernel has 2 vats');
});

it('should handle errors during vat lifecycle', async () => {
it.todo('should handle errors during vat lifecycle', async () => {
// Mock an error during vat launch
vi.mocked(mockKernel.launchVat).mockRejectedValue(
new Error('Launch failed'),
Expand Down
Loading