space-data-module-sdk 0.5.22 → 0.6.1

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.
Files changed (65) hide show
  1. package/README.md +22 -2
  2. package/package.json +2 -2
  3. package/schemas/orbpro/BaseTypes.fbs +7 -0
  4. package/schemas/orbpro/CatalogQueryBase.fbs +8 -0
  5. package/schemas/orbpro/CatalogQueryRequest.fbs +14 -0
  6. package/schemas/orbpro/CatalogQueryResult.fbs +17 -0
  7. package/schemas/orbpro/EntityMetadata.fbs +53 -0
  8. package/schemas/orbpro/EntityStandardsLink.fbs +18 -0
  9. package/schemas/orbpro/Plugin.fbs +8 -0
  10. package/schemas/orbpro/Propagator.fbs +89 -0
  11. package/schemas/orbpro/StandardsRecordIndex.fbs +15 -0
  12. package/src/generated/orbpro/entity/entity-kind.js +15 -0
  13. package/src/generated/orbpro/entity/entity-kind.ts +15 -0
  14. package/src/generated/orbpro/entity/entity-metadata.js +322 -0
  15. package/src/generated/orbpro/entity/entity-metadata.ts +426 -0
  16. package/src/generated/orbpro/entity/entity-standards-link.js +143 -0
  17. package/src/generated/orbpro/entity/entity-standards-link.ts +183 -0
  18. package/src/generated/orbpro/entity/standards-record-index.js +115 -0
  19. package/src/generated/orbpro/entity/standards-record-index.ts +147 -0
  20. package/src/generated/orbpro/entity.js +10 -0
  21. package/src/generated/orbpro/entity.ts +5 -0
  22. package/src/generated/orbpro/plugin/raw-data-payload.js +79 -0
  23. package/src/generated/orbpro/plugin/raw-data-payload.ts +92 -0
  24. package/src/generated/orbpro/plugin.js +4 -0
  25. package/src/generated/orbpro/plugin.ts +5 -0
  26. package/src/generated/orbpro/propagator/propagator-describe-sources-batch-request.js +79 -0
  27. package/src/generated/orbpro/propagator/propagator-describe-sources-batch-request.ts +95 -0
  28. package/src/generated/orbpro/propagator/propagator-describe-sources-batch-result.js +70 -0
  29. package/src/generated/orbpro/propagator/propagator-describe-sources-batch-result.ts +78 -0
  30. package/src/generated/orbpro/propagator/propagator-sample-trajectory-states-request.js +97 -0
  31. package/src/generated/orbpro/propagator/propagator-sample-trajectory-states-request.ts +117 -0
  32. package/src/generated/orbpro/propagator/propagator-sample-trajectory-states-result.js +140 -0
  33. package/src/generated/orbpro/propagator/propagator-sample-trajectory-states-result.ts +175 -0
  34. package/src/generated/orbpro/propagator/propagator-source-description.js +208 -0
  35. package/src/generated/orbpro/propagator/propagator-source-description.ts +257 -0
  36. package/src/generated/orbpro/propagator/propagator-source-kind.js +8 -0
  37. package/src/generated/orbpro/propagator/propagator-source-kind.ts +8 -0
  38. package/src/generated/orbpro/propagator/reference-frame.js +12 -0
  39. package/src/generated/orbpro/propagator/reference-frame.ts +12 -0
  40. package/src/generated/orbpro/propagator/state-flags.js +13 -0
  41. package/src/generated/orbpro/propagator/state-flags.ts +13 -0
  42. package/src/generated/orbpro/propagator/state-vector.js +55 -0
  43. package/src/generated/orbpro/propagator/state-vector.ts +61 -0
  44. package/src/generated/orbpro/propagator.js +20 -0
  45. package/src/generated/orbpro/propagator.ts +13 -0
  46. package/src/generated/orbpro/query/catalog-query-kind.js +10 -0
  47. package/src/generated/orbpro/query/catalog-query-kind.ts +10 -0
  48. package/src/generated/orbpro/query/catalog-query-request.js +89 -0
  49. package/src/generated/orbpro/query/catalog-query-request.ts +105 -0
  50. package/src/generated/orbpro/query/catalog-query-result.js +145 -0
  51. package/src/generated/orbpro/query/catalog-query-result.ts +179 -0
  52. package/src/generated/orbpro/query.js +8 -0
  53. package/src/generated/orbpro/query.ts +4 -0
  54. package/src/generated/orbpro/vec3.js +39 -0
  55. package/src/generated/orbpro/vec3.ts +40 -0
  56. package/src/host/abi.js +189 -3
  57. package/src/host/browserEdgeShims.js +17 -0
  58. package/src/host/browserHost.js +486 -40
  59. package/src/host/isomorphicLoader.js +44 -20
  60. package/src/host/nodeHost.js +337 -63
  61. package/src/index.d.ts +99 -3
  62. package/src/runtime-host/index.js +90 -0
  63. package/src/testing/browserModuleHarness.js +22 -3
  64. package/src/testing/index.d.ts +1 -0
  65. package/src/testing/index.js +39 -1
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { RuntimeTarget } from "../runtime/constants.js";
9
+ import { toUint8Array } from "../utils/encoding.js";
9
10
  import {
10
11
  parseCronExpression,
11
12
  matchesCronExpression,
@@ -20,12 +21,21 @@ export const BrowserHostSupportedCapabilities = Object.freeze([
20
21
  "schedule_cron",
21
22
  "http",
22
23
  "websocket",
24
+ "network",
23
25
  "filesystem",
26
+ "ipfs",
27
+ "protocol_handle",
28
+ "protocol_dial",
24
29
  "context_read",
25
30
  "context_write",
26
31
  "crypto_hash",
32
+ "crypto_sign",
33
+ "crypto_verify",
27
34
  "crypto_encrypt",
28
35
  "crypto_decrypt",
36
+ "crypto_key_agreement",
37
+ "crypto_kdf",
38
+ "wallet_sign",
29
39
  "logging",
30
40
  ]);
31
41
 
@@ -39,11 +49,13 @@ export const BrowserHostSupportedOperations = Object.freeze([
39
49
  "clock.monotonicNow",
40
50
  "clock.nowIso",
41
51
  "random.bytes",
52
+ "timers.delay",
42
53
  "schedule.parse",
43
54
  "schedule.matches",
44
55
  "schedule.next",
45
56
  "http.request",
46
57
  "websocket.exchange",
58
+ "network.request",
47
59
  "filesystem.resolvePath",
48
60
  "filesystem.readFile",
49
61
  "filesystem.writeFile",
@@ -53,6 +65,14 @@ export const BrowserHostSupportedOperations = Object.freeze([
53
65
  "filesystem.readdir",
54
66
  "filesystem.stat",
55
67
  "filesystem.rename",
68
+ "ipfs.invoke",
69
+ "ipfs.add",
70
+ "ipfs.cat",
71
+ "protocol_handle.register",
72
+ "protocol_handle.unregister",
73
+ "protocol_dial.dial",
74
+ "protocol.request",
75
+ "keyslot.get",
56
76
  "context.get",
57
77
  "context.set",
58
78
  "context.delete",
@@ -60,8 +80,15 @@ export const BrowserHostSupportedOperations = Object.freeze([
60
80
  "context.listScopes",
61
81
  "crypto.sha256",
62
82
  "crypto.sha512",
83
+ "crypto.hkdf",
63
84
  "crypto.aesGcmEncrypt",
64
85
  "crypto.aesGcmDecrypt",
86
+ "crypto.x25519.generateKeypair",
87
+ "crypto.x25519.publicKey",
88
+ "crypto.x25519.sharedSecret",
89
+ "crypto.ed25519.publicKeyFromSeed",
90
+ "crypto.ed25519.sign",
91
+ "crypto.ed25519.verify",
65
92
  ]);
66
93
 
67
94
  export class BrowserHostCapabilityError extends Error {
@@ -73,9 +100,85 @@ export class BrowserHostCapabilityError extends Error {
73
100
  }
74
101
  }
75
102
 
103
+ function normalizeBrowserNetworkTransport(params = {}) {
104
+ const explicit = String(
105
+ params.transport ?? params.kind ?? params.request?.transport ?? "",
106
+ )
107
+ .trim()
108
+ .toLowerCase();
109
+ if (explicit) {
110
+ return explicit;
111
+ }
112
+ const candidateUrl = params.url ?? params.request?.url ?? null;
113
+ if (candidateUrl) {
114
+ const protocol = new URL(candidateUrl, "https://browser-host.invalid")
115
+ .protocol.toLowerCase();
116
+ if (protocol === "http:" || protocol === "https:") {
117
+ return "http";
118
+ }
119
+ if (protocol === "ws:" || protocol === "wss:") {
120
+ return "websocket";
121
+ }
122
+ }
123
+ throw new Error(
124
+ 'network.request requires a transport value such as "http" or "websocket".',
125
+ );
126
+ }
127
+
128
+ function resolveCapabilityAdapters(options = {}) {
129
+ const adapters =
130
+ options.capabilityAdapters && typeof options.capabilityAdapters === "object"
131
+ ? options.capabilityAdapters
132
+ : {};
133
+ return {
134
+ filesystem: options.filesystem ?? adapters.filesystem ?? null,
135
+ network: options.network ?? adapters.network ?? null,
136
+ ipfs: options.ipfs ?? adapters.ipfs ?? null,
137
+ walletSign:
138
+ options.walletSign ??
139
+ adapters.walletSign ??
140
+ adapters.wallet_sign ??
141
+ adapters.keyslot ??
142
+ null,
143
+ protocolHandle:
144
+ options.protocolHandle ??
145
+ adapters.protocolHandle ??
146
+ adapters.protocol_handle ??
147
+ null,
148
+ protocolDial:
149
+ options.protocolDial ??
150
+ adapters.protocolDial ??
151
+ adapters.protocol_dial ??
152
+ null,
153
+ };
154
+ }
155
+
156
+ function normalizeCryptoBytes(value) {
157
+ if (typeof value === "string") {
158
+ return new TextEncoder().encode(value);
159
+ }
160
+ return toUint8Array(value);
161
+ }
162
+
163
+ async function invokeAdapterMethod(adapter, methodName, params, label) {
164
+ if (!adapter || typeof adapter !== "object") {
165
+ throw new Error(`${label} adapter is not configured for this host.`);
166
+ }
167
+ if (typeof adapter[methodName] === "function") {
168
+ return adapter[methodName](params);
169
+ }
170
+ if (typeof adapter.invoke === "function") {
171
+ return adapter.invoke(methodName, params);
172
+ }
173
+ throw new Error(
174
+ `${label} adapter does not implement "${methodName}" or invoke().`,
175
+ );
176
+ }
177
+
76
178
  export class BrowserHost {
77
179
  constructor(options = {}) {
78
180
  this.runtimeTarget = RuntimeTarget.BROWSER;
181
+ const capabilityAdapters = resolveCapabilityAdapters(options);
79
182
 
80
183
  const granted = options.capabilities
81
184
  ? new Set(options.capabilities)
@@ -86,7 +189,15 @@ export class BrowserHost {
86
189
  WebSocket: options.WebSocket ?? options.edgeShims?.WebSocket,
87
190
  crypto: options.crypto ?? options.edgeShims?.crypto,
88
191
  performance: options.performance ?? options.edgeShims?.performance,
89
- filesystem: options.filesystem ?? options.edgeShims?.filesystem,
192
+ capabilityAdapters,
193
+ network: capabilityAdapters.network ?? options.edgeShims?.network,
194
+ ipfs: capabilityAdapters.ipfs ?? options.edgeShims?.ipfs,
195
+ protocolHandle:
196
+ capabilityAdapters.protocolHandle ?? options.edgeShims?.protocolHandle,
197
+ protocolDial:
198
+ capabilityAdapters.protocolDial ?? options.edgeShims?.protocolDial,
199
+ filesystem:
200
+ capabilityAdapters.filesystem ?? options.edgeShims?.filesystem,
90
201
  filesystemRoot: options.filesystemRoot ?? options.edgeShims?.filesystemRoot,
91
202
  });
92
203
  const performanceApi = edgeShims.performance ?? {
@@ -94,9 +205,15 @@ export class BrowserHost {
94
205
  timeOrigin: 0,
95
206
  };
96
207
  const cryptoApi = edgeShims.crypto;
208
+ const wasmWallet = options.wasmWallet ?? null;
97
209
  const fetchImpl = edgeShims.fetch;
98
210
  const WebSocketImpl = edgeShims.WebSocket;
99
211
  const filesystem = edgeShims.filesystem;
212
+ const networkAdapter = edgeShims.network;
213
+ const ipfsAdapter = edgeShims.ipfs;
214
+ const walletSignAdapter = capabilityAdapters.walletSign;
215
+ const protocolHandleAdapter = edgeShims.protocolHandle;
216
+ const protocolDialAdapter = edgeShims.protocolDial;
100
217
 
101
218
  this._grantedCapabilities = granted;
102
219
  this._contextStore = options.contextStore ?? new Map();
@@ -239,6 +356,97 @@ export class BrowserHost {
239
356
  },
240
357
  });
241
358
 
359
+ this.network = Object.freeze({
360
+ request: async (params = {}) => {
361
+ this.#assertCapability("network", "network.request");
362
+ const transport = normalizeBrowserNetworkTransport(params);
363
+ const request = params.request ?? params;
364
+ if (networkAdapter) {
365
+ return invokeAdapterMethod(networkAdapter, "request", {
366
+ ...request,
367
+ transport,
368
+ }, "network");
369
+ }
370
+ if (transport === "http") {
371
+ return this.http.request(request);
372
+ }
373
+ if (transport === "websocket") {
374
+ return this.websocket.exchange(request);
375
+ }
376
+ throw new Error(
377
+ `Browser host does not support network transport "${transport}".`,
378
+ );
379
+ },
380
+ });
381
+
382
+ this.ipfs = Object.freeze({
383
+ invoke: async (params = {}) => {
384
+ this.#assertCapability("ipfs", "ipfs.invoke");
385
+ const operation = String(params.operation ?? "invoke").trim();
386
+ if (!operation) {
387
+ throw new Error("ipfs.invoke requires a non-empty operation.");
388
+ }
389
+ return invokeAdapterMethod(ipfsAdapter, operation, params, "ipfs");
390
+ },
391
+ add: async (params = {}) => {
392
+ this.#assertCapability("ipfs", "ipfs.add");
393
+ return invokeAdapterMethod(ipfsAdapter, "add", params, "ipfs");
394
+ },
395
+ cat: async (params = {}) => {
396
+ this.#assertCapability("ipfs", "ipfs.cat");
397
+ return invokeAdapterMethod(ipfsAdapter, "cat", params, "ipfs");
398
+ },
399
+ });
400
+
401
+ this.protocolHandle = Object.freeze({
402
+ register: async (params = {}) => {
403
+ this.#assertCapability("protocol_handle", "protocol_handle.register");
404
+ return invokeAdapterMethod(
405
+ protocolHandleAdapter,
406
+ "register",
407
+ params,
408
+ "protocol_handle",
409
+ );
410
+ },
411
+ unregister: async (params = {}) => {
412
+ this.#assertCapability("protocol_handle", "protocol_handle.unregister");
413
+ return invokeAdapterMethod(
414
+ protocolHandleAdapter,
415
+ "unregister",
416
+ params,
417
+ "protocol_handle",
418
+ );
419
+ },
420
+ });
421
+
422
+ this.protocolDial = Object.freeze({
423
+ dial: async (params = {}) => {
424
+ this.#assertCapability("protocol_dial", "protocol_dial.dial");
425
+ return invokeAdapterMethod(
426
+ protocolDialAdapter,
427
+ "dial",
428
+ params,
429
+ "protocol_dial",
430
+ );
431
+ },
432
+ request: async (params = {}) => {
433
+ this.#assertCapability("protocol_dial", "protocol.request");
434
+ return invokeAdapterMethod(
435
+ protocolDialAdapter,
436
+ "request",
437
+ params,
438
+ "protocol_dial",
439
+ );
440
+ },
441
+ });
442
+
443
+ this.keyslot = Object.freeze({
444
+ get: async (params = {}) => {
445
+ this.#assertCapability("wallet_sign", "keyslot.get");
446
+ return invokeAdapterMethod(walletSignAdapter, "get", params, "wallet_sign");
447
+ },
448
+ });
449
+
242
450
  this.context = Object.freeze({
243
451
  get: (scope, key) => {
244
452
  this.#assertCapability("context_read", "context.get");
@@ -267,60 +475,161 @@ export class BrowserHost {
267
475
  });
268
476
 
269
477
  this.crypto = Object.freeze({
270
- sha256: async (data) => {
478
+ sha256: (data) => {
271
479
  this.#assertCapability("crypto_hash", "crypto.sha256");
272
- if (!cryptoApi?.subtle) {
273
- throw new Error("No Web Crypto subtle implementation is available.");
480
+ if (!wasmWallet?.utils?.sha256) {
481
+ throw new Error("Browser host crypto.sha256 requires a preloaded wasmWallet.");
274
482
  }
275
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
276
- return new Uint8Array(await cryptoApi.subtle.digest("SHA-256", bytes));
483
+ return new Uint8Array(wasmWallet.utils.sha256(normalizeCryptoBytes(data)));
277
484
  },
278
- sha512: async (data) => {
485
+ sha512: (data) => {
279
486
  this.#assertCapability("crypto_hash", "crypto.sha512");
280
- if (!cryptoApi?.subtle) {
281
- throw new Error("No Web Crypto subtle implementation is available.");
487
+ if (!wasmWallet?.utils?.sha512) {
488
+ throw new Error("Browser host crypto.sha512 requires a preloaded wasmWallet.");
282
489
  }
283
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
284
- return new Uint8Array(await cryptoApi.subtle.digest("SHA-512", bytes));
490
+ return new Uint8Array(wasmWallet.utils.sha512(normalizeCryptoBytes(data)));
285
491
  },
286
- aesGcmEncrypt: async (params) => {
287
- this.#assertCapability("crypto_encrypt", "crypto.aesGcmEncrypt");
288
- if (!cryptoApi?.subtle || !cryptoApi?.getRandomValues) {
289
- throw new Error("No Web Crypto implementation is available.");
492
+ hkdf: (params = {}) => {
493
+ this.#assertCapability("crypto_kdf", "crypto.hkdf");
494
+ if (!wasmWallet?.utils?.hkdf) {
495
+ throw new Error("Browser host crypto.hkdf requires a preloaded wasmWallet.");
290
496
  }
291
- const key = await cryptoApi.subtle.importKey(
292
- "raw",
293
- params.key,
294
- { name: "AES-GCM" },
295
- false,
296
- ["encrypt"],
497
+ return new Uint8Array(
498
+ wasmWallet.utils.hkdf(
499
+ toUint8Array(params.ikm),
500
+ toUint8Array(params.salt),
501
+ params.info === undefined || params.info === null
502
+ ? new Uint8Array()
503
+ : toUint8Array(params.info),
504
+ Number(params.length ?? 0),
505
+ ),
297
506
  );
298
- const iv = params.iv ?? cryptoApi.getRandomValues(new Uint8Array(12));
299
- const ciphertext = new Uint8Array(
300
- await cryptoApi.subtle.encrypt({ name: "AES-GCM", iv }, key, params.plaintext),
507
+ },
508
+ aesGcmEncrypt: (params = {}) => {
509
+ this.#assertCapability("crypto_encrypt", "crypto.aesGcmEncrypt");
510
+ if (!wasmWallet?.utils?.aesGcm?.encrypt) {
511
+ throw new Error(
512
+ "Browser host crypto.aesGcmEncrypt requires a preloaded wasmWallet.",
513
+ );
514
+ }
515
+ const iv =
516
+ params.iv === undefined || params.iv === null
517
+ ? new Uint8Array(wasmWallet.utils.getRandomBytes(12))
518
+ : toUint8Array(params.iv);
519
+ const result = wasmWallet.utils.aesGcm.encrypt(
520
+ toUint8Array(params.key),
521
+ toUint8Array(params.plaintext),
522
+ iv,
523
+ params.aad === undefined || params.aad === null
524
+ ? undefined
525
+ : toUint8Array(params.aad),
301
526
  );
302
- return { ciphertext, iv };
527
+ return {
528
+ ciphertext: new Uint8Array(result.ciphertext),
529
+ tag: new Uint8Array(result.tag),
530
+ iv,
531
+ };
303
532
  },
304
- aesGcmDecrypt: async (params) => {
533
+ aesGcmDecrypt: (params = {}) => {
305
534
  this.#assertCapability("crypto_decrypt", "crypto.aesGcmDecrypt");
306
- if (!cryptoApi?.subtle) {
307
- throw new Error("No Web Crypto subtle implementation is available.");
535
+ if (!wasmWallet?.utils?.aesGcm?.decrypt) {
536
+ throw new Error(
537
+ "Browser host crypto.aesGcmDecrypt requires a preloaded wasmWallet.",
538
+ );
308
539
  }
309
- const key = await cryptoApi.subtle.importKey(
310
- "raw",
311
- params.key,
312
- { name: "AES-GCM" },
313
- false,
314
- ["decrypt"],
540
+ return new Uint8Array(
541
+ wasmWallet.utils.aesGcm.decrypt(
542
+ toUint8Array(params.key),
543
+ toUint8Array(params.ciphertext),
544
+ toUint8Array(params.tag),
545
+ toUint8Array(params.iv),
546
+ params.aad === undefined || params.aad === null
547
+ ? undefined
548
+ : toUint8Array(params.aad),
549
+ ),
550
+ );
551
+ },
552
+ generateX25519Keypair: () => {
553
+ this.#assertCapability(
554
+ "crypto_key_agreement",
555
+ "crypto.x25519.generateKeypair",
315
556
  );
557
+ if (!wasmWallet?.curves?.x25519 || !wasmWallet?.utils?.getRandomBytes) {
558
+ throw new Error(
559
+ "Browser host crypto.x25519.generateKeypair requires a preloaded wasmWallet.",
560
+ );
561
+ }
562
+ const privateKey = new Uint8Array(wasmWallet.utils.getRandomBytes(32));
563
+ const publicKey = new Uint8Array(
564
+ wasmWallet.curves.x25519.publicKey(privateKey),
565
+ );
566
+ return { privateKey, publicKey };
567
+ },
568
+ x25519PublicKey: (privateKey) => {
569
+ this.#assertCapability("crypto_key_agreement", "crypto.x25519.publicKey");
570
+ if (!wasmWallet?.curves?.x25519?.publicKey) {
571
+ throw new Error(
572
+ "Browser host crypto.x25519.publicKey requires a preloaded wasmWallet.",
573
+ );
574
+ }
316
575
  return new Uint8Array(
317
- await cryptoApi.subtle.decrypt(
318
- { name: "AES-GCM", iv: params.iv },
319
- key,
320
- params.ciphertext,
576
+ wasmWallet.curves.x25519.publicKey(toUint8Array(privateKey)),
577
+ );
578
+ },
579
+ x25519SharedSecret: (privateKey, publicKey) => {
580
+ this.#assertCapability("crypto_key_agreement", "crypto.x25519.sharedSecret");
581
+ if (!wasmWallet?.curves?.x25519?.ecdh) {
582
+ throw new Error(
583
+ "Browser host crypto.x25519.sharedSecret requires a preloaded wasmWallet.",
584
+ );
585
+ }
586
+ return new Uint8Array(
587
+ wasmWallet.curves.x25519.ecdh(
588
+ toUint8Array(privateKey),
589
+ toUint8Array(publicKey),
321
590
  ),
322
591
  );
323
592
  },
593
+ ed25519: Object.freeze({
594
+ publicKeyFromSeed: (seed) => {
595
+ this.#assertCapability("crypto_sign", "crypto.ed25519.publicKeyFromSeed");
596
+ if (!wasmWallet?.curves?.ed25519?.publicKeyFromSeed) {
597
+ throw new Error(
598
+ "Browser host crypto.ed25519.publicKeyFromSeed requires a preloaded wasmWallet.",
599
+ );
600
+ }
601
+ return new Uint8Array(
602
+ wasmWallet.curves.ed25519.publicKeyFromSeed(toUint8Array(seed)),
603
+ );
604
+ },
605
+ sign: (message, seed) => {
606
+ this.#assertCapability("crypto_sign", "crypto.ed25519.sign");
607
+ if (!wasmWallet?.curves?.ed25519?.sign) {
608
+ throw new Error(
609
+ "Browser host crypto.ed25519.sign requires a preloaded wasmWallet.",
610
+ );
611
+ }
612
+ return new Uint8Array(
613
+ wasmWallet.curves.ed25519.sign(
614
+ toUint8Array(message),
615
+ toUint8Array(seed),
616
+ ),
617
+ );
618
+ },
619
+ verify: (message, signature, publicKey) => {
620
+ this.#assertCapability("crypto_verify", "crypto.ed25519.verify");
621
+ if (!wasmWallet?.curves?.ed25519?.verify) {
622
+ throw new Error(
623
+ "Browser host crypto.ed25519.verify requires a preloaded wasmWallet.",
624
+ );
625
+ }
626
+ return wasmWallet.curves.ed25519.verify(
627
+ toUint8Array(message),
628
+ toUint8Array(signature),
629
+ toUint8Array(publicKey),
630
+ );
631
+ },
632
+ }),
324
633
  });
325
634
 
326
635
  this.filesystem = Object.freeze({
@@ -378,17 +687,154 @@ export class BrowserHost {
378
687
  }
379
688
 
380
689
  hasCapability(capability) {
381
- return this._grantedCapabilities.has(capability);
690
+ const normalized = String(capability ?? "").trim();
691
+ return (
692
+ this._grantedCapabilities.has(normalized) ||
693
+ (this._grantedCapabilities.has("network") &&
694
+ ["http", "websocket"].includes(normalized))
695
+ );
382
696
  }
383
697
 
384
698
  assertCapability(capability, operation) {
385
699
  this.#assertCapability(capability, operation);
386
700
  }
387
701
 
702
+ async invoke(operation, params = {}) {
703
+ const normalized = String(operation ?? "").trim();
704
+ switch (normalized) {
705
+ case "host.runtimeTarget":
706
+ return this.runtimeTarget;
707
+ case "host.listCapabilities":
708
+ return this.listCapabilities();
709
+ case "host.listSupportedCapabilities":
710
+ return this.listSupportedCapabilities();
711
+ case "host.listOperations":
712
+ return this.listOperations();
713
+ case "host.hasCapability":
714
+ return this.hasCapability(params.capability);
715
+ case "clock.now":
716
+ return this.clock.now();
717
+ case "clock.monotonicNow":
718
+ return this.clock.monotonicNow();
719
+ case "clock.nowIso":
720
+ return this.clock.nowIso();
721
+ case "random.bytes":
722
+ return this.random.bytes(params.length);
723
+ case "timers.delay":
724
+ return this.timers.delay(params.ms ?? params.delayMs ?? 0);
725
+ case "schedule.parse":
726
+ return this.schedule.parse(params.expression);
727
+ case "schedule.matches":
728
+ return this.schedule.matches(params.expression, params.date);
729
+ case "schedule.next":
730
+ return this.schedule.next(params.expression, params.from);
731
+ case "http.request":
732
+ return this.http.request(params);
733
+ case "websocket.exchange":
734
+ return this.websocket.exchange(params);
735
+ case "network.request":
736
+ return this.network.request(params);
737
+ case "filesystem.resolvePath":
738
+ return this.filesystem.resolvePath(params.path);
739
+ case "filesystem.readFile":
740
+ return this.filesystem.readFile(params.path, {
741
+ encoding: params.encoding,
742
+ });
743
+ case "filesystem.writeFile":
744
+ return this.filesystem.writeFile(params.path, params.value, {
745
+ encoding: params.encoding,
746
+ });
747
+ case "filesystem.appendFile":
748
+ return this.filesystem.appendFile(params.path, params.value, {
749
+ encoding: params.encoding,
750
+ });
751
+ case "filesystem.deleteFile":
752
+ return this.filesystem.deleteFile(params.path);
753
+ case "filesystem.mkdir":
754
+ return this.filesystem.mkdir(params.path, {
755
+ recursive: params.recursive,
756
+ });
757
+ case "filesystem.readdir":
758
+ return this.filesystem.readdir(params.path);
759
+ case "filesystem.stat":
760
+ return this.filesystem.stat(params.path);
761
+ case "filesystem.rename":
762
+ return this.filesystem.rename(params.fromPath, params.toPath);
763
+ case "ipfs.invoke":
764
+ return this.ipfs.invoke(params);
765
+ case "ipfs.add":
766
+ return this.ipfs.add(params);
767
+ case "ipfs.cat":
768
+ return this.ipfs.cat(params);
769
+ case "protocol_handle.register":
770
+ return this.protocolHandle.register(params);
771
+ case "protocol_handle.unregister":
772
+ return this.protocolHandle.unregister(params);
773
+ case "protocol_dial.dial":
774
+ return this.protocolDial.dial(params);
775
+ case "protocol.request":
776
+ return this.protocolDial.request(params);
777
+ case "keyslot.get":
778
+ return this.keyslot.get(params);
779
+ case "context.get":
780
+ return this.context.get(params.scope, params.key);
781
+ case "context.set":
782
+ return this.context.set(params.scope, params.key, params.value);
783
+ case "context.delete":
784
+ return this.context.delete(params.scope, params.key);
785
+ case "context.listKeys":
786
+ return this.context.listKeys(params.scope);
787
+ case "context.listScopes":
788
+ return this.context.listScopes();
789
+ case "crypto.sha256":
790
+ return this.crypto.sha256(params.value ?? params.bytes);
791
+ case "crypto.sha512":
792
+ return this.crypto.sha512(params.value ?? params.bytes);
793
+ case "crypto.hkdf":
794
+ return this.crypto.hkdf(params);
795
+ case "crypto.aesGcmEncrypt":
796
+ return this.crypto.aesGcmEncrypt(params);
797
+ case "crypto.aesGcmDecrypt":
798
+ return this.crypto.aesGcmDecrypt(params);
799
+ case "crypto.x25519.generateKeypair":
800
+ return this.crypto.generateX25519Keypair();
801
+ case "crypto.x25519.publicKey":
802
+ return this.crypto.x25519PublicKey(params.privateKey);
803
+ case "crypto.x25519.sharedSecret":
804
+ return this.crypto.x25519SharedSecret(
805
+ params.privateKey,
806
+ params.publicKey,
807
+ );
808
+ case "crypto.ed25519.publicKeyFromSeed":
809
+ return this.crypto.ed25519.publicKeyFromSeed(params.seed);
810
+ case "crypto.ed25519.sign":
811
+ return this.crypto.ed25519.sign(params.message, params.seed);
812
+ case "crypto.ed25519.verify":
813
+ return this.crypto.ed25519.verify(
814
+ params.message,
815
+ params.signature,
816
+ params.publicKey,
817
+ );
818
+ default:
819
+ throw new Error(`Unknown browser host operation "${normalized}".`);
820
+ }
821
+ }
822
+
388
823
  // --- Private ---
389
824
 
390
825
  #assertCapability(capability, operation) {
391
- if (!this._grantedCapabilities.has(capability)) {
826
+ const normalized = String(capability ?? "").trim();
827
+ const networkBackedCapabilities = new Set([
828
+ "http",
829
+ "websocket",
830
+ ]);
831
+ if (
832
+ !this._grantedCapabilities.has(normalized) &&
833
+ !(
834
+ this._grantedCapabilities.has("network") &&
835
+ networkBackedCapabilities.has(normalized)
836
+ )
837
+ ) {
392
838
  throw new BrowserHostCapabilityError(capability, operation);
393
839
  }
394
840
  }