From 1408e1a908c9baed8506d9f15197dddf5d3aa69e Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Fri, 16 Aug 2024 14:12:24 +0100 Subject: [PATCH] feat(extension): Add distributed object capability programming --- packages/extension/package.json | 4 + packages/extension/src/background.ts | 36 +++--- packages/extension/src/iframe-manager.ts | 120 +++++++++++++++---- packages/extension/src/iframe.ts | 84 +++++++++++--- packages/extension/src/offscreen.ts | 19 +++- packages/extension/src/shared.ts | 45 ++++++++ packages/shims/package.json | 2 +- yarn.lock | 139 ++++++++++++++++++++++- 8 files changed, 393 insertions(+), 56 deletions(-) diff --git a/packages/extension/package.json b/packages/extension/package.json index 3fcdf03ac..2a0832770 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -26,7 +26,11 @@ "test:watch": "vitest --config vitest.config.ts" }, "dependencies": { + "@endo/captp": "^4.2.2", + "@endo/eventual-send": "^1.2.2", + "@endo/exo": "^1.5.2", "@endo/lockdown": "^1.0.9", + "@endo/patterns": "^1.4.2", "@endo/promise-kit": "^1.1.4", "@metamask/snaps-utils": "^7.8.0", "@metamask/utils": "^9.1.0", diff --git a/packages/extension/src/background.ts b/packages/extension/src/background.ts index b899e06a7..59b6a4ae0 100644 --- a/packages/extension/src/background.ts +++ b/packages/extension/src/background.ts @@ -1,4 +1,5 @@ /* eslint-disable import-x/no-unassigned-import */ +import type { Json } from '@metamask/utils'; import './dev-console.js'; import './endoify.mjs'; /* eslint-enable import-x/no-unassigned-import */ @@ -8,8 +9,12 @@ import { Command, makeHandledCallback } from './shared.js'; // globalThis.kernel will exist due to dev-console.js Object.defineProperties(globalThis.kernel, { - sendMessage: { - value: sendMessage, + capTpCall: { + value: async (method: string, params: Json[]) => + sendMessage(Command.CapTpCall, { method, params }), + }, + capTpInit: { + value: async () => sendMessage(Command.CapTpInit), }, evaluate: { value: async (source: string) => sendMessage(Command.Evaluate, source), @@ -17,6 +22,9 @@ Object.defineProperties(globalThis.kernel, { ping: { value: async () => sendMessage(Command.Ping), }, + sendMessage: { + value: sendMessage, + }, }); const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html'; @@ -33,7 +41,7 @@ chrome.action.onClicked.addListener(() => { * @param data - The message data. * @param data.name - The name to include in the message. */ -async function sendMessage(type: string, data?: string): Promise { +async function sendMessage(type: string, data?: Json): Promise { await provideOffScreenDocument(); await chrome.runtime.sendMessage({ @@ -68,9 +76,10 @@ chrome.runtime.onMessage.addListener( switch (message.type) { case Command.Evaluate: + case Command.CapTpCall: + case Command.CapTpInit: case Command.Ping: console.log(message.data); - await closeOffscreenDocument(); break; default: console.error( @@ -81,12 +90,13 @@ chrome.runtime.onMessage.addListener( }), ); -/** - * Close the offscreen document if it exists. - */ -async function closeOffscreenDocument(): Promise { - if (!(await chrome.offscreen.hasDocument())) { - return; - } - await chrome.offscreen.closeDocument(); -} +// TODO: Add method to close offscreen document? +// /** +// * Close the offscreen document if it exists. +// */ +// async function closeOffscreenDocument(): Promise { +// if (!(await chrome.offscreen.hasDocument())) { +// return; +// } +// await chrome.offscreen.closeDocument(); +// } diff --git a/packages/extension/src/iframe-manager.ts b/packages/extension/src/iframe-manager.ts index 5e5c1866d..025ef1101 100644 --- a/packages/extension/src/iframe-manager.ts +++ b/packages/extension/src/iframe-manager.ts @@ -1,14 +1,17 @@ +import { makeCapTP } from '@endo/captp'; +import { E } from '@endo/eventual-send'; import type { PromiseKit } from '@endo/promise-kit'; import { makePromiseKit } from '@endo/promise-kit'; import { createWindow } from '@metamask/snaps-utils'; +import type { Json } from '@metamask/utils'; import type { MessagePortReader, MessagePortStreamPair } from '@ocap/streams'; import { initializeMessageChannel, makeMessagePortStreamPair, } from '@ocap/streams'; -import type { IframeMessage, WrappedIframeMessage } from './shared.js'; -import { Command, isWrappedIframeMessage } from './shared.js'; +import type { IframeMessage, StreamPayloadEnvelope } from './shared.js'; +import { isStreamPayloadEnvelope, Command } from './shared.js'; const IFRAME_URI = 'iframe.html'; @@ -24,6 +27,11 @@ type PromiseCallbacks = Omit, 'promise'>; type GetPort = (targetWindow: Window) => Promise; +type VatRecord = { + streams: MessagePortStreamPair; + capTp?: ReturnType; +}; + /** * A singleton class to manage and message iframes. */ @@ -32,7 +40,7 @@ export class IframeManager { readonly #unresolvedMessages: Map; - readonly #vats: Map>; + readonly #vats: Map; /** * Create a new IframeManager. @@ -59,10 +67,10 @@ export class IframeManager { const newWindow = await createWindow(IFRAME_URI, getHtmlId(id)); const port = await getPort(newWindow); - const streams = makeMessagePortStreamPair(port); - this.#vats.set(id, streams); + const streams = makeMessagePortStreamPair(port); + this.#vats.set(id, { streams }); /* v8 ignore next 4: Not known to be possible. */ - this.#receiveMessages(streams.reader).catch((error) => { + this.#receiveMessages(id, streams.reader).catch((error) => { console.error(`Unexpected read error from vat "${id}"`, error); this.delete(id).catch(() => undefined); }); @@ -79,12 +87,12 @@ export class IframeManager { * @returns A promise that resolves when the iframe is deleted. */ async delete(id: string): Promise { - const streams = this.#vats.get(id); - if (streams === undefined) { + const vat = this.#vats.get(id); + if (vat === undefined) { return undefined; } - const closeP = streams.return(); + const closeP = vat.streams.return(); // TODO: Handle orphaned messages this.#vats.delete(id); @@ -110,25 +118,58 @@ export class IframeManager { id: string, message: IframeMessage, ): Promise { - const streams = this.#vats.get(id); - if (streams === undefined) { - throw new Error(`No vat with id "${id}"`); - } - + const vat = this.#expectGetVat(id); const { promise, reject, resolve } = makePromiseKit(); const messageId = this.#nextId(); + this.#unresolvedMessages.set(messageId, { reject, resolve }); - await streams.writer.next({ id: messageId, message }); + await vat.streams.writer.next({ + label: 'message', + payload: { id: messageId, message }, + }); return promise; } + async callCapTp( + id: string, + method: string, + ...params: Json[] + ): Promise { + const { capTp } = this.#expectGetVat(id); + if (capTp === undefined) { + throw new Error(`Vat with id "${id}" does not have a CapTP connection.`); + } + // @ts-expect-error The types are unwell. + return E(capTp.getBootstrap())[method](...params); + } + + async makeCapTp(id: string): Promise { + const vat = this.#expectGetVat(id); + if (vat.capTp !== undefined) { + throw new Error(`Vat with id "${id}" already has a CapTP connection.`); + } + + // Handle writes here. #receiveMessages() handles reads. + const { writer } = vat.streams; + // https://github.com/endojs/endo/issues/2412 + // eslint-disable-next-line @typescript-eslint/no-misused-promises + const ctp = makeCapTP(id, async (payload: unknown) => { + console.log('CapTP to vat', JSON.stringify(payload, null, 2)); + await writer.next({ label: 'capTp', payload }); + }); + + vat.capTp = ctp; + await this.sendMessage(id, { type: Command.CapTpInit, data: null }); + } + async #receiveMessages( - reader: MessagePortReader, + vatId: string, + reader: MessagePortReader, ): Promise { for await (const rawMessage of reader) { console.debug('Offscreen received message', rawMessage); - if (!isWrappedIframeMessage(rawMessage)) { + if (!isStreamPayloadEnvelope(rawMessage)) { console.warn( 'Offscreen received message with unexpected format', rawMessage, @@ -136,15 +177,48 @@ export class IframeManager { return; } - const { id, message } = rawMessage; - const promiseCallbacks = this.#unresolvedMessages.get(id); - if (promiseCallbacks === undefined) { - console.error(`No unresolved message with id "${id}".`); - continue; + switch (rawMessage.label) { + case 'capTp': { + console.log( + 'CapTP from vat', + JSON.stringify(rawMessage.payload, null, 2), + ); + const { capTp } = this.#expectGetVat(vatId); + if (capTp !== undefined) { + capTp.dispatch(rawMessage.payload); + } + break; + } + case 'message': { + const { id, message } = rawMessage.payload; + const promiseCallbacks = this.#unresolvedMessages.get(id); + if (promiseCallbacks === undefined) { + console.error(`No unresolved message with id "${id}".`); + } else { + promiseCallbacks.resolve(message.data); + } + break; + } + /* v8 ignore next 3: Exhaustiveness check */ + default: + // @ts-expect-error Exhaustiveness check + throw new Error(`Unexpected message label "${rawMessage.label}".`); } + } + } - promiseCallbacks.resolve(message.data); + /** + * Get a vat record by id, or throw an error if it doesn't exist. + * + * @param id - The id of the vat to get. + * @returns The vat record. + */ + #expectGetVat(id: string): VatRecord { + const vat = this.#vats.get(id); + if (vat === undefined) { + throw new Error(`No vat with id "${id}"`); } + return vat; } #nextId(): string { diff --git a/packages/extension/src/iframe.ts b/packages/extension/src/iframe.ts index e5940f27c..36472c0ff 100644 --- a/packages/extension/src/iframe.ts +++ b/packages/extension/src/iframe.ts @@ -1,7 +1,10 @@ +import { makeCapTP } from '@endo/captp'; +import { makeExo } from '@endo/exo'; +import { M } from '@endo/patterns'; import { receiveMessagePort, makeMessagePortStreamPair } from '@ocap/streams'; -import type { WrappedIframeMessage } from './shared.js'; -import { Command, isWrappedIframeMessage } from './shared.js'; +import type { StreamPayloadEnvelope, WrappedIframeMessage } from './shared.js'; +import { isStreamPayloadEnvelope, Command } from './shared.js'; const defaultCompartment = new Compartment({ URL }); @@ -12,21 +15,50 @@ main().catch(console.error); */ async function main(): Promise { const port = await receiveMessagePort(); - const streams = makeMessagePortStreamPair(port); + const streams = makeMessagePortStreamPair(port); + let capTp: ReturnType | undefined; - for await (const wrappedMessage of streams.reader) { - console.debug('iframe received message', wrappedMessage); + for await (const rawMessage of streams.reader) { + console.debug('iframe received message', rawMessage); - if (!isWrappedIframeMessage(wrappedMessage)) { + if (!isStreamPayloadEnvelope(rawMessage)) { console.error( 'iframe received message with unexpected format', - wrappedMessage, + rawMessage, ); return; } - const { id, message } = wrappedMessage; + switch (rawMessage.label) { + case 'capTp': + if (capTp !== undefined) { + capTp.dispatch(rawMessage.payload); + } + break; + case 'message': + await handleMessage(rawMessage.payload); + break; + /* v8 ignore next 3: Exhaustiveness check */ + default: + // @ts-expect-error Exhaustiveness check + throw new Error(`Unexpected message label "${rawMessage.label}".`); + } + } + await streams.return(); + throw new Error('MessagePortReader ended unexpectedly.'); + + /** + * Handle a message from the parent window. + * + * @param wrappedMessage - The wrapped message to handle. + * @param wrappedMessage.id - The id of the message. + * @param wrappedMessage.message - The message to handle. + */ + async function handleMessage({ + id, + message, + }: WrappedIframeMessage): Promise { switch (message.type) { case Command.Evaluate: { if (typeof message.data !== 'string') { @@ -37,11 +69,29 @@ async function main(): Promise { return; } const result = safelyEvaluate(message.data); - await reply(id, Command.Evaluate, stringifyResult(result)); + await replyToMessage(id, Command.Evaluate, stringifyResult(result)); + break; + } + case Command.CapTpInit: { + const bootstrap = makeExo( + 'TheGreatFrangooly', + M.interface('TheGreatFrangooly', {}, { defaultGuards: 'passable' }), + { whatIsTheGreatFrangooly: () => 'Crowned with Chaos' }, + ); + + capTp = makeCapTP( + 'iframe', // TODO + // https://github.com/endojs/endo/issues/2412 + // eslint-disable-next-line @typescript-eslint/no-misused-promises + async (payload: unknown) => + streams.writer.next({ label: 'capTp', payload }), + bootstrap, + ); + await replyToMessage(id, Command.CapTpInit); break; } case Command.Ping: - await reply(id, Command.Ping, 'pong'); + await replyToMessage(id, Command.Ping, 'pong'); break; default: console.error( @@ -51,22 +101,22 @@ async function main(): Promise { } } - await streams.return(); - throw new Error('MessagePortReader ended unexpectedly.'); - /** - * Reply to the parent window. + * Reply to a message from the parent window. * * @param id - The id of the message to reply to. * @param messageType - The message type. * @param data - The message data. */ - async function reply( + async function replyToMessage( id: string, messageType: Command, - data: string, + data: string | null = null, ): Promise { - await streams.writer.next({ id, message: { type: messageType, data } }); + await streams.writer.next({ + label: 'message', + payload: { id, message: { type: messageType, data } }, + }); } /** diff --git a/packages/extension/src/offscreen.ts b/packages/extension/src/offscreen.ts index 5c48b8d13..9e98f25ae 100644 --- a/packages/extension/src/offscreen.ts +++ b/packages/extension/src/offscreen.ts @@ -11,7 +11,9 @@ async function main(): Promise { // Hard-code a single iframe for now. const IFRAME_ID = 'default'; const iframeManager = new IframeManager(); - const iframeReadyP = iframeManager.create({ id: IFRAME_ID }); + const iframeReadyP = iframeManager + .create({ id: IFRAME_ID }) + .then(async () => iframeManager.makeCapTp(IFRAME_ID)); // Handle messages from the background service worker chrome.runtime.onMessage.addListener( @@ -29,6 +31,21 @@ async function main(): Promise { case Command.Evaluate: await reply(Command.Evaluate, await evaluate(message.data)); break; + case Command.CapTpCall: { + const result = await iframeManager.callCapTp( + IFRAME_ID, + // @ts-expect-error TODO: Type assertions + message.data.method, + // @ts-expect-error TODO: Type assertions + ...message.data.params, + ); + await reply(Command.CapTpCall, JSON.stringify(result, null, 2)); + break; + } + case Command.CapTpInit: + await iframeManager.makeCapTp(IFRAME_ID); + await reply(Command.CapTpInit, '~~~ CapTP Initialized ~~~'); + break; case Command.Ping: await reply(Command.Ping, 'pong'); break; diff --git a/packages/extension/src/shared.ts b/packages/extension/src/shared.ts index 99c82f6b9..dda929a74 100644 --- a/packages/extension/src/shared.ts +++ b/packages/extension/src/shared.ts @@ -1,6 +1,8 @@ import { isObject } from '@metamask/utils'; export enum Command { + CapTpCall = 'callCapTp', + CapTpInit = 'makeCapTp', Evaluate = 'evaluate', Ping = 'ping', } @@ -36,6 +38,49 @@ export const isWrappedIframeMessage = ( typeof value.message.type === 'string' && (typeof value.message.data === 'string' || value.message.data === null); +export type StreamPayloadEnvelope = + | { + label: 'message'; + payload: WrappedIframeMessage; + } + | { label: 'capTp'; payload: unknown }; + +type MessageHandler = (message: WrappedIframeMessage) => void | Promise; +type CapTpHandler = (capTpMessage: unknown) => void | Promise; +export const makeEnvelopeUnwrapper = + (handleMessage: MessageHandler, handleCapTp: CapTpHandler) => + async (envelope: StreamPayloadEnvelope): Promise => { + switch (envelope.label) { + case 'capTp': + return handleCapTp(envelope.payload); + case 'message': + return handleMessage(envelope.payload); + default: + throw new Error( + `Unexpected message label in message:\n${JSON.stringify( + envelope, + null, + 2, + )}`, + ); + } + }; + +export const isStreamPayloadEnvelope = ( + value: unknown, +): value is StreamPayloadEnvelope => { + if (!isObject(value)) { + return false; + } + if ( + value.label !== 'capTp' && + (value.label !== 'message' || !isWrappedIframeMessage(value.payload)) + ) { + return false; + } + return true; +}; + /** * Wrap an async callback to ensure any errors are at least logged. * diff --git a/packages/shims/package.json b/packages/shims/package.json index a257b5b88..38225d981 100644 --- a/packages/shims/package.json +++ b/packages/shims/package.json @@ -33,7 +33,7 @@ "test:watch": "vitest --config vitest.config.ts" }, "dependencies": { - "@endo/eventual-send": "^1.2.4", + "@endo/eventual-send": "^1.2.2", "@endo/lockdown": "^1.0.9", "ses": "^1.7.0" }, diff --git a/yarn.lock b/yarn.lock index d5df286f8..50b906af6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -296,6 +296,19 @@ __metadata: languageName: node linkType: hard +"@endo/captp@npm:^4.2.2": + version: 4.2.2 + resolution: "@endo/captp@npm:4.2.2" + dependencies: + "@endo/errors": "npm:^1.2.4" + "@endo/eventual-send": "npm:^1.2.4" + "@endo/marshal": "npm:^1.5.2" + "@endo/nat": "npm:^5.0.9" + "@endo/promise-kit": "npm:^1.1.4" + checksum: 10/01ec9bc8c54aed3cdd04cd38249f96f6a426298cdeb696911ce81026445e3c2eb9b02fc54d2999dda52fe8bb99853a18ac8205f4ac2690a7acd2d14d1efb8d8b + languageName: node + linkType: hard + "@endo/cjs-module-analyzer@npm:^1.0.6": version: 1.0.6 resolution: "@endo/cjs-module-analyzer@npm:1.0.6" @@ -303,6 +316,17 @@ __metadata: languageName: node linkType: hard +"@endo/common@npm:^1.2.4": + version: 1.2.4 + resolution: "@endo/common@npm:1.2.4" + dependencies: + "@endo/errors": "npm:^1.2.4" + "@endo/eventual-send": "npm:^1.2.4" + "@endo/promise-kit": "npm:^1.1.4" + checksum: 10/d636b08a2124fc45a71747e684199bcf02be7a0571df29abde5e8b01ba8bb37139c8047185e66718375b2831d8ce3fd71734d3e6ff579f1b6bdcc8e4e7f8cfb0 + languageName: node + linkType: hard + "@endo/compartment-mapper@npm:^1.2.0": version: 1.2.1 resolution: "@endo/compartment-mapper@npm:1.2.1" @@ -322,6 +346,15 @@ __metadata: languageName: node linkType: hard +"@endo/errors@npm:^1.2.4": + version: 1.2.4 + resolution: "@endo/errors@npm:1.2.4" + dependencies: + ses: "npm:^1.7.0" + checksum: 10/2609aad411c9ed5abc989421e0e7e4eeb3f8b9f71c603efc6f7c067673235b757fde998434981a8f421aca245ac570e86ebdaade7407c976008b2782dc61aa6e + languageName: node + linkType: hard + "@endo/evasive-transform@npm:^1.2.0": version: 1.2.1 resolution: "@endo/evasive-transform@npm:1.2.1" @@ -343,6 +376,32 @@ __metadata: languageName: node linkType: hard +"@endo/exo@npm:^1.5.2": + version: 1.5.2 + resolution: "@endo/exo@npm:1.5.2" + dependencies: + "@endo/common": "npm:^1.2.4" + "@endo/env-options": "npm:^1.1.5" + "@endo/errors": "npm:^1.2.4" + "@endo/eventual-send": "npm:^1.2.4" + "@endo/far": "npm:^1.1.4" + "@endo/pass-style": "npm:^1.4.2" + "@endo/patterns": "npm:^1.4.2" + checksum: 10/610d653789c1daf9763b83d2debafc0e8257af3d7efe87d1677ee9ea93feefbbc745886b5f5f1fc2da0d77d527904f94fa9ad63b26b903b54d2bb5084fe8a7b3 + languageName: node + linkType: hard + +"@endo/far@npm:^1.1.4": + version: 1.1.4 + resolution: "@endo/far@npm:1.1.4" + dependencies: + "@endo/errors": "npm:^1.2.4" + "@endo/eventual-send": "npm:^1.2.4" + "@endo/pass-style": "npm:^1.4.2" + checksum: 10/71d34d80cc966da446b5c2a0f6d0c2b0248409238a52234c4e3757166c8a24c80e67d6118ad0e120cff07ab853c696e6466fdec11a381a1def8b447559f47d94 + languageName: node + linkType: hard + "@endo/init@npm:^1.1.3": version: 1.1.3 resolution: "@endo/init@npm:1.1.3" @@ -364,6 +423,20 @@ __metadata: languageName: node linkType: hard +"@endo/marshal@npm:^1.5.2": + version: 1.5.2 + resolution: "@endo/marshal@npm:1.5.2" + dependencies: + "@endo/common": "npm:^1.2.4" + "@endo/errors": "npm:^1.2.4" + "@endo/eventual-send": "npm:^1.2.4" + "@endo/nat": "npm:^5.0.9" + "@endo/pass-style": "npm:^1.4.2" + "@endo/promise-kit": "npm:^1.1.4" + checksum: 10/eb708e7ecffcbf7bb3830302a43e8a601388fa7b89ad877fa5e2098946f0b1e56da10f0fa69d7fce4e9970c4cf6183c3697fc298370c5f05b80a4868e18ad812 + languageName: node + linkType: hard + "@endo/module-source@npm:^1.0.1": version: 1.0.1 resolution: "@endo/module-source@npm:1.0.1" @@ -377,6 +450,39 @@ __metadata: languageName: node linkType: hard +"@endo/nat@npm:^5.0.9": + version: 5.0.9 + resolution: "@endo/nat@npm:5.0.9" + checksum: 10/93b8185d0336844c3e75513d63cac10b7a630ae963adc19c932572b27d824d756e454a845bd2b3e56bbf1e9e8bc3197ca7c08186b2dd3154ab74f834c5a50a9e + languageName: node + linkType: hard + +"@endo/pass-style@npm:^1.4.2": + version: 1.4.2 + resolution: "@endo/pass-style@npm:1.4.2" + dependencies: + "@endo/env-options": "npm:^1.1.5" + "@endo/errors": "npm:^1.2.4" + "@endo/eventual-send": "npm:^1.2.4" + "@endo/promise-kit": "npm:^1.1.4" + "@fast-check/ava": "npm:^1.1.5" + checksum: 10/a67f745428770969a6ed048a44098011a4e1df00272e1f75b8aeb25bf185c796de364bb9e335d41fb208461e25eb6fa9aa7b16a05b339c61e5854ef97e0accbb + languageName: node + linkType: hard + +"@endo/patterns@npm:^1.4.2": + version: 1.4.2 + resolution: "@endo/patterns@npm:1.4.2" + dependencies: + "@endo/common": "npm:^1.2.4" + "@endo/errors": "npm:^1.2.4" + "@endo/eventual-send": "npm:^1.2.4" + "@endo/marshal": "npm:^1.5.2" + "@endo/promise-kit": "npm:^1.1.4" + checksum: 10/b942bb229439aa77550a6d78ccde01d24421056b0a147268cf46345404e8271e61f841888b00af0d9b0f414df9f992766f92c0cf5addab96842590a46df1ec50 + languageName: node + linkType: hard + "@endo/promise-kit@npm:^1.1.2, @endo/promise-kit@npm:^1.1.3, @endo/promise-kit@npm:^1.1.4": version: 1.1.4 resolution: "@endo/promise-kit@npm:1.1.4" @@ -667,6 +773,17 @@ __metadata: languageName: node linkType: hard +"@fast-check/ava@npm:^1.1.5": + version: 1.2.1 + resolution: "@fast-check/ava@npm:1.2.1" + dependencies: + fast-check: "npm:^3.0.0" + peerDependencies: + ava: ^4 || ^5 || ^6 + checksum: 10/816ac43e5fb0c2a101bc7e2307f67b68ede4dd64d029cecc78795113580c90f4120ccc3ea6931ad757c465dff06a79c24f45cbe399b8ab6d74494d72f9f19736 + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.11.14": version: 0.11.14 resolution: "@humanwhocodes/config-array@npm:0.11.14" @@ -1251,7 +1368,11 @@ __metadata: resolution: "@ocap/extension@workspace:packages/extension" dependencies: "@arethetypeswrong/cli": "npm:^0.15.3" + "@endo/captp": "npm:^4.2.2" + "@endo/eventual-send": "npm:^1.2.2" + "@endo/exo": "npm:^1.5.2" "@endo/lockdown": "npm:^1.0.9" + "@endo/patterns": "npm:^1.4.2" "@endo/promise-kit": "npm:^1.1.4" "@metamask/auto-changelog": "npm:^3.4.4" "@metamask/snaps-utils": "npm:^7.8.0" @@ -1318,7 +1439,7 @@ __metadata: resolution: "@ocap/shims@workspace:packages/shims" dependencies: "@endo/bundle-source": "npm:^3.3.0" - "@endo/eventual-send": "npm:^1.2.4" + "@endo/eventual-send": "npm:^1.2.2" "@endo/lockdown": "npm:^1.0.9" "@metamask/auto-changelog": "npm:^3.4.4" deepmerge: "npm:^4.3.1" @@ -3598,6 +3719,15 @@ __metadata: languageName: node linkType: hard +"fast-check@npm:^3.0.0": + version: 3.21.0 + resolution: "fast-check@npm:3.21.0" + dependencies: + pure-rand: "npm:^6.1.0" + checksum: 10/64e221858d5d98c6ea10c81e6a1a66760565bca41883466891974197a5439c7f0fe1dc293b8d887eaf959d0ff85197cc9d76813ae6215b018cde313254db7d53 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -5584,6 +5714,13 @@ __metadata: languageName: node linkType: hard +"pure-rand@npm:^6.1.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10/256aa4bcaf9297256f552914e03cbdb0039c8fe1db11fa1e6d3f80790e16e563eb0a859a1e61082a95e224fc0c608661839439f8ecc6a3db4e48d46d99216ee4 + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0"