diff --git a/.depcheckrc.yml b/.depcheckrc.yml index 115a7ce4d..d33658e87 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -11,5 +11,6 @@ ignores: - 'jsdom' - 'prettier-plugin-*' - 'typedoc' + - 'vite-tsconfig-paths' - 'vite' - 'vitest' diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 88% rename from .eslintrc.js rename to .eslintrc.cjs index 31d833a3a..597f96f9a 100644 --- a/.eslintrc.js +++ b/.eslintrc.cjs @@ -12,7 +12,7 @@ module.exports = { }, ignorePatterns: [ - '!.eslintrc.js', + '!.eslintrc.cjs', '!vite.config.mts', '!vitest.config.mts', 'node_modules', @@ -27,6 +27,14 @@ module.exports = { // in `@metamask/eslint-config-nodejs` in the future. 'import/no-nodejs-modules': 'off', + 'import/no-useless-path-segments': [ + 'error', + { + // Enabling this causes false errors in ESM files. + noUselessIndex: false, + }, + ], + // This prevents using Node.js and/or browser specific globals. We // currently use both in our codebase, so this rule is disabled. 'no-restricted-globals': 'off', @@ -34,7 +42,7 @@ module.exports = { overrides: [ { - files: ['*.js', '*.cjs'], + files: ['*.cjs'], parserOptions: { sourceType: 'script', ecmaVersion: '2020', @@ -42,7 +50,7 @@ module.exports = { }, { - files: ['*.mjs'], + files: ['*.js', '*.mjs'], parserOptions: { sourceType: 'module', ecmaVersion: '2020', @@ -70,8 +78,8 @@ module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'error', - // This rule is broken, and without the `allowAny` option, it causes - // a lot of false positives. + // This rule is broken, and without the `allowAny` option, it reports a lot + // of false errors. '@typescript-eslint/restrict-template-expressions': [ 'error', { @@ -98,6 +106,15 @@ module.exports = { }, }, + { + // Overrides of overrides. + files: ['*'], + rules: { + // This prevents pretty formatting of comments with multi-line lists entries. + 'jsdoc/check-indentation': 'off', + }, + }, + { // @metamask/eslint-plugin-vitest does not exist, so this is copied from the // jest-equivalent. All of the rules we specify are the same. Ref: diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs diff --git a/constraints.pro b/constraints.pro index be0347393..7ac5e7b93 100644 --- a/constraints.pro +++ b/constraints.pro @@ -209,9 +209,11 @@ gen_enforced_field(WorkspaceCwd, 'module', './dist/index.mjs') :- % Non-published packages must not specify an entrypoint. gen_enforced_field(WorkspaceCwd, 'main', null) :- WorkspaceCwd \= 'packages/shims', + WorkspaceCwd \= 'packages/streams', workspace_field(WorkspaceCwd, 'private', true). gen_enforced_field(WorkspaceCwd, 'module', null) :- WorkspaceCwd \= 'packages/shims', + WorkspaceCwd \= 'packages/streams', workspace_field(WorkspaceCwd, 'private', true). % The type definitions entrypoint for all publishable packages must be the same. @@ -220,6 +222,7 @@ gen_enforced_field(WorkspaceCwd, 'types', './dist/index.d.cts') :- % Non-published packages must not specify a type definitions entrypoint. gen_enforced_field(WorkspaceCwd, 'types', null) :- WorkspaceCwd \= 'packages/shims', + WorkspaceCwd \= 'packages/streams', workspace_field(WorkspaceCwd, 'private', true). % The exports for all published packages must be the same. @@ -235,11 +238,11 @@ gen_enforced_field(WorkspaceCwd, 'exports["."].import.types', './dist/index.d.mt \+ workspace_field(WorkspaceCwd, 'private', true). % package.json gen_enforced_field(WorkspaceCwd, 'exports["./package.json"]', './package.json') :- - \+ workspace_field(WorkspaceCwd, 'private', true). -% Non-published packages must not specify exports. + WorkspaceCwd \= 'packages/extension', + WorkspaceCwd \= '.'. +% The root package must not specify exports. gen_enforced_field(WorkspaceCwd, 'exports', null) :- - WorkspaceCwd \= 'packages/shims', - workspace_field(WorkspaceCwd, 'private', true). + WorkspaceCwd = '.'. % Published packages must not have side effects. gen_enforced_field(WorkspaceCwd, 'sideEffects', false) :- @@ -247,6 +250,7 @@ gen_enforced_field(WorkspaceCwd, 'sideEffects', false) :- % Non-published packages must not specify side effects. gen_enforced_field(WorkspaceCwd, 'sideEffects', null) :- WorkspaceCwd \= 'packages/shims', + WorkspaceCwd \= 'packages/streams', workspace_field(WorkspaceCwd, 'private', true). % The list of files included in published packages must only include files @@ -265,9 +269,14 @@ gen_enforced_field(WorkspaceCwd, 'files', []) :- % gen_enforced_field(WorkspaceCwd, 'scripts.build', '') :- % WorkspaceCwd \= '.'. -% All packages except the root and extension must have the same "build:docs" script. +% All packages except the root and other exceptions must have the same "build:docs" script. gen_enforced_field(WorkspaceCwd, 'scripts.build:docs', 'typedoc') :- WorkspaceCwd \= 'packages/extension', + WorkspaceCwd \= 'packages/test-utils', + WorkspaceCwd \= '.'. + +% All packages except the root must have the same "clean" script. +gen_enforced_field(WorkspaceCwd, 'scripts.clean', 'rimraf ./dist \'./*.tsbuildinfo\'') :- WorkspaceCwd \= '.'. % All published packages must have the same "publish:preview" script. @@ -301,53 +310,56 @@ gen_enforced_field(WorkspaceCwd, 'scripts.changelog:update', CorrectChangelogUpd % All non-root packages must have the same "test" script. gen_enforced_field(WorkspaceCwd, 'scripts.test', 'vitest run --config vitest.config.mts') :- WorkspaceCwd \= 'packages/shims', + WorkspaceCwd \= 'packages/test-utils', WorkspaceCwd \= '.'. % All non-root packages must have the same "test:clean" script. gen_enforced_field(WorkspaceCwd, 'scripts.test:clean', 'yarn test --no-cache --coverage.clean') :- + WorkspaceCwd \= 'packages/test-utils', WorkspaceCwd \= '.'. % All non-root packages must have the same "test:dev" script. gen_enforced_field(WorkspaceCwd, 'scripts.test:dev', 'yarn test --coverage false') :- + WorkspaceCwd \= 'packages/test-utils', WorkspaceCwd \= '.'. % All non-root packages must have the same "test:verbose" script. gen_enforced_field(WorkspaceCwd, 'scripts.test:verbose', 'yarn test --reporter verbose') :- + WorkspaceCwd \= 'packages/test-utils', WorkspaceCwd \= '.'. % All non-root packages must have the same "test:watch" script. gen_enforced_field(WorkspaceCwd, 'scripts.test:watch', 'vitest --config vitest.config.mts') :- + WorkspaceCwd \= 'packages/test-utils', WorkspaceCwd \= '.'. % All dependency ranges must be recognizable (this makes it possible to apply % the next two rules effectively). gen_enforced_dependency(WorkspaceCwd, DependencyIdent, 'a range optionally starting with ^ or ~', DependencyType) :- workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType), - \+ is_valid_version_range(DependencyRange). - -% All version ranges used to reference one workspace package in another -% workspace package's `dependencies` or `devDependencies` must be the same. -% Among all references to the same dependency across the monorepo, the one with -% the smallest version range will win. (We handle `peerDependencies` in another -% constraint, as it has slightly different logic.) + \+ ( + DependencyRange = '^1.0.0-rc.12'; % is_valid_version_range does not handle rc suffixes + is_valid_version_range(DependencyRange) + ). + +% All dependency ranges for a package must be synchronized across the monorepo +% (the least version range wins), regardless of which "*dependencies" field +% where the package appears. gen_enforced_dependency(WorkspaceCwd, DependencyIdent, OtherDependencyRange, DependencyType) :- workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType), workspace_has_dependency(OtherWorkspaceCwd, DependencyIdent, OtherDependencyRange, OtherDependencyType), WorkspaceCwd \= OtherWorkspaceCwd, DependencyRange \= OtherDependencyRange, - npm_version_range_out_of_sync(DependencyRange, OtherDependencyRange), - DependencyType \= 'peerDependencies', - OtherDependencyType \= 'peerDependencies'. - -% All version ranges used to reference one workspace package in another -% workspace package's `dependencies` or `devDependencies` must match the current -% version of that package. (We handle `peerDependencies` in another rule.) -gen_enforced_dependency(WorkspaceCwd, DependencyIdent, CorrectDependencyRange, DependencyType) :- - DependencyType \= 'peerDependencies', + npm_version_range_out_of_sync(DependencyRange, OtherDependencyRange). + +% If a dependency is listed under "dependencies", it should not be listed under +% "devDependencies". We match on the same dependency range so that if a +% dependency is listed under both lists, their versions are synchronized and +% then this constraint will apply and remove the "right" duplicate. +gen_enforced_dependency(WorkspaceCwd, DependencyIdent, null, DependencyType) :- + workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, 'dependencies'), workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType), - workspace_ident(OtherWorkspaceCwd, DependencyIdent), - workspace_version(OtherWorkspaceCwd, OtherWorkspaceVersion), - atomic_list_concat(['^', OtherWorkspaceVersion], CorrectDependencyRange). + DependencyType == 'devDependencies'. % If a workspace package is listed under another workspace package's % `dependencies`, it should not also be listed under its `devDependencies`. diff --git a/package.json b/package.json index 978c610e4..2f7a4fa67 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "type": "git", "url": "https://github.com/MetaMask/ocap-kernel.git" }, + "type": "module", "files": [], "workspaces": [ "packages/*" @@ -19,13 +20,13 @@ "build:watch": "yarn run build --watch", "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 dist '**/*.tsbuildinfo'", + "clean": "rimraf '**/*.tsbuildinfo' && yarn workspaces foreach --all --parallel --interlaced --verbose run clean", "lint": "yarn lint:eslint && yarn lint:misc --check && yarn constraints && yarn lint:dependencies", "lint:dependencies": "depcheck && yarn dedupe --check", "lint:dependencies:fix": "depcheck && yarn dedupe", "lint:eslint": "eslint . --cache --ext js,mjs,cjs,ts,mts,cts", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn constraints --fix && yarn lint:dependencies:fix", - "lint:misc": "prettier '**/*.json' '**/*.md' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path .gitignore", + "lint:misc": "prettier '**/*.json' '**/*.md' '**/*.html' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path .gitignore", "prepack": "./scripts/prepack.sh", "test": "yarn workspaces foreach --all --parallel --verbose run test", "test:clean": "yarn workspaces foreach --all --parallel --verbose run test:clean && yarn test", @@ -39,7 +40,7 @@ "@metamask/eslint-config": "^12.2.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@ts-bridge/cli": "^0.1.4", + "@ts-bridge/cli": "^0.3.0", "@ts-bridge/shims": "^0.1.1", "@types/node": "^18.18.14", "@typescript-eslint/eslint-plugin": "^5.43.0", @@ -60,7 +61,8 @@ "typedoc": "^0.24.8", "typescript": "~4.9.5", "vite": "^5.3.5", - "vitest": "^2.0.4" + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^2.0.5" }, "packageManager": "yarn@4.2.2", "engines": { diff --git a/packages/extension/.eslintrc.js b/packages/extension/.eslintrc.cjs similarity index 94% rename from packages/extension/.eslintrc.js rename to packages/extension/.eslintrc.cjs index 13900fb6d..1ff55a970 100644 --- a/packages/extension/.eslintrc.js +++ b/packages/extension/.eslintrc.cjs @@ -1,5 +1,5 @@ module.exports = { - extends: ['../../.eslintrc.js'], + extends: ['../../.eslintrc.cjs'], overrides: [ { diff --git a/packages/extension/package.json b/packages/extension/package.json index 08af11d5b..b9ce4d54e 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -7,6 +7,7 @@ "type": "git", "url": "https://github.com/MetaMask/ocap-kernel.git" }, + "type": "module", "files": [ "dist/" ], @@ -15,6 +16,7 @@ "build:types": "tsc --project tsconfig.build.json", "build:vite": "vite build --config vite.config.mts", "changelog:validate": "../../scripts/validate-changelog.sh @ocap/extension", + "clean": "rimraf ./dist './*.tsbuildinfo'", "publish:preview": "yarn npm publish --tag preview", "start": "vite --config vite.config.mts & vite build --watch --config vite.config.mts", "test": "vitest run --config vitest.config.mts", @@ -28,22 +30,26 @@ "@endo/promise-kit": "^1.1.2", "@metamask/snaps-utils": "^7.8.0", "@metamask/utils": "^9.1.0", - "@ocap/shims": "^0.0.0", - "ses": "^1.5.0" + "@ocap/shims": "workspace:^", + "@ocap/streams": "workspace:^", + "ses": "^1.7.0" }, "devDependencies": { "@arethetypeswrong/cli": "^0.15.3", "@metamask/auto-changelog": "^3.4.4", + "@ocap/test-utils": "workspace:^", "@types/chrome": "^0.0.268", - "@vitest/coverage-v8": "^2.0.4", + "@vitest/coverage-v8": "^2.0.5", + "cheerio": "^1.0.0-rc.12", "deepmerge": "^4.3.1", "jsdom": "^24.1.1", + "prettier": "^2.7.1", "typedoc": "^0.24.8", "typedoc-plugin-missing-exports": "^2.0.0", "typescript": "~4.9.5", "vite": "^5.3.5", "vite-plugin-static-copy": "^1.0.6", - "vitest": "^2.0.4" + "vitest": "^2.0.5" }, "engines": { "node": "^18.18 || >=20" diff --git a/packages/extension/src/background.ts b/packages/extension/src/background.ts index d04913a8d..f1213e891 100644 --- a/packages/extension/src/background.ts +++ b/packages/extension/src/background.ts @@ -3,8 +3,8 @@ import './dev-console.mjs'; import './endoify.mjs'; /* eslint-enable import/extensions,import/no-unassigned-import */ -import type { ExtensionMessage } from './shared'; -import { Command, makeHandledCallback } from './shared'; +import type { ExtensionMessage } from './shared.js'; +import { Command, makeHandledCallback } from './shared.js'; // globalThis.kernel will exist due to dev-console.mjs Object.defineProperties(globalThis.kernel, { diff --git a/packages/extension/src/iframe-manager.test.ts b/packages/extension/src/iframe-manager.test.ts index a5c621570..2a5c5bf29 100644 --- a/packages/extension/src/iframe-manager.test.ts +++ b/packages/extension/src/iframe-manager.test.ts @@ -1,53 +1,21 @@ import * as snapsUtils from '@metamask/snaps-utils'; -import { vi, beforeEach, describe, it, expect } from 'vitest'; +import { delay, makePromiseKitMock } from '@ocap/test-utils'; +import { vi, describe, it, expect } from 'vitest'; -import { Command } from './shared'; +import { IframeManager } from './iframe-manager.js'; +import { Command } from './shared.js'; -vi.mock('@endo/promise-kit', () => ({ - makePromiseKit: () => { - let resolve: (value: unknown) => void, reject: (reason?: unknown) => void; - const promise = new Promise((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; - }); - // @ts-expect-error We have in fact assigned resolve and reject. - return { promise, resolve, reject }; - }, -})); +vi.mock('@endo/promise-kit', () => makePromiseKitMock()); vi.mock('@metamask/snaps-utils', () => ({ createWindow: vi.fn(), })); describe('IframeManager', () => { - // eslint-disable-next-line @typescript-eslint/consistent-type-imports - let IframeManager: typeof import('./iframe-manager').IframeManager; - - beforeEach(async () => { - vi.resetModules(); - IframeManager = (await import('./iframe-manager')).IframeManager; - }); - - describe('getInstance', () => { - it('is a singleton', () => { - expect(IframeManager.getInstance()).toBe(IframeManager.getInstance()); - }); - - it('sets up event listener on construction', () => { - const addEventListenerSpy = vi.spyOn(window, 'addEventListener'); - let manager = IframeManager.getInstance(); - - expect(manager).toBeInstanceOf(IframeManager); - expect(addEventListenerSpy).toHaveBeenCalledOnce(); - expect(addEventListenerSpy).toHaveBeenCalledWith( - 'message', - expect.any(Function), - ); - - manager = IframeManager.getInstance(); - expect(addEventListenerSpy).toHaveBeenCalledOnce(); - }); - }); + const makeGetPort = + (port: MessagePort = new MessageChannel().port1) => + async (_window: Window) => + Promise.resolve(port); describe('create', () => { it('creates a new iframe', async () => { @@ -55,12 +23,11 @@ describe('IframeManager', () => { vi.mocked(snapsUtils.createWindow).mockResolvedValueOnce( mockWindow as Window, ); - - const manager = IframeManager.getInstance(); + const manager = new IframeManager(); const sendMessageSpy = vi .spyOn(manager, 'sendMessage') .mockImplementation(vi.fn()); - const [newWindow, id] = await manager.create(); + const [newWindow, id] = await manager.create({ getPort: makeGetPort() }); expect(newWindow).toBe(mockWindow); expect(id).toBeTypeOf('string'); @@ -77,12 +44,15 @@ describe('IframeManager', () => { mockWindow as Window, ); - const manager = IframeManager.getInstance(); + const manager = new IframeManager(); const sendMessageSpy = vi .spyOn(manager, 'sendMessage') .mockImplementation(vi.fn()); const id = 'foo'; - const [newWindow, returnedId] = await manager.create(id); + const [newWindow, returnedId] = await manager.create({ + id, + getPort: makeGetPort(), + }); expect(newWindow).toBe(mockWindow); expect(returnedId).toBe(id); @@ -92,6 +62,35 @@ describe('IframeManager', () => { data: null, }); }); + + it('creates a new iframe with the default getPort function', async () => { + vi.resetModules(); + vi.doMock('@ocap/streams', () => ({ + initializeMessageChannel: vi.fn(), + MessagePortReader: class Mock1 {}, + MessagePortWriter: class Mock2 {}, + })); + const IframeManager2 = (await import('./iframe-manager.js')) + .IframeManager; + + const mockWindow = {}; + vi.mocked(snapsUtils.createWindow).mockResolvedValueOnce( + mockWindow as Window, + ); + const manager = new IframeManager2(); + const sendMessageSpy = vi + .spyOn(manager, 'sendMessage') + .mockImplementation(vi.fn()); + const [newWindow, id] = await manager.create(); + + expect(newWindow).toBe(mockWindow); + expect(id).toBeTypeOf('string'); + expect(sendMessageSpy).toHaveBeenCalledOnce(); + expect(sendMessageSpy).toHaveBeenCalledWith(id, { + type: 'ping', + data: null, + }); + }); }); describe('delete', () => { @@ -106,22 +105,22 @@ describe('IframeManager', () => { return iframe.contentWindow as Window; }); - const manager = IframeManager.getInstance(); + const manager = new IframeManager(); vi.spyOn(manager, 'sendMessage').mockImplementation(vi.fn()); - await manager.create(id); - manager.delete(id); + await manager.create({ id, getPort: makeGetPort() }); + await manager.delete(id); expect(removeSpy).toHaveBeenCalledOnce(); }); it('ignores attempt to delete unrecognized iframe', async () => { const id = 'foo'; - const manager = IframeManager.getInstance(); + const manager = new IframeManager(); const iframe = document.createElement('iframe'); const removeSpy = vi.spyOn(iframe, 'remove'); - manager.delete(id); + await manager.delete(id); expect(removeSpy).not.toHaveBeenCalled(); }); @@ -129,72 +128,98 @@ describe('IframeManager', () => { describe('sendMessage', () => { it('sends a message to an iframe', async () => { - const iframeWindow = { postMessage: vi.fn() }; - vi.mocked(snapsUtils.createWindow).mockResolvedValueOnce( - iframeWindow as unknown as Window, - ); + vi.mocked(snapsUtils.createWindow).mockResolvedValueOnce({} as Window); - const manager = IframeManager.getInstance(); + const manager = new IframeManager(); const sendMessageSpy = vi.spyOn(manager, 'sendMessage'); + // Intercept the ping message in create() sendMessageSpy.mockImplementationOnce(async () => Promise.resolve()); + const { port1, port2 } = new MessageChannel(); + const portPostMessageSpy = vi.spyOn(port1, 'postMessage'); const id = 'foo'; - await manager.create(id); + await manager.create({ id, getPort: makeGetPort(port1) }); const message = { type: Command.Evaluate, data: '2+2' }; const messagePromise = manager.sendMessage(id, message); const messageId: string | undefined = - iframeWindow.postMessage.mock.lastCall?.[0]?.id; + portPostMessageSpy.mock.lastCall?.[0]?.id; expect(messageId).toBeTypeOf('string'); - window.dispatchEvent( - new MessageEvent('message', { - data: { - id: messageId, - message: { - type: Command.Evaluate, - data: '4', - }, - }, - }), - ); + port2.postMessage({ + id: messageId, + message: { + type: Command.Evaluate, + data: '4', + }, + }); - expect(iframeWindow.postMessage).toHaveBeenCalledOnce(); - expect(iframeWindow.postMessage).toHaveBeenCalledWith( - { id: messageId, message }, - '*', - ); + expect(portPostMessageSpy).toHaveBeenCalledOnce(); + expect(portPostMessageSpy).toHaveBeenCalledWith({ + id: messageId, + message, + }); expect(await messagePromise).toBe('4'); }); it('throws if iframe not found', async () => { - const manager = IframeManager.getInstance(); + const manager = new IframeManager(); const id = 'foo'; const message = { type: Command.Ping, data: null }; await expect(manager.sendMessage(id, message)).rejects.toThrow( - `No iframe with id "${id}"`, + `No vat with id "${id}"`, ); }); }); - describe('warnings', () => { - it('calls console.warn when receiving unexpected message', () => { - // Initialize manager - IframeManager.getInstance(); + describe('miscellaneous', () => { + it('calls console.warn when receiving unexpected message', async () => { + vi.mocked(snapsUtils.createWindow).mockResolvedValueOnce({} as Window); + + const manager = new IframeManager(); const warnSpy = vi.spyOn(console, 'warn'); + const sendMessageSpy = vi.spyOn(manager, 'sendMessage'); + // Intercept the ping message in create() + sendMessageSpy.mockImplementationOnce(async () => Promise.resolve()); - window.dispatchEvent( - new MessageEvent('message', { - data: 'foo', - }), - ); + const { port1, port2 } = new MessageChannel(); + await manager.create({ getPort: makeGetPort(port1) }); + + port2.postMessage('foo'); + await delay(10); expect(warnSpy).toHaveBeenCalledWith( 'Offscreen received message with unexpected format', 'foo', ); }); + + it('calls console.error when receiving message with unknown id', async () => { + vi.mocked(snapsUtils.createWindow).mockResolvedValueOnce({} as Window); + + const manager = new IframeManager(); + const errorSpy = vi.spyOn(console, 'error'); + const sendMessageSpy = vi.spyOn(manager, 'sendMessage'); + // Intercept the ping message in create() + sendMessageSpy.mockImplementationOnce(async () => Promise.resolve()); + + const { port1, port2 } = new MessageChannel(); + await manager.create({ getPort: makeGetPort(port1) }); + + port2.postMessage({ + id: 'foo', + message: { + type: Command.Evaluate, + data: '"bar"', + }, + }); + await delay(10); + + expect(errorSpy).toHaveBeenCalledWith( + 'No unresolved message with id "foo".', + ); + }); }); }); diff --git a/packages/extension/src/iframe-manager.ts b/packages/extension/src/iframe-manager.ts index 6e7148b2c..5d8c0608e 100644 --- a/packages/extension/src/iframe-manager.ts +++ b/packages/extension/src/iframe-manager.ts @@ -1,96 +1,107 @@ import type { PromiseKit } from '@endo/promise-kit'; import { makePromiseKit } from '@endo/promise-kit'; import { createWindow } from '@metamask/snaps-utils'; - -import type { IframeMessage } from './shared'; -import { Command, isWrappedIframeMessage } from './shared'; +import { + initializeMessageChannel, + MessagePortReader, + MessagePortWriter, +} from '@ocap/streams'; + +import type { + IframeMessage, + PortStreams, + WrappedIframeMessage, +} from './shared.js'; +import { Command, isWrappedIframeMessage } from './shared.js'; const IFRAME_URI = 'iframe.html'; -// The actual