diff --git a/eslint.config.mjs b/eslint.config.mjs index e0ae0ec51..3181e0cf3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -124,10 +124,12 @@ const config = createConfig([ files: ['**/*.test.ts', '**/*.test.tsx'], extends: [metamaskVitestConfig], rules: { + // It's fine to do this in tests. + '@typescript-eslint/no-non-null-assertion': 'off', // This causes false positives in tests especially. '@typescript-eslint/unbound-method': 'off', // We should enable this instead, but the rule is unreleased. - // See https://github.com/vitest-dev/eslint-plugin-vitest/issues/359 + // See https://github.com/vitest-dev/eslint-plugin-vitest/issues/591 // 'vitest/unbound-method': 'error', }, }, diff --git a/packages/kernel-browser-runtime/package.json b/packages/kernel-browser-runtime/package.json index 5f2746f75..4be9bfa3e 100644 --- a/packages/kernel-browser-runtime/package.json +++ b/packages/kernel-browser-runtime/package.json @@ -64,7 +64,7 @@ }, "dependencies": { "@endo/marshal": "^1.8.0", - "@metamask/json-rpc-engine": "^10.0.3", + "@metamask/json-rpc-engine": "^10.2.0", "@metamask/kernel-errors": "workspace:^", "@metamask/kernel-rpc-methods": "workspace:^", "@metamask/kernel-store": "workspace:^", diff --git a/packages/kernel-browser-runtime/src/kernel-worker/kernel-worker.ts b/packages/kernel-browser-runtime/src/kernel-worker/kernel-worker.ts index e15a5d198..9108b72fb 100644 --- a/packages/kernel-browser-runtime/src/kernel-worker/kernel-worker.ts +++ b/packages/kernel-browser-runtime/src/kernel-worker/kernel-worker.ts @@ -1,4 +1,4 @@ -import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import { JsonRpcServer } from '@metamask/json-rpc-engine/v2'; import { makeSQLKernelDatabase } from '@metamask/kernel-store/sqlite/wasm'; import { isJsonRpcCall } from '@metamask/kernel-utils'; import type { JsonRpcCall } from '@metamask/kernel-utils'; @@ -9,14 +9,14 @@ import { MessagePortDuplexStream, receiveMessagePort, } from '@metamask/streams/browser'; -import type { JsonRpcRequest, JsonRpcResponse } from '@metamask/utils'; +import type { JsonRpcResponse } from '@metamask/utils'; import defaultSubcluster from '../default-cluster.json'; import { PlatformServicesClient } from '../PlatformServicesClient.ts'; import { receiveUiConnections } from '../ui-connections.ts'; import { getRelaysFromCurrentLocation } from '../utils/relay-query-string.ts'; import { makeLoggingMiddleware } from './middleware/logging.ts'; -import { createPanelMessageMiddleware } from './middleware/panel-message.ts'; +import { makePanelMessageMiddleware } from './middleware/panel-message.ts'; const logger = new Logger('kernel-worker'); const DB_FILENAME = 'store.db'; @@ -57,13 +57,15 @@ async function main(): Promise { }, ); - const kernelEngine = new JsonRpcEngine(); - kernelEngine.push(makeLoggingMiddleware(logger.subLogger('kernel-command'))); - kernelEngine.push(createPanelMessageMiddleware(kernel, kernelDatabase)); - // JsonRpcEngine type error: does not handle JSON-RPC notifications + const rpcServer = new JsonRpcServer({ + middleware: [ + makeLoggingMiddleware(logger.subLogger('kernel-command')), + makePanelMessageMiddleware(kernel, kernelDatabase), + ], + }); + receiveUiConnections({ - handleInstanceMessage: async (request) => - kernelEngine.handle(request as JsonRpcRequest), + handleInstanceMessage: async (request) => rpcServer.handle(request), logger, }); diff --git a/packages/kernel-browser-runtime/src/kernel-worker/middleware/logging.test.ts b/packages/kernel-browser-runtime/src/kernel-worker/middleware/logging.test.ts index 0f5bc18c2..db7ba2d9a 100644 --- a/packages/kernel-browser-runtime/src/kernel-worker/middleware/logging.test.ts +++ b/packages/kernel-browser-runtime/src/kernel-worker/middleware/logging.test.ts @@ -1,31 +1,25 @@ -import { - createAsyncMiddleware, - JsonRpcEngine, -} from '@metamask/json-rpc-engine'; +import { JsonRpcEngineV2 } from '@metamask/json-rpc-engine/v2'; import { Logger } from '@metamask/logger'; -import type { JsonRpcRequest, JsonRpcSuccess } from '@metamask/utils'; +import type { JsonRpcRequest } from '@metamask/utils'; +import { delay } from '@ocap/repo-tools/test-utils'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { makeLoggingMiddleware } from './logging.ts'; describe('loggingMiddleware', () => { - let engine: JsonRpcEngine; let logger: Logger; beforeEach(() => { vi.clearAllMocks(); - engine = new JsonRpcEngine(); logger = new Logger('test'); - engine.push(makeLoggingMiddleware(logger)); }); it('should pass the request to the next middleware', async () => { // Create a spy middleware to verify the request is passed through - const nextSpy = vi.fn((_req, res, next) => { - res.result = 'success'; - return next(); + const nextSpy = vi.fn(() => 'success'); + const engine = JsonRpcEngineV2.create({ + middleware: [makeLoggingMiddleware(logger), nextSpy], }); - engine.push(nextSpy); const request: JsonRpcRequest = { id: 1, @@ -40,9 +34,8 @@ describe('loggingMiddleware', () => { it('should return the result from the next middleware', async () => { // Add a middleware that sets a result - engine.push((_req, res, _next, end) => { - res.result = 'test result'; - return end(); + const engine = JsonRpcEngineV2.create({ + middleware: [makeLoggingMiddleware(logger), () => 'test result'], }); const request: JsonRpcRequest = { @@ -52,20 +45,21 @@ describe('loggingMiddleware', () => { params: {}, }; - const response = (await engine.handle(request)) as JsonRpcSuccess; - expect(response.result).toBe('test result'); + const result = await engine.handle(request); + expect(result).toBe('test result'); }); it('should log the execution duration', async () => { const debugSpy = vi.spyOn(logger, 'debug'); // Add a middleware that introduces a delay - engine.push( - createAsyncMiddleware(async (_req, res, _next) => { - await new Promise((resolve) => setTimeout(resolve, 10)); - res.result = 'delayed result'; - }), - ); + const nextSpy = vi.fn(async () => { + await delay(10); + return 'delayed result'; + }); + const engine = JsonRpcEngineV2.create({ + middleware: [makeLoggingMiddleware(logger), nextSpy], + }); const request: JsonRpcRequest = { id: 3, @@ -86,8 +80,13 @@ describe('loggingMiddleware', () => { const error = new Error('Test error'); // Add a middleware that throws an error - engine.push(() => { - throw error; + const engine = JsonRpcEngineV2.create({ + middleware: [ + makeLoggingMiddleware(logger), + () => { + throw error; + }, + ], }); const request: JsonRpcRequest = { @@ -97,11 +96,7 @@ describe('loggingMiddleware', () => { params: {}, }; - expect(await engine.handle(request)).toMatchObject({ - error: expect.objectContaining({ - message: 'Test error', - }), - }); + await expect(engine.handle(request)).rejects.toThrow(error); expect(debugSpy).toHaveBeenCalledWith( expect.stringMatching(/Command executed in \d*\.?\d+ms/u), diff --git a/packages/kernel-browser-runtime/src/kernel-worker/middleware/logging.ts b/packages/kernel-browser-runtime/src/kernel-worker/middleware/logging.ts index 842ead757..2bc13ac4e 100644 --- a/packages/kernel-browser-runtime/src/kernel-worker/middleware/logging.ts +++ b/packages/kernel-browser-runtime/src/kernel-worker/middleware/logging.ts @@ -1,15 +1,15 @@ -import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; -import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine/v2'; import { Logger } from '@metamask/logger'; -import type { Json, JsonRpcParams } from '@metamask/utils'; -export const makeLoggingMiddleware = ( - logger: Logger, -): JsonRpcMiddleware => - createAsyncMiddleware(async (_req, _res, next) => { +export const makeLoggingMiddleware = + (logger: Logger): JsonRpcMiddleware => + async ({ next }) => { const start = performance.now(); - // eslint-disable-next-line n/callback-return - await next(); - const duration = performance.now() - start; - logger.debug(`Command executed in ${duration}ms`); - }); + try { + // eslint-disable-next-line n/callback-return + await next(); + } finally { + const duration = performance.now() - start; + logger.debug(`Command executed in ${duration}ms`); + } + }; diff --git a/packages/kernel-browser-runtime/src/kernel-worker/middleware/panel-message.test.ts b/packages/kernel-browser-runtime/src/kernel-worker/middleware/panel-message.test.ts index 2a2d7eff2..635ee9010 100644 --- a/packages/kernel-browser-runtime/src/kernel-worker/middleware/panel-message.test.ts +++ b/packages/kernel-browser-runtime/src/kernel-worker/middleware/panel-message.test.ts @@ -1,10 +1,10 @@ -import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import { JsonRpcEngineV2 } from '@metamask/json-rpc-engine/v2'; import type { KernelDatabase } from '@metamask/kernel-store'; -import type { ClusterConfig, Kernel } from '@metamask/ocap-kernel'; +import type { Kernel } from '@metamask/ocap-kernel'; import type { JsonRpcRequest } from '@metamask/utils'; import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { createPanelMessageMiddleware } from './panel-message.ts'; +import { makePanelMessageMiddleware } from './panel-message.ts'; const { mockAssertHasMethod, mockExecute } = vi.hoisted(() => ({ mockAssertHasMethod: vi.fn(), @@ -25,15 +25,6 @@ vi.mock('@metamask/kernel-rpc-methods', () => ({ assertHasMethod = mockAssertHasMethod; execute = (method: string, params: unknown) => { - // For updateClusterConfig test, call the actual implementation - if (method === 'updateClusterConfig' && params) { - const updateFn = this.#dependencies.updateClusterConfig as ( - config: unknown, - ) => void; - updateFn(params); - return Promise.resolve(); - } - // For executeDBQuery test, call the actual implementation if ( method === 'executeDBQuery' && @@ -56,36 +47,31 @@ vi.mock('../handlers/index.ts', () => ({ handlers: { testMethod1: { method: 'testMethod1' }, testMethod2: { method: 'testMethod2' }, - updateClusterConfig: { method: 'updateClusterConfig' }, executeDBQuery: { method: 'executeDBQuery' }, }, })); -describe('createPanelMessageMiddleware', () => { +describe('makePanelMessageMiddleware', () => { let mockKernel: Kernel; let mockKernelDatabase: KernelDatabase; - let engine: JsonRpcEngine; + let engine: JsonRpcEngineV2; beforeEach(() => { // Set up mocks - mockKernel = { - clusterConfig: {} as ClusterConfig, - } as Kernel; + mockKernel = {} as Kernel; mockKernelDatabase = { executeQuery: vi.fn(), } as unknown as KernelDatabase; - // Create a new JSON-RPC engine with our middleware - engine = new JsonRpcEngine(); - engine.push(createPanelMessageMiddleware(mockKernel, mockKernelDatabase)); + engine = JsonRpcEngineV2.create({ + middleware: [makePanelMessageMiddleware(mockKernel, mockKernelDatabase)], + }); }); it('should handle successful command execution', async () => { - // Set up the mock to return a successful result const expectedResult = { success: true, data: 'test data' }; mockExecute.mockResolvedValueOnce(expectedResult); - // Create a request const request = { id: 1, jsonrpc: '2.0', @@ -93,25 +79,15 @@ describe('createPanelMessageMiddleware', () => { params: { foo: 'bar' }, } as JsonRpcRequest; - // Process the request - const response = await engine.handle(request); + const result = await engine.handle(request); - // Verify the response contains the expected result - expect(response).toStrictEqual({ - id: 1, - jsonrpc: '2.0', - result: expectedResult, - }); - - // Verify the middleware called execute with the right parameters + expect(result).toStrictEqual(expectedResult); expect(mockExecute).toHaveBeenCalledWith('testMethod1', { foo: 'bar' }); }); it('should handle command execution with empty params', async () => { - // Set up the mock to return a successful result mockExecute.mockResolvedValueOnce(null); - // Create a request with no params const request = { id: 2, jsonrpc: '2.0', @@ -119,26 +95,16 @@ describe('createPanelMessageMiddleware', () => { params: [], } as JsonRpcRequest; - // Process the request - const response = await engine.handle(request); + const result = await engine.handle(request); - // Verify the middleware called execute with the right parameters + expect(result).toBeNull(); expect(mockExecute).toHaveBeenCalledWith('testMethod2', []); - - // Verify the response contains the expected result - expect(response).toStrictEqual({ - id: 2, - jsonrpc: '2.0', - result: null, - }); }); it('should handle command execution errors', async () => { - // Set up the mock to throw an error const error = new Error('Test error'); mockExecute.mockRejectedValueOnce(error); - // Create a request const request = { id: 3, jsonrpc: '2.0', @@ -146,32 +112,13 @@ describe('createPanelMessageMiddleware', () => { params: { foo: 'bar' }, } as JsonRpcRequest; - // Process the request - const response = await engine.handle(request); - - // Verify the middleware called execute + await expect(engine.handle(request)).rejects.toThrow(error); expect(mockExecute).toHaveBeenCalledWith('testMethod1', { foo: 'bar' }); - - // Verify the response contains the error - expect(response).toStrictEqual({ - id: 3, - jsonrpc: '2.0', - error: expect.objectContaining({ - code: -32603, // Internal error - data: expect.objectContaining({ - cause: expect.objectContaining({ - message: 'Test error', - }), - }), - }), - }); }); it('should handle array params', async () => { - // Set up the mock to return a successful result - mockExecute.mockResolvedValueOnce({ result: 'array processed' }); + mockExecute.mockResolvedValueOnce('array processed'); - // Create a request with array params const request = { id: 4, jsonrpc: '2.0', @@ -179,25 +126,14 @@ describe('createPanelMessageMiddleware', () => { params: ['item1', 'item2'], } as JsonRpcRequest; - // Process the request - const response = await engine.handle(request); - - // Verify the middleware called execute with the array params + const result = await engine.handle(request); + expect(result).toBe('array processed'); expect(mockExecute).toHaveBeenCalledWith('testMethod1', ['item1', 'item2']); - - // Verify the response contains the expected result - expect(response).toStrictEqual({ - id: 4, - jsonrpc: '2.0', - result: { result: 'array processed' }, - }); }); it('should handle requests without params', async () => { - // Set up the mock to return a successful result mockExecute.mockResolvedValueOnce({ status: 'ok' }); - // Create a request without params const request = { id: 5, jsonrpc: '2.0', @@ -205,18 +141,9 @@ describe('createPanelMessageMiddleware', () => { // No params field } as JsonRpcRequest; - // Process the request - const response = await engine.handle(request); - - // Verify the middleware called execute with undefined params + const result = await engine.handle(request); + expect(result).toStrictEqual({ status: 'ok' }); expect(mockExecute).toHaveBeenCalledWith('testMethod2', undefined); - - // Verify the response contains the expected result - expect(response).toStrictEqual({ - id: 5, - jsonrpc: '2.0', - result: { status: 'ok' }, - }); }); it('rejects unknown methods', async () => { @@ -230,81 +157,30 @@ describe('createPanelMessageMiddleware', () => { throw new Error('The method does not exist / is not available.'); }); - const response = await engine.handle(request); - + await expect(engine.handle(request)).rejects.toThrow( + 'The method does not exist / is not available.', + ); expect(mockExecute).not.toHaveBeenCalled(); - - // Verify the response contains the error - expect(response).toStrictEqual({ - id: 6, - jsonrpc: '2.0', - error: expect.objectContaining({ - code: -32603, // Internal error - data: expect.objectContaining({ - cause: expect.objectContaining({ - message: 'The method does not exist / is not available.', - }), - }), - }), - }); - }); - - it('should update kernel.clusterConfig when updateClusterConfig is called', async () => { - // Create a test cluster config that matches the expected structure - const testConfig = { - bootstrap: 'test-bootstrap', - vats: { - test: { - bundleSpec: 'test-bundle', - }, - }, - forceReset: true, - } as ClusterConfig; - - // Create a request to update cluster config - const request = { - id: 7, - jsonrpc: '2.0', - method: 'updateClusterConfig', - params: testConfig, - } as JsonRpcRequest; - - // Process the request - await engine.handle(request); - - // Verify that kernel.clusterConfig was updated with the provided config - expect(mockKernel.clusterConfig).toStrictEqual(testConfig); }); it('should call kernelDatabase.executeQuery when executeDBQuery is called', async () => { - // Set up mock database response const mockQueryResult = [{ id: '1', name: 'test' }]; vi.mocked(mockKernelDatabase.executeQuery).mockResolvedValueOnce( mockQueryResult, ); - // Test SQL query const testSql = 'SELECT * FROM test_table'; - // Create a request to execute DB query const request = { id: 8, jsonrpc: '2.0', method: 'executeDBQuery', params: { sql: testSql }, - } as JsonRpcRequest; + } satisfies JsonRpcRequest; - // Process the request - const response = await engine.handle(request); + const result = await engine.handle(request); - // Verify that kernelDatabase.executeQuery was called with the correct SQL + expect(result).toStrictEqual(mockQueryResult); expect(mockKernelDatabase.executeQuery).toHaveBeenCalledWith(testSql); - - // Verify the response contains the query result - expect(response).toStrictEqual({ - id: 8, - jsonrpc: '2.0', - result: mockQueryResult, - }); }); }); diff --git a/packages/kernel-browser-runtime/src/kernel-worker/middleware/panel-message.ts b/packages/kernel-browser-runtime/src/kernel-worker/middleware/panel-message.ts index 1ab9cb28b..31a3a076f 100644 --- a/packages/kernel-browser-runtime/src/kernel-worker/middleware/panel-message.ts +++ b/packages/kernel-browser-runtime/src/kernel-worker/middleware/panel-message.ts @@ -1,36 +1,34 @@ -import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; -import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine/v2'; import { RpcService } from '@metamask/kernel-rpc-methods'; import type { KernelDatabase } from '@metamask/kernel-store'; -import type { ClusterConfig, Kernel } from '@metamask/ocap-kernel'; -import type { Json, JsonRpcParams } from '@metamask/utils'; +import type { Kernel } from '@metamask/ocap-kernel'; +import { isJsonRpcRequest } from '@metamask/utils'; import { rpcHandlers } from '../../rpc-handlers/index.ts'; /** - * Creates a middleware function that handles panel messages. + * Makes a middleware function that handles panel messages. * * @param kernel - The kernel instance. * @param kernelDatabase - The kernel database instance. * @returns The middleware function. */ -export const createPanelMessageMiddleware = ( +export const makePanelMessageMiddleware = ( kernel: Kernel, kernelDatabase: KernelDatabase, -): JsonRpcMiddleware => { +): JsonRpcMiddleware => { const rpcService: RpcService = new RpcService( rpcHandlers, { kernel, executeDBQuery: (sql: string) => kernelDatabase.executeQuery(sql), - updateClusterConfig: (config: ClusterConfig) => - (kernel.clusterConfig = config), }, ); - return createAsyncMiddleware(async (req, res, _next) => { - const { method, params } = req; + return async ({ request }) => { + const { method, params } = request; rpcService.assertHasMethod(method); - res.result = await rpcService.execute(method, params); - }); + const result = await rpcService.execute(method, params); + return isJsonRpcRequest(request) ? result : undefined; + }; }; diff --git a/packages/kernel-browser-runtime/src/ui-connections.test.ts b/packages/kernel-browser-runtime/src/ui-connections.test.ts index 300fd82d7..b4ce3deb9 100644 --- a/packages/kernel-browser-runtime/src/ui-connections.test.ts +++ b/packages/kernel-browser-runtime/src/ui-connections.test.ts @@ -1,6 +1,7 @@ import type { JsonRpcCall } from '@metamask/kernel-utils'; import { Logger } from '@metamask/logger'; import type { PostMessageTarget } from '@metamask/streams/browser'; +import { PostMessageDuplexStream } from '@metamask/streams/browser'; import type { JsonRpcResponse } from '@metamask/utils'; import { delay } from '@ocap/repo-tools/test-utils'; import { TestDuplexStream } from '@ocap/repo-tools/test-utils/streams'; @@ -33,10 +34,13 @@ vi.mock('@metamask/streams/browser', async () => { // @ts-expect-error: We're overriding the static make() method class MockStream extends TestDuplexStream { + static instances: MockStream[] = []; + messageTarget: MockPostMessageTarget; constructor({ onEnd, messageTarget }: MockStreamOptions) { super(() => undefined, { readerOnEnd: onEnd, writerOnEnd: onEnd }); + MockStream.instances.push(this); this.messageTarget = messageTarget; this.messageTarget.onmessage = (event) => { // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -101,8 +105,13 @@ const makeMockLogger = () => }) as unknown as Logger; describe('ui-connections', () => { + const streamInstances: PostMessageDuplexStream[] = + // @ts-expect-error: This class is mocked + PostMessageDuplexStream.instances; + beforeEach(() => { MockBroadcastChannel.channels.clear(); + streamInstances.length = 0; }); describe('establishKernelConnection', () => { @@ -169,11 +178,14 @@ describe('ui-connections', () => { const logger = makeMockLogger(); const mockHandleMessage = vi.fn( - async (_request: JsonRpcCall): Promise => ({ - id: 'foo', - jsonrpc: '2.0' as const, - result: { vats: [], clusterConfig: makeClusterConfig() }, - }), + async (request: JsonRpcCall): Promise => + 'id' in request + ? { + id: 1, + jsonrpc: '2.0' as const, + result: { vats: [], clusterConfig: makeClusterConfig() }, + } + : undefined, ); it('should handle new UI connections', async () => { @@ -219,23 +231,78 @@ describe('ui-connections', () => { }), ); + await delay(); + const instanceStream = streamInstances[0]!; + expect(instanceStream).toBeDefined(); + const instanceStreamWriteSpy = vi.spyOn(instanceStream, 'write'); + const instanceChannel = MockBroadcastChannel.channels.get( 'test-instance-channel', - ); - instanceChannel?.onmessage?.( + )!; + instanceChannel.onmessage?.( new MessageEvent('message', { data: { method: 'getStatus', params: null, + id: 1, }, }), ); - await delay(10); + await delay(); expect(mockHandleMessage).toHaveBeenCalledWith({ method: 'getStatus', params: null, + id: 1, + }); + expect(instanceStreamWriteSpy).toHaveBeenCalledWith({ + jsonrpc: '2.0', + id: 1, + result: { vats: [], clusterConfig: makeClusterConfig() }, + }); + }); + + it('should handle JSON-RPC notifications', async () => { + receiveUiConnections({ + handleInstanceMessage: mockHandleMessage, + logger, + }); + + const controlChannel = MockBroadcastChannel.channels.get( + UI_CONTROL_CHANNEL_NAME, + ); + controlChannel?.onmessage?.( + new MessageEvent('message', { + data: { + method: 'init', + params: 'test-instance-channel', + }, + }), + ); + + await delay(); + const instanceStream = streamInstances[0]!; + expect(instanceStream).toBeDefined(); + const instanceStreamWriteSpy = vi.spyOn(instanceStream, 'write'); + + const instanceChannel = MockBroadcastChannel.channels.get( + 'test-instance-channel', + )!; + instanceChannel.onmessage?.( + new MessageEvent('message', { + data: { + method: 'notification', + }, + }), + ); + + await delay(); + + expect(mockHandleMessage).toHaveBeenCalledTimes(1); + expect(mockHandleMessage).toHaveBeenCalledWith({ + method: 'notification', }); + expect(instanceStreamWriteSpy).not.toHaveBeenCalled(); }); it('should handle multiple simultaneous connections', async () => { @@ -290,7 +357,7 @@ describe('ui-connections', () => { }, }), ); - await delay(10); + await delay(); expect(MockBroadcastChannel.channels.size).toBe(2); const instanceChannel = MockBroadcastChannel.channels.get( @@ -299,7 +366,7 @@ describe('ui-connections', () => { instanceChannel?.onmessageerror?.( new MessageEvent('messageerror', { data: new Error('Test error') }), ); - await delay(10); + await delay(); expect(MockBroadcastChannel.channels.size).toBe(1); controlChannel?.onmessage?.( @@ -310,7 +377,7 @@ describe('ui-connections', () => { }, }), ); - await delay(10); + await delay(); expect(MockBroadcastChannel.channels.size).toBe(2); }); @@ -378,7 +445,7 @@ describe('ui-connections', () => { }, }), ); - await delay(10); + await delay(); const instanceChannel = MockBroadcastChannel.channels.get( 'test-instance-channel', @@ -386,7 +453,7 @@ describe('ui-connections', () => { instanceChannel?.onmessageerror?.( new MessageEvent('messageerror', { data: new Error('Test error') }), ); - await delay(10); + await delay(); expect(logger.error).toHaveBeenCalledWith( 'Error handling message from UI instance "test-instance-channel":', diff --git a/packages/kernel-browser-runtime/src/ui-connections.ts b/packages/kernel-browser-runtime/src/ui-connections.ts index c803f1e1e..dcc7f1061 100644 --- a/packages/kernel-browser-runtime/src/ui-connections.ts +++ b/packages/kernel-browser-runtime/src/ui-connections.ts @@ -21,7 +21,9 @@ export type KernelControlReplyStream = PostMessageDuplexStream< JsonRpcCall >; -type HandleInstanceMessage = (request: JsonRpcCall) => Promise; +type HandleInstanceMessage = ( + request: JsonRpcCall, +) => Promise; type Options = { logger: Logger; @@ -140,7 +142,9 @@ export const receiveUiConnections = ({ .then(async (instanceStream) => { return instanceStream.drain(async (message) => { const reply = await handleInstanceMessage(message); - await instanceStream.write(reply); + if (reply !== undefined) { + await instanceStream.write(reply); + } }); }) .catch((error) => { diff --git a/packages/kernel-ui/src/components/SubclusterAccordion.test.tsx b/packages/kernel-ui/src/components/SubclusterAccordion.test.tsx index 9eba079ea..c613393f2 100644 --- a/packages/kernel-ui/src/components/SubclusterAccordion.test.tsx +++ b/packages/kernel-ui/src/components/SubclusterAccordion.test.tsx @@ -130,7 +130,6 @@ describe('SubclusterAccordion', () => { }); it('renders singular vat text when only one vat', () => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const singleVat = mockVats[0]!; render(); diff --git a/vitest.config.ts b/vitest.config.ts index 526cb2936..5e732ba37 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -86,10 +86,10 @@ export default defineConfig({ lines: 94.15, }, 'packages/kernel-browser-runtime/**': { - statements: 83.71, - functions: 90.9, - branches: 94.16, - lines: 83.71, + statements: 83.5, + functions: 90.76, + branches: 93.38, + lines: 83.5, }, 'packages/kernel-errors/**': { statements: 100, @@ -106,7 +106,7 @@ export default defineConfig({ 'packages/kernel-platforms/**': { statements: 99.38, functions: 100, - branches: 96.2, + branches: 96.25, lines: 99.38, }, 'packages/kernel-rpc-methods/**': { diff --git a/yarn.lock b/yarn.lock index 91d594790..a71ec3f35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2009,14 +2009,17 @@ __metadata: languageName: node linkType: hard -"@metamask/json-rpc-engine@npm:^10.0.2, @metamask/json-rpc-engine@npm:^10.0.3, @metamask/json-rpc-engine@npm:^10.1.1": - version: 10.1.1 - resolution: "@metamask/json-rpc-engine@npm:10.1.1" +"@metamask/json-rpc-engine@npm:^10.0.2, @metamask/json-rpc-engine@npm:^10.0.3, @metamask/json-rpc-engine@npm:^10.1.1, @metamask/json-rpc-engine@npm:^10.2.0": + version: 10.2.0 + resolution: "@metamask/json-rpc-engine@npm:10.2.0" dependencies: "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/safe-event-emitter": "npm:^3.0.0" "@metamask/utils": "npm:^11.8.1" - checksum: 10/ad28e430543ca93d4487ff3f9c2d2247013ed9bc1d73867b06f2cea09b0e864207c856cc90ef945e7323e3dacc03205ffdbaddce34fc6c9165cd96bd55dc7bd0 + "@types/deep-freeze-strict": "npm:^1.1.0" + deep-freeze-strict: "npm:^1.1.1" + klona: "npm:^2.0.6" + checksum: 10/c4d588ee2a27c5cd3b4634dbafebebb193092364e1eec2bf1fef2bd2f2352ab7fa59758067ab53440d8d93b812270bdf2c0e508640614b51e5d06d68a76a37c1 languageName: node linkType: hard @@ -2042,7 +2045,7 @@ __metadata: "@metamask/eslint-config": "npm:^14.0.0" "@metamask/eslint-config-nodejs": "npm:^14.0.0" "@metamask/eslint-config-typescript": "npm:^14.0.0" - "@metamask/json-rpc-engine": "npm:^10.0.3" + "@metamask/json-rpc-engine": "npm:^10.2.0" "@metamask/kernel-errors": "workspace:^" "@metamask/kernel-rpc-methods": "workspace:^" "@metamask/kernel-store": "workspace:^" @@ -10471,6 +10474,13 @@ __metadata: languageName: node linkType: hard +"klona@npm:^2.0.6": + version: 2.0.6 + resolution: "klona@npm:2.0.6" + checksum: 10/ed7e2c9af58cb646e758e60b75dec24bf72466066290f78c515a2bae23a06fa280f11ff3210c43b94a18744954aa5358f9d46583d5e4c36da073ecc3606355c4 + languageName: node + linkType: hard + "kolorist@npm:^1.8.0": version: 1.8.0 resolution: "kolorist@npm:1.8.0"