@peerbit/canonical-host 0.0.0-e209d2e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ import { CanonicalHost, PeerbitCanonicalRuntime, } from "./index.js";
2
+ let hostPromise;
3
+ const getHost = async (options) => {
4
+ if (hostPromise)
5
+ return hostPromise;
6
+ const { modules, hostOptions, ...runtimeOptions } = options ?? {};
7
+ const runtime = new PeerbitCanonicalRuntime(runtimeOptions);
8
+ const host = new CanonicalHost(runtime, hostOptions);
9
+ if (modules?.length) {
10
+ host.registerModules(modules);
11
+ }
12
+ hostPromise = Promise.resolve(host);
13
+ return hostPromise;
14
+ };
15
+ export const installSharedWorkerHost = (options) => {
16
+ const scope = self;
17
+ const onConnect = async (e) => {
18
+ const port = e.ports?.[0];
19
+ if (!port) {
20
+ throw new Error("SharedWorker onconnect event missing MessagePort");
21
+ }
22
+ const host = await getHost(options);
23
+ host.attachControlPort(port);
24
+ };
25
+ scope.addEventListener("connect", onConnect);
26
+ };
27
+ //# sourceMappingURL=shared-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-worker.js","sourceRoot":"","sources":["../../src/shared-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EAIb,uBAAuB,GACvB,MAAM,YAAY,CAAC;AAOpB,IAAI,WAA+C,CAAC;AAEpD,MAAM,OAAO,GAAG,KAAK,EAAE,OAAwC,EAAE,EAAE;IAClE,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;IAClE,MAAM,OAAO,GAAG,IAAI,uBAAuB,CAAC,cAAc,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACrD,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;QACrB,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IACD,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,WAAW,CAAC;AACpB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACtC,OAAwC,EACvC,EAAE;IACH,MAAM,KAAK,GAAG,IAA0C,CAAC;IACzD,MAAM,SAAS,GAAG,KAAK,EAAE,CAAe,EAAE,EAAE;QAC3C,MAAM,IAAI,GAAiB,CAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC;IACF,KAAK,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAqC,CAAC,CAAC;AAC1E,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { CanonicalHost, type CanonicalHostOptions, type CanonicalModule, type CanonicalRuntimeOptions } from "./index.js";
2
+ export declare const CANONICAL_WINDOW_CONNECT_OP = "__peerbit_canonical_window_connect__";
3
+ export declare const CANONICAL_WINDOW_READY_KEY = "__peerbit_canonical_window_ready__";
4
+ export type InstallWindowHostOptions = CanonicalRuntimeOptions & {
5
+ modules?: CanonicalModule[];
6
+ hostOptions?: CanonicalHostOptions;
7
+ host?: CanonicalHost;
8
+ createHost?: () => CanonicalHost | Promise<CanonicalHost>;
9
+ channel?: string;
10
+ targetOrigin?: string;
11
+ allow?: (event: MessageEvent) => boolean;
12
+ onError?: (error: unknown) => void;
13
+ };
14
+ export type WindowHost = {
15
+ connect: (childWindow: Window, options?: {
16
+ origin?: string;
17
+ }) => void;
18
+ disconnect: (childWindow: Window) => void;
19
+ close: () => void;
20
+ };
21
+ export declare const installWindowHost: (options?: InstallWindowHostOptions) => WindowHost;
22
+ //# sourceMappingURL=window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../src/window.ts"],"names":[],"mappings":"AACA,OAAO,EACN,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAE5B,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,2BAA2B,yCACD,CAAC;AACxC,eAAO,MAAM,0BAA0B,uCAAuC,CAAC;AAE/E,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,GAAG;IAChE,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,oBAAoB,CAAC;IACnC,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,OAAO,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACtE,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,KAAK,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAuCF,eAAO,MAAM,iBAAiB,GAC7B,UAAU,wBAAwB,KAChC,UAiGF,CAAC"}
@@ -0,0 +1,117 @@
1
+ import { createWindowTransport } from "@peerbit/canonical-transport";
2
+ import { CanonicalHost, PeerbitCanonicalRuntime, } from "./index.js";
3
+ export const CANONICAL_WINDOW_CONNECT_OP = "__peerbit_canonical_window_connect__";
4
+ export const CANONICAL_WINDOW_READY_KEY = "__peerbit_canonical_window_ready__";
5
+ let hostPromise;
6
+ const getHost = async (options) => {
7
+ if (hostPromise)
8
+ return hostPromise;
9
+ const provided = options?.host;
10
+ const create = options?.createHost;
11
+ const { modules, hostOptions, ...runtimeOptions } = options ?? {};
12
+ const host = provided
13
+ ? provided
14
+ : create
15
+ ? await create()
16
+ : new CanonicalHost(new PeerbitCanonicalRuntime(runtimeOptions), hostOptions);
17
+ if (modules?.length) {
18
+ host.registerModules(modules);
19
+ }
20
+ hostPromise = Promise.resolve(host);
21
+ return hostPromise;
22
+ };
23
+ const coerceErrorMessage = (error) => {
24
+ if (error instanceof Error)
25
+ return error.message;
26
+ return String(error?.message ?? error);
27
+ };
28
+ const coerceOrigin = (origin) => {
29
+ if (typeof origin !== "string")
30
+ return undefined;
31
+ if (origin.length === 0 || origin === "null")
32
+ return undefined;
33
+ return origin;
34
+ };
35
+ export const installWindowHost = (options) => {
36
+ const channel = options?.channel ?? "peerbit-canonical";
37
+ const defaultTargetOrigin = options?.targetOrigin ?? "*";
38
+ const connections = new Map();
39
+ const connect = (childWindow, connectOptions) => {
40
+ if (connections.has(childWindow))
41
+ return;
42
+ const targetOrigin = connectOptions?.origin ?? defaultTargetOrigin;
43
+ const transport = createWindowTransport(childWindow, {
44
+ channel,
45
+ source: childWindow,
46
+ targetOrigin,
47
+ });
48
+ getHost(options)
49
+ .then((host) => {
50
+ const detach = host.attachControlTransport(transport);
51
+ connections.set(childWindow, detach);
52
+ })
53
+ .catch((error) => {
54
+ options?.onError?.(error);
55
+ });
56
+ };
57
+ const disconnect = (childWindow) => {
58
+ const detach = connections.get(childWindow);
59
+ if (!detach)
60
+ return;
61
+ connections.delete(childWindow);
62
+ detach();
63
+ };
64
+ const onMessage = (event) => {
65
+ if (options?.allow && !options.allow(event)) {
66
+ return;
67
+ }
68
+ const data = event.data;
69
+ const isConnect = data?.op === CANONICAL_WINDOW_CONNECT_OP ||
70
+ data?.__peerbit_canonical_window_connect__ === true;
71
+ if (!isConnect)
72
+ return;
73
+ if (data?.channel && String(data.channel) !== channel)
74
+ return;
75
+ const source = event.source;
76
+ if (!source || typeof source.postMessage !== "function") {
77
+ return;
78
+ }
79
+ const requestId = data?.requestId != null ? String(data.requestId) : undefined;
80
+ const origin = coerceOrigin(event.origin) ?? defaultTargetOrigin;
81
+ connect(source, { origin });
82
+ getHost(options)
83
+ .then(() => {
84
+ try {
85
+ source.postMessage({
86
+ [CANONICAL_WINDOW_READY_KEY]: true,
87
+ channel,
88
+ requestId,
89
+ }, origin === "*" ? "*" : origin);
90
+ }
91
+ catch { }
92
+ })
93
+ .catch((error) => {
94
+ options?.onError?.(error);
95
+ const message = coerceErrorMessage(error);
96
+ try {
97
+ source.postMessage({
98
+ [CANONICAL_WINDOW_READY_KEY]: false,
99
+ channel,
100
+ requestId,
101
+ error: message,
102
+ }, origin === "*" ? "*" : origin);
103
+ }
104
+ catch { }
105
+ });
106
+ };
107
+ globalThis.addEventListener("message", onMessage);
108
+ const close = () => {
109
+ globalThis.removeEventListener("message", onMessage);
110
+ for (const detach of connections.values()) {
111
+ detach();
112
+ }
113
+ connections.clear();
114
+ };
115
+ return { connect, disconnect, close };
116
+ };
117
+ //# sourceMappingURL=window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window.js","sourceRoot":"","sources":["../../src/window.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,98 @@
1
+ {
2
+ "name": "@peerbit/canonical-host",
3
+ "version": "0.0.0-e209d2e",
4
+ "description": "Peerbit canonical runtime host implementation (worker/server)",
5
+ "author": "dao.xyz",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "type": "module",
9
+ "types": "./dist/src/index.d.ts",
10
+ "typesVersions": {
11
+ "*": {
12
+ "*": [
13
+ "*",
14
+ "dist/*",
15
+ "dist/src/*",
16
+ "dist/src/*/index"
17
+ ],
18
+ "src/*": [
19
+ "*",
20
+ "dist/*",
21
+ "dist/src/*",
22
+ "dist/src/*/index"
23
+ ]
24
+ }
25
+ },
26
+ "files": [
27
+ "src",
28
+ "dist",
29
+ "!dist/test",
30
+ "!**/*.tsbuildinfo"
31
+ ],
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/src/index.d.ts",
35
+ "import": "./dist/src/index.js"
36
+ },
37
+ "./shared-worker": {
38
+ "types": "./dist/src/shared-worker.d.ts",
39
+ "import": "./dist/src/shared-worker.js"
40
+ },
41
+ "./service-worker": {
42
+ "types": "./dist/src/service-worker.d.ts",
43
+ "import": "./dist/src/service-worker.js"
44
+ },
45
+ "./window": {
46
+ "types": "./dist/src/window.d.ts",
47
+ "import": "./dist/src/window.js"
48
+ }
49
+ },
50
+ "eslintConfig": {
51
+ "extends": "peerbit",
52
+ "parserOptions": {
53
+ "project": true,
54
+ "sourceType": "module"
55
+ },
56
+ "ignorePatterns": [
57
+ "!.aegir.js",
58
+ "test/ts-use",
59
+ "*.d.ts"
60
+ ]
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "https://github.com/dao-xyz/peerbit"
68
+ },
69
+ "engines": {
70
+ "node": ">=18"
71
+ },
72
+ "dependencies": {
73
+ "@dao-xyz/borsh": "^6.0.0",
74
+ "@libp2p/peer-id": "^6.0.1",
75
+ "@peerbit/canonical-transport": "0.0.0-e209d2e",
76
+ "@peerbit/crypto": "2.4.1-e209d2e",
77
+ "peerbit": "4.4.14-e209d2e"
78
+ },
79
+ "devDependencies": {
80
+ "@peerbit/canonical-client": "0.0.0-e209d2e"
81
+ },
82
+ "localMaintainers": [
83
+ "dao.xyz"
84
+ ],
85
+ "keywords": [
86
+ "peerbit",
87
+ "rpc",
88
+ "canonical",
89
+ "worker",
90
+ "sharedworker"
91
+ ],
92
+ "scripts": {
93
+ "clean": "aegir clean",
94
+ "build": "aegir build --no-bundle",
95
+ "test": "aegir test --target node",
96
+ "lint": "aegir lint"
97
+ }
98
+ }
package/src/index.ts ADDED
@@ -0,0 +1,394 @@
1
+ import { deserialize, serialize } from "@dao-xyz/borsh";
2
+ import { peerIdFromString } from "@libp2p/peer-id";
3
+ import {
4
+ CanonicalBootstrapRequest,
5
+ CanonicalConnection,
6
+ CanonicalControlRequest,
7
+ CanonicalControlResponse,
8
+ CanonicalLoadProgramRequest,
9
+ CanonicalSignRequest,
10
+ createMessagePortTransport as createControlTransport,
11
+ } from "@peerbit/canonical-transport";
12
+ import type {
13
+ CanonicalChannel,
14
+ CanonicalTransport,
15
+ } from "@peerbit/canonical-transport";
16
+ import { PreHash } from "@peerbit/crypto";
17
+ import { type CreateInstanceOptions, Peerbit } from "peerbit";
18
+
19
+ export type { CanonicalChannel };
20
+ export {
21
+ createRpcTransport as createMessagePortTransport,
22
+ type CanonicalRpcTransport,
23
+ } from "@peerbit/canonical-transport";
24
+
25
+ export type CanonicalRuntimeOptions = {
26
+ directory?: string;
27
+ peerOptions?: CreateInstanceOptions;
28
+ };
29
+
30
+ export type CanonicalContext = {
31
+ peer: () => Promise<Peerbit>;
32
+ peerId: () => Promise<string>;
33
+ startPeer?: () => Promise<void>;
34
+ stopPeer?: () => Promise<void>;
35
+ };
36
+
37
+ export type CanonicalHostOptions = {
38
+ idleTimeoutMs?: number;
39
+ idleCheckIntervalMs?: number;
40
+ };
41
+
42
+ export type CanonicalModule = {
43
+ name: string;
44
+ open: (
45
+ ctx: CanonicalContext,
46
+ channel: CanonicalChannel,
47
+ payload: Uint8Array,
48
+ ) => void | Promise<void>;
49
+ };
50
+
51
+ export class PeerbitCanonicalRuntime implements CanonicalContext {
52
+ private _peer?: Peerbit;
53
+
54
+ constructor(readonly options: CanonicalRuntimeOptions = {}) {}
55
+
56
+ async peer(): Promise<Peerbit> {
57
+ if (this._peer) return this._peer;
58
+ const peerOptions = this.options.peerOptions ?? {};
59
+ const directory = this.options.directory ?? peerOptions.directory;
60
+ this._peer = await Peerbit.create({ ...peerOptions, directory });
61
+ await this._peer.start();
62
+ return this._peer;
63
+ }
64
+
65
+ async startPeer(): Promise<void> {
66
+ const peer = await this.peer();
67
+ await peer.start();
68
+ }
69
+
70
+ async stopPeer(): Promise<void> {
71
+ if (!this._peer) return;
72
+ await this._peer.stop();
73
+ this._peer = undefined;
74
+ }
75
+
76
+ async peerId(): Promise<string> {
77
+ return (await this.peer()).peerId.toString();
78
+ }
79
+ }
80
+
81
+ const asUint8Array = (payload: any): Uint8Array => {
82
+ if (payload instanceof Uint8Array) return payload;
83
+ if (payload instanceof ArrayBuffer) return new Uint8Array(payload);
84
+ throw new Error("Expected Uint8Array payload");
85
+ };
86
+
87
+ export class CanonicalHost {
88
+ private readonly modules = new Map<string, CanonicalModule>();
89
+ private nextChannelId = 1;
90
+
91
+ constructor(
92
+ readonly ctx: CanonicalContext,
93
+ readonly options: CanonicalHostOptions = {},
94
+ ) {}
95
+
96
+ registerModule(module: CanonicalModule): void {
97
+ this.modules.set(module.name, module);
98
+ }
99
+
100
+ registerModules(modules: CanonicalModule[]): void {
101
+ for (const m of modules) this.registerModule(m);
102
+ }
103
+
104
+ attachControlPort(port: MessagePort): () => void {
105
+ return this.attachControlTransport(createControlTransport(port));
106
+ }
107
+
108
+ attachControlTransport(transport: CanonicalTransport): () => void {
109
+ const connection = new CanonicalConnection(transport);
110
+ let lastSeen = Date.now();
111
+ const offActivity = connection.onActivity(() => {
112
+ lastSeen = Date.now();
113
+ });
114
+ const unsubscribe = connection.onControl((frame) => {
115
+ if (frame instanceof CanonicalControlRequest) {
116
+ void this.handleControlRequest(connection, frame);
117
+ }
118
+ });
119
+ let idleTimer: ReturnType<typeof setInterval> | undefined;
120
+ const idleTimeoutMs = this.options.idleTimeoutMs;
121
+ const idleIntervalMs =
122
+ this.options.idleCheckIntervalMs ??
123
+ Math.max(1000, Math.min(10_000, Math.floor((idleTimeoutMs ?? 0) / 2)));
124
+ let closed = false;
125
+
126
+ const close = () => {
127
+ if (closed) return;
128
+ closed = true;
129
+ offActivity();
130
+ unsubscribe();
131
+ if (idleTimer) clearInterval(idleTimer);
132
+ connection.close();
133
+ };
134
+
135
+ if (idleTimeoutMs && idleTimeoutMs > 0) {
136
+ idleTimer = setInterval(() => {
137
+ if (Date.now() - lastSeen > idleTimeoutMs) {
138
+ close();
139
+ }
140
+ }, idleIntervalMs);
141
+ }
142
+
143
+ return close;
144
+ }
145
+
146
+ private sendResponse(
147
+ connection: CanonicalConnection,
148
+ response: CanonicalControlResponse,
149
+ ) {
150
+ connection.sendControl(response);
151
+ }
152
+
153
+ private async handleControlRequest(
154
+ connection: CanonicalConnection,
155
+ request: CanonicalControlRequest,
156
+ ): Promise<void> {
157
+ const id = request.id;
158
+ try {
159
+ if (request.op === "peerId") {
160
+ const peerId = await this.ctx.peerId();
161
+ this.sendResponse(
162
+ connection,
163
+ new CanonicalControlResponse({ id, ok: true, peerId }),
164
+ );
165
+ return;
166
+ }
167
+
168
+ if (request.op === "peerInfo") {
169
+ const peer = await this.ctx.peer();
170
+ const peerId = await this.ctx.peerId();
171
+ const publicKey = peer.identity.publicKey.bytes;
172
+ const strings = peer.getMultiaddrs().map((x) => x.toString());
173
+ this.sendResponse(
174
+ connection,
175
+ new CanonicalControlResponse({
176
+ id,
177
+ ok: true,
178
+ peerId,
179
+ payload: publicKey,
180
+ strings,
181
+ }),
182
+ );
183
+ return;
184
+ }
185
+
186
+ if (request.op === "dial") {
187
+ const address = String(request.name ?? "");
188
+ if (!address) {
189
+ throw new Error("Canonical dial requires request.name to be set");
190
+ }
191
+ const peer = await this.ctx.peer();
192
+ await peer.dial(address);
193
+ this.sendResponse(
194
+ connection,
195
+ new CanonicalControlResponse({ id, ok: true }),
196
+ );
197
+ return;
198
+ }
199
+
200
+ if (request.op === "start") {
201
+ if (typeof (this.ctx as any).startPeer === "function") {
202
+ await (this.ctx as any).startPeer();
203
+ } else {
204
+ const peer = await this.ctx.peer();
205
+ await peer.start();
206
+ }
207
+ this.sendResponse(
208
+ connection,
209
+ new CanonicalControlResponse({ id, ok: true }),
210
+ );
211
+ return;
212
+ }
213
+
214
+ if (request.op === "stop") {
215
+ if (typeof (this.ctx as any).stopPeer === "function") {
216
+ await (this.ctx as any).stopPeer();
217
+ } else {
218
+ const peer = await this.ctx.peer();
219
+ await peer.stop();
220
+ }
221
+ this.sendResponse(
222
+ connection,
223
+ new CanonicalControlResponse({ id, ok: true }),
224
+ );
225
+ return;
226
+ }
227
+
228
+ if (request.op === "bootstrap") {
229
+ let addresses: string[] | undefined;
230
+ if (request.payload) {
231
+ const parsed = deserialize(
232
+ request.payload,
233
+ CanonicalBootstrapRequest,
234
+ ) as CanonicalBootstrapRequest;
235
+ addresses = parsed.addresses?.length ? parsed.addresses : undefined;
236
+ }
237
+ const peer = await this.ctx.peer();
238
+ await peer.bootstrap(addresses);
239
+ this.sendResponse(
240
+ connection,
241
+ new CanonicalControlResponse({ id, ok: true }),
242
+ );
243
+ return;
244
+ }
245
+
246
+ if (request.op === "loadProgram") {
247
+ const address = String(request.name ?? "");
248
+ if (!address) {
249
+ throw new Error(
250
+ "Canonical loadProgram requires request.name to be set",
251
+ );
252
+ }
253
+ let timeoutMs: number | undefined;
254
+ if (request.payload) {
255
+ const parsed = deserialize(
256
+ request.payload,
257
+ CanonicalLoadProgramRequest,
258
+ ) as CanonicalLoadProgramRequest;
259
+ timeoutMs = parsed.timeoutMs;
260
+ }
261
+
262
+ const peer = await this.ctx.peer();
263
+ const bytes = await peer.services.blocks.get(address, {
264
+ remote: { timeout: timeoutMs },
265
+ });
266
+ if (!bytes) {
267
+ this.sendResponse(
268
+ connection,
269
+ new CanonicalControlResponse({
270
+ id,
271
+ ok: false,
272
+ error: "Program not found",
273
+ }),
274
+ );
275
+ return;
276
+ }
277
+
278
+ this.sendResponse(
279
+ connection,
280
+ new CanonicalControlResponse({ id, ok: true, payload: bytes }),
281
+ );
282
+ return;
283
+ }
284
+
285
+ if (request.op === "hangUp") {
286
+ const address = String(request.name ?? "");
287
+ if (!address) {
288
+ throw new Error("Canonical hangUp requires request.name to be set");
289
+ }
290
+ const peer = await this.ctx.peer();
291
+ try {
292
+ await peer.hangUp(address);
293
+ } catch (e) {
294
+ try {
295
+ await peer.hangUp(peerIdFromString(address));
296
+ } catch {
297
+ throw e;
298
+ }
299
+ }
300
+ this.sendResponse(
301
+ connection,
302
+ new CanonicalControlResponse({ id, ok: true }),
303
+ );
304
+ return;
305
+ }
306
+
307
+ if (request.op === "sign") {
308
+ if (!request.payload) {
309
+ throw new Error("Canonical sign requires request.payload to be set");
310
+ }
311
+ const signRequest = deserialize(
312
+ request.payload,
313
+ CanonicalSignRequest,
314
+ ) as CanonicalSignRequest;
315
+ const peer = await this.ctx.peer();
316
+ const prehash =
317
+ signRequest.prehash != null
318
+ ? (signRequest.prehash as PreHash)
319
+ : PreHash.NONE;
320
+ const signature = await peer.identity.sign(signRequest.data, prehash);
321
+ this.sendResponse(
322
+ connection,
323
+ new CanonicalControlResponse({
324
+ id,
325
+ ok: true,
326
+ payload: serialize(signature),
327
+ }),
328
+ );
329
+ return;
330
+ }
331
+
332
+ if (request.op === "ping") {
333
+ this.sendResponse(
334
+ connection,
335
+ new CanonicalControlResponse({ id, ok: true }),
336
+ );
337
+ return;
338
+ }
339
+
340
+ if (request.op === "open") {
341
+ const name = String(request.name ?? "");
342
+ const mod = this.modules.get(name);
343
+ if (!mod) {
344
+ this.sendResponse(
345
+ connection,
346
+ new CanonicalControlResponse({
347
+ id,
348
+ ok: false,
349
+ error: `Unknown module '${name}'`,
350
+ }),
351
+ );
352
+ return;
353
+ }
354
+
355
+ const payload =
356
+ request.payload != null
357
+ ? asUint8Array(request.payload)
358
+ : new Uint8Array();
359
+ const channelId = this.nextChannelId++;
360
+ const channel = connection.createChannel(channelId);
361
+ try {
362
+ await mod.open(this.ctx, channel, payload);
363
+ } catch (e: any) {
364
+ connection.releaseChannel(channelId);
365
+ throw e;
366
+ }
367
+
368
+ this.sendResponse(
369
+ connection,
370
+ new CanonicalControlResponse({ id, ok: true, channelId }),
371
+ );
372
+ return;
373
+ }
374
+
375
+ this.sendResponse(
376
+ connection,
377
+ new CanonicalControlResponse({
378
+ id,
379
+ ok: false,
380
+ error: `Unknown op '${String(request.op)}'`,
381
+ }),
382
+ );
383
+ } catch (e: any) {
384
+ this.sendResponse(
385
+ connection,
386
+ new CanonicalControlResponse({
387
+ id,
388
+ ok: false,
389
+ error: String(e?.message || e),
390
+ }),
391
+ );
392
+ }
393
+ }
394
+ }