Skip to content

Commit 83c673f

Browse files
committed
feat(kernel-browser-runtime): pass full RemoteCommsOptions via worker URL
1 parent 22fe1a6 commit 83c673f

11 files changed

Lines changed: 448 additions & 239 deletions

File tree

docs/usage.md

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,30 +49,32 @@ const kernel = await Kernel.make(platformServices, kernelDatabase, {
4949
});
5050
```
5151

52-
#### Configuring Relay Addresses for Workers
52+
#### Configuring Remote Comms for Workers
5353

54-
When creating kernel workers with relay configuration, use the utilities from `@metamask/kernel-browser-runtime`:
54+
When creating kernel workers with relay and other remote comms options, use the utilities from `@metamask/kernel-browser-runtime`:
5555

5656
```typescript
5757
import {
58-
createWorkerUrlWithRelays,
59-
getRelaysFromCurrentLocation,
58+
createCommsQueryString,
59+
getCommsParamsFromCurrentLocation,
6060
} from '@metamask/kernel-browser-runtime';
6161

62-
// Define relay addresses (libp2p multiaddrs)
63-
const relays = [
64-
'/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWJBDqsyHQF2MWiCdU4kdqx4zTsSTLRdShg7Ui6CRWB4uc',
65-
];
66-
67-
// Create a worker with relay configuration
68-
const worker = new Worker(
69-
createWorkerUrlWithRelays('kernel-worker.js', relays),
70-
{ type: 'module' },
71-
);
72-
73-
// Inside the worker, retrieve relay configuration
74-
const relays = getRelaysFromCurrentLocation();
75-
await kernel.initRemoteComms(relays);
62+
// Define relay addresses and other options (libp2p multiaddrs, etc.)
63+
const commsParams = {
64+
relays: [
65+
'/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWJBDqsyHQF2MWiCdU4kdqx4zTsSTLRdShg7Ui6CRWB4uc',
66+
],
67+
allowedWsHosts: ['localhost'],
68+
};
69+
70+
// Build worker URL with query string (append params with .set() before .toString() if needed)
71+
const workerUrl = new URL('kernel-worker.js', import.meta.url);
72+
workerUrl.search = createCommsQueryString(commsParams).toString();
73+
const worker = new Worker(workerUrl, { type: 'module' });
74+
75+
// Inside the worker, retrieve all comms options and init
76+
const options = getCommsParamsFromCurrentLocation();
77+
await kernel.initRemoteComms(options);
7678
```
7779

7880
### Node.js Environment

packages/extension/src/offscreen.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {
22
makeIframeVatWorker,
33
PlatformServicesServer,
4-
createRelayQueryString,
4+
createCommsQueryString,
55
setupConsoleForwarding,
66
isConsoleForwardMessage,
77
} from '@metamask/kernel-browser-runtime';
88
import { delay, isJsonRpcMessage } from '@metamask/kernel-utils';
99
import type { JsonRpcMessage } from '@metamask/kernel-utils';
1010
import { Logger } from '@metamask/logger';
11+
import type { RemoteCommsOptions } from '@metamask/ocap-kernel';
1112
import type { DuplexStream } from '@metamask/streams';
1213
import {
1314
initializeMessageChannel,
@@ -16,6 +17,13 @@ import {
1617
} from '@metamask/streams/browser';
1718
import type { PostMessageTarget } from '@metamask/streams/browser';
1819

20+
/**
21+
* Default relay addresses to use if not provided to {@link makeKernelWorker}.
22+
*/
23+
const DEFAULT_RELAYS = [
24+
'/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWJBDqsyHQF2MWiCdU4kdqx4zTsSTLRdShg7Ui6CRWB4uc',
25+
];
26+
1927
const logger = new Logger('offscreen');
2028

2129
main().catch(logger.error);
@@ -59,19 +67,14 @@ async function main(): Promise<void> {
5967
/**
6068
* Creates and initializes the kernel worker.
6169
*
70+
* @param remoteCommsOptions - Options passed to {@link Kernel.initRemoteComms} via the worker URL (relays, allowedWsHosts, etc.); defaults to DEFAULT_RELAYS.
6271
* @returns The message port stream for worker communication
6372
*/
64-
async function makeKernelWorker(): Promise<
65-
DuplexStream<JsonRpcMessage, JsonRpcMessage>
66-
> {
67-
// Assign local relay address generated from `yarn ocap relay`
68-
const relayQueryString = createRelayQueryString([
69-
'/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWJBDqsyHQF2MWiCdU4kdqx4zTsSTLRdShg7Ui6CRWB4uc',
70-
// '/dns4/troll.fudco.com/tcp/9001/ws/p2p/12D3KooWJBDqsyHQF2MWiCdU4kdqx4zTsSTLRdShg7Ui6CRWB4uc',
71-
// '/dns4/troll.fudco.com/tcp/9003/ws/p2p/12D3KooWL9PaFePyNg2hFLpaWPFEPVYGzTvrWAFU9Lk2KoiKqJqR',
72-
]);
73-
74-
const workerUrlParams = new URLSearchParams(relayQueryString);
73+
async function makeKernelWorker(
74+
remoteCommsOptions?: Partial<RemoteCommsOptions>,
75+
): Promise<DuplexStream<JsonRpcMessage, JsonRpcMessage>> {
76+
const opts = remoteCommsOptions ?? { relays: DEFAULT_RELAYS };
77+
const workerUrlParams = createCommsQueryString(opts);
7578
workerUrlParams.set('reset-storage', process.env.RESET_STORAGE ?? 'false');
7679

7780
const workerUrl = new URL('kernel-worker.js', import.meta.url);

packages/kernel-browser-runtime/src/index.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ describe('index', () => {
88
'PlatformServicesClient',
99
'PlatformServicesServer',
1010
'connectToKernel',
11-
'createRelayQueryString',
11+
'createCommsQueryString',
1212
'getCapTPMessage',
13-
'getRelaysFromCurrentLocation',
13+
'getCommsParamsFromCurrentLocation',
1414
'handleConsoleForwardMessage',
1515
'isCapTPNotification',
1616
'isConsoleForwardMessage',
1717
'makeBackgroundCapTP',
1818
'makeCapTPNotification',
1919
'makeIframeVatWorker',
20-
'parseRelayQueryString',
20+
'parseCommsQueryString',
2121
'receiveInternalConnections',
2222
'rpcHandlers',
2323
'rpcMethodSpecs',

packages/kernel-browser-runtime/src/kernel-worker/kernel-worker.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { setupConsoleForwarding } from '../utils/console-forwarding.ts';
2121
import { makeKernelCapTP } from './captp/index.ts';
2222
import { makeLoggingMiddleware } from './middleware/logging.ts';
2323
import { makePanelMessageMiddleware } from './middleware/panel-message.ts';
24-
import { getRelaysFromCurrentLocation } from '../utils/relay-query-string.ts';
24+
import { getCommsParamsFromCurrentLocation } from '../utils/comms-query-string.ts';
2525

2626
const logger = new Logger('kernel-worker');
2727
const DB_FILENAME = 'store.db';
@@ -107,6 +107,6 @@ async function main(): Promise<void> {
107107
logger.error('Message stream error:', error);
108108
});
109109

110-
const relays = getRelaysFromCurrentLocation();
111-
await kernel.initRemoteComms({ relays });
110+
const commsOptions = getCommsParamsFromCurrentLocation();
111+
await kernel.initRemoteComms(commsOptions);
112112
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2+
3+
import {
4+
createCommsQueryString,
5+
parseCommsQueryString,
6+
getCommsParamsFromCurrentLocation,
7+
} from './comms-query-string.ts';
8+
9+
// Mock the logger module
10+
vi.mock('@metamask/logger', () => ({
11+
Logger: vi.fn().mockImplementation(function () {
12+
return {
13+
error: vi.fn(),
14+
warn: vi.fn(),
15+
};
16+
}),
17+
}));
18+
19+
describe('comms-query-string', () => {
20+
describe('createCommsQueryString', () => {
21+
it('returns URLSearchParams with relays only', () => {
22+
const relays = ['/ip4/127.0.0.1/tcp/9001/ws'];
23+
const result = createCommsQueryString({ relays });
24+
expect(result.get('relays')).toBe(JSON.stringify(relays));
25+
});
26+
27+
it('returns URLSearchParams with allowedWsHosts only', () => {
28+
const allowedWsHosts = ['localhost', 'relay.example.com'];
29+
const result = createCommsQueryString({ allowedWsHosts });
30+
expect(result.get('allowedWsHosts')).toBe(JSON.stringify(allowedWsHosts));
31+
});
32+
33+
it('returns URLSearchParams with both params', () => {
34+
const relays = ['/ip4/127.0.0.1/tcp/9001/ws'];
35+
const allowedWsHosts = ['localhost'];
36+
const result = createCommsQueryString({ relays, allowedWsHosts });
37+
expect(result.has('relays')).toBe(true);
38+
expect(result.has('allowedWsHosts')).toBe(true);
39+
});
40+
41+
it('returns empty URLSearchParams for empty arrays', () => {
42+
expect(
43+
createCommsQueryString({ relays: [], allowedWsHosts: [] }).toString(),
44+
).toBe('');
45+
expect(createCommsQueryString({}).toString()).toBe('');
46+
});
47+
48+
it('returns URLSearchParams with number options and directListenAddresses', () => {
49+
const result = createCommsQueryString({
50+
relays: ['/ip4/127.0.0.1/tcp/9001/ws'],
51+
maxRetryAttempts: 3,
52+
maxQueue: 100,
53+
directListenAddresses: ['/ip4/0.0.0.0/udp/0/quic-v1'],
54+
});
55+
expect(result.get('relays')).toBe(
56+
JSON.stringify(['/ip4/127.0.0.1/tcp/9001/ws']),
57+
);
58+
expect(result.get('maxRetryAttempts')).toBe('3');
59+
expect(result.get('maxQueue')).toBe('100');
60+
expect(result.get('directListenAddresses')).toBe(
61+
JSON.stringify(['/ip4/0.0.0.0/udp/0/quic-v1']),
62+
);
63+
});
64+
65+
it('round-trips full options via createCommsQueryString and parseCommsQueryString', () => {
66+
const options = {
67+
relays: ['/dns4/relay.example.com/tcp/443/wss/p2p/QmRelay'],
68+
allowedWsHosts: ['relay.example.com'],
69+
maxRetryAttempts: 5,
70+
maxQueue: 200,
71+
};
72+
const params = createCommsQueryString(options);
73+
expect(parseCommsQueryString(`?${params.toString()}`)).toStrictEqual(
74+
options,
75+
);
76+
});
77+
});
78+
79+
describe('parseCommsQueryString', () => {
80+
it('returns both relays and allowedWsHosts', () => {
81+
const queryString = `?relays=${encodeURIComponent(JSON.stringify(['/ip4/127.0.0.1/tcp/9001/ws']))}&allowedWsHosts=${encodeURIComponent(JSON.stringify(['localhost']))}`;
82+
expect(parseCommsQueryString(queryString)).toStrictEqual({
83+
relays: ['/ip4/127.0.0.1/tcp/9001/ws'],
84+
allowedWsHosts: ['localhost'],
85+
});
86+
});
87+
88+
it('returns empty object when no comms params present', () => {
89+
expect(parseCommsQueryString('?foo=bar')).toStrictEqual({});
90+
});
91+
92+
it('parses directListenAddresses and number options', () => {
93+
const queryString = `?directListenAddresses=${encodeURIComponent(JSON.stringify(['/ip4/0.0.0.0/udp/0/quic-v1']))}&maxRetryAttempts=5&maxQueue=100`;
94+
expect(parseCommsQueryString(queryString)).toStrictEqual({
95+
directListenAddresses: ['/ip4/0.0.0.0/udp/0/quic-v1'],
96+
maxRetryAttempts: 5,
97+
maxQueue: 100,
98+
});
99+
});
100+
101+
it('parses mnemonic when present', () => {
102+
const mnemonic =
103+
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
104+
const queryString = `?mnemonic=${encodeURIComponent(mnemonic)}`;
105+
expect(parseCommsQueryString(queryString)).toStrictEqual({ mnemonic });
106+
});
107+
108+
it('ignores invalid number values', () => {
109+
expect(parseCommsQueryString('?maxRetryAttempts=-1')).toStrictEqual({});
110+
expect(parseCommsQueryString('?maxRetryAttempts=1.5')).toStrictEqual({});
111+
expect(parseCommsQueryString('?maxRetryAttempts=10')).toStrictEqual({
112+
maxRetryAttempts: 10,
113+
});
114+
});
115+
});
116+
117+
describe('getCommsParamsFromCurrentLocation', () => {
118+
const originalLocation = globalThis.location;
119+
120+
beforeEach(() => {
121+
Object.defineProperty(globalThis, 'location', {
122+
value: { search: '' },
123+
writable: true,
124+
configurable: true,
125+
});
126+
});
127+
128+
afterEach(() => {
129+
if (originalLocation) {
130+
Object.defineProperty(globalThis, 'location', {
131+
value: originalLocation,
132+
writable: true,
133+
configurable: true,
134+
});
135+
} else {
136+
// @ts-expect-error - deleting global property
137+
delete globalThis.location;
138+
}
139+
});
140+
141+
it('returns relays and allowedWsHosts from location', () => {
142+
const relays = ['/ip4/127.0.0.1/tcp/9001/ws'];
143+
const allowedWsHosts = ['localhost'];
144+
globalThis.location.search = `?relays=${encodeURIComponent(JSON.stringify(relays))}&allowedWsHosts=${encodeURIComponent(JSON.stringify(allowedWsHosts))}`;
145+
expect(getCommsParamsFromCurrentLocation()).toStrictEqual({
146+
relays,
147+
allowedWsHosts,
148+
});
149+
});
150+
151+
it('returns empty object when location is undefined', () => {
152+
// @ts-expect-error - testing undefined location
153+
delete globalThis.location;
154+
expect(getCommsParamsFromCurrentLocation()).toStrictEqual({});
155+
});
156+
157+
it('returns all parsed options including numbers and directListenAddresses', () => {
158+
globalThis.location.search = `?relays=${encodeURIComponent(JSON.stringify(['/ip4/127.0.0.1/tcp/9001/ws']))}&maxQueue=50&stalePeerTimeoutMs=3600000`;
159+
expect(getCommsParamsFromCurrentLocation()).toStrictEqual({
160+
relays: ['/ip4/127.0.0.1/tcp/9001/ws'],
161+
maxQueue: 50,
162+
stalePeerTimeoutMs: 3600000,
163+
});
164+
});
165+
});
166+
});

0 commit comments

Comments
 (0)