@nice-code/action 0.6.0 → 0.6.2

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 (22) hide show
  1. package/build/devtools/browser/index.js +267 -164
  2. package/build/devtools/server/index.js +132 -114
  3. package/build/index.js +1116 -46
  4. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Transport.types.d.ts +7 -1
  5. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/TransportWebSocket.types.d.ts +31 -3
  6. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketConnection.d.ts +18 -1
  7. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketTransport.d.ts +12 -1
  8. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionFrameCrypto.d.ts +31 -0
  9. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWireCodec.d.ts +41 -0
  10. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWsHandshake.d.ts +187 -0
  11. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsAdapter.d.ts +20 -0
  12. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsSessionFactory.d.ts +31 -0
  13. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/ws_util.d.ts +9 -0
  14. package/build/types/ActionRuntime/Handler/Server/ActionServerHandler.d.ts +191 -0
  15. package/build/types/devtools/browser/components/OriginChip.d.ts +15 -0
  16. package/build/types/devtools/browser/components/SectionLabel.d.ts +1 -1
  17. package/build/types/devtools/browser/components/utils.d.ts +11 -0
  18. package/build/types/devtools/core/devtools_colors.d.ts +1 -0
  19. package/build/types/devtools/server/index.d.ts +2 -2
  20. package/build/types/index.d.ts +7 -0
  21. package/build/types/utils/decodeActionFrame.d.ts +17 -0
  22. package/package.json +6 -5
package/build/index.js CHANGED
@@ -965,6 +965,7 @@ class ActionRuntime {
965
965
  }
966
966
  async handleActionPayload(action, options) {
967
967
  if (action.type === "request" /* request */) {
968
+ const observers = action.context._domain._collectActionObservers();
968
969
  let handlerForAction;
969
970
  try {
970
971
  handlerForAction = this.getHandlerForActionOrThrow(action, options);
@@ -973,6 +974,7 @@ class ActionRuntime {
973
974
  context: action.context,
974
975
  request: action
975
976
  });
977
+ runningAction2.addUpdateListeners(observers);
976
978
  runningAction2._completeWithResult(action.errorResult(castNiceError(err2)));
977
979
  return runningAction2;
978
980
  }
@@ -980,6 +982,7 @@ class ActionRuntime {
980
982
  ...options,
981
983
  targetLocalRuntime: this
982
984
  });
985
+ runningAction.addUpdateListeners(observers);
983
986
  this._trySetupReturnDispatch(runningAction);
984
987
  return runningAction;
985
988
  }
@@ -1293,6 +1296,9 @@ class ActionDomainBase {
1293
1296
  this._listeners = this._listeners.filter((l) => l !== listener);
1294
1297
  };
1295
1298
  }
1299
+ _getActionObservers() {
1300
+ return this._listeners;
1301
+ }
1296
1302
  }
1297
1303
 
1298
1304
  // src/ActionDefinition/Domain/ActionDomain.ts
@@ -1309,6 +1315,9 @@ class ActionDomain extends ActionDomainBase {
1309
1315
  get rootDomain() {
1310
1316
  return this._rootDomain;
1311
1317
  }
1318
+ _collectActionObservers() {
1319
+ return [...this._rootDomain._getActionObservers(), ...this._getActionObservers()];
1320
+ }
1312
1321
  _registerRuntime(runtime2) {
1313
1322
  this._rootDomain._registerRuntime(runtime2);
1314
1323
  }
@@ -1975,7 +1984,7 @@ class ActionExternalClientHandler extends ActionHandler {
1975
1984
  });
1976
1985
  if (methods.sendReturnData == null)
1977
1986
  return false;
1978
- methods.sendReturnData(payload);
1987
+ methods.sendReturnData(payload, { localClient, externalClient: this.externalClient });
1979
1988
  return true;
1980
1989
  } catch {
1981
1990
  return false;
@@ -2339,6 +2348,606 @@ class HttpTransport extends Transport {
2339
2348
  };
2340
2349
  }
2341
2350
  }
2351
+ // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionFrameCrypto.ts
2352
+ import { pack, unpack } from "msgpackr";
2353
+ var ENCRYPTED_ENVELOPE_LENGTH = 2;
2354
+ function createActionFrameCrypto({
2355
+ link,
2356
+ linkedClientId
2357
+ }) {
2358
+ return {
2359
+ async encryptFrame(frame) {
2360
+ const { nonce, ciphertext } = await link.encryptBytesForLinkedClient({
2361
+ linkedClientId,
2362
+ dataToEncrypt: frame
2363
+ });
2364
+ return pack([nonce, ciphertext]);
2365
+ },
2366
+ async decryptFrame(frame) {
2367
+ if (typeof frame === "string") {
2368
+ throw new Error("[ws-crypto] expected an encrypted binary frame, received text");
2369
+ }
2370
+ const buffer = frame instanceof ArrayBuffer ? new Uint8Array(frame) : frame;
2371
+ const envelope = unpack(buffer);
2372
+ if (!Array.isArray(envelope) || envelope.length !== ENCRYPTED_ENVELOPE_LENGTH) {
2373
+ throw new Error("[ws-crypto] malformed encrypted frame envelope");
2374
+ }
2375
+ const [nonce, ciphertext] = envelope;
2376
+ if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) {
2377
+ throw new Error("[ws-crypto] malformed encrypted frame fields");
2378
+ }
2379
+ return await link.decryptBytesFromLinkedClient({
2380
+ linkedClientId,
2381
+ dataToDecrypt: { nonce, ciphertext }
2382
+ });
2383
+ }
2384
+ };
2385
+ }
2386
+ // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWsHandshake.ts
2387
+ import {
2388
+ createTypedStorage
2389
+ } from "@nice-code/util";
2390
+ import { nanoid as nanoid5 } from "nanoid";
2391
+ import * as v from "valibot";
2392
+ var HANDSHAKE_PROTOCOL = "nice-ws-hs/1";
2393
+ var ESecurityLevel;
2394
+ ((ESecurityLevel2) => {
2395
+ ESecurityLevel2["none"] = "none";
2396
+ ESecurityLevel2["authenticated"] = "authenticated";
2397
+ ESecurityLevel2["encrypted"] = "encrypted";
2398
+ })(ESecurityLevel ||= {});
2399
+ var EHandshakeMessageType;
2400
+ ((EHandshakeMessageType2) => {
2401
+ EHandshakeMessageType2["hello"] = "hello";
2402
+ EHandshakeMessageType2["welcome"] = "welcome";
2403
+ EHandshakeMessageType2["prove"] = "prove";
2404
+ EHandshakeMessageType2["accept"] = "accept";
2405
+ EHandshakeMessageType2["reject"] = "reject";
2406
+ })(EHandshakeMessageType ||= {});
2407
+ var vEd25519Raw = v.custom((val) => typeof val === "string" && val.startsWith("ed25519::raw_base64::"));
2408
+ var vX25519Raw = v.custom((val) => typeof val === "string" && val.startsWith("x25519::raw_base64::"));
2409
+ var vCoordinate = v.object({
2410
+ envId: v.string(),
2411
+ perId: v.optional(v.string()),
2412
+ insId: v.optional(v.string())
2413
+ });
2414
+ var vSecurityLevel = v.picklist([
2415
+ "none" /* none */,
2416
+ "authenticated" /* authenticated */,
2417
+ "encrypted" /* encrypted */
2418
+ ]);
2419
+ var vHsHello = v.object({
2420
+ t: v.literal("hello" /* hello */),
2421
+ protocol: v.string(),
2422
+ securityLevel: vSecurityLevel,
2423
+ dictionaryVersion: v.string(),
2424
+ client: vCoordinate,
2425
+ clientNonce: v.string(),
2426
+ verifyPublicKey: vEd25519Raw,
2427
+ exchangePublicKey: v.optional(vX25519Raw)
2428
+ });
2429
+ var vHsWelcome = v.object({
2430
+ t: v.literal("welcome" /* welcome */),
2431
+ securityLevel: vSecurityLevel,
2432
+ dictionaryVersion: v.string(),
2433
+ server: vCoordinate,
2434
+ serverNonce: v.string(),
2435
+ verifyPublicKey: vEd25519Raw,
2436
+ exchangePublicKey: v.optional(vX25519Raw)
2437
+ });
2438
+ var vHsProve = v.object({
2439
+ t: v.literal("prove" /* prove */),
2440
+ signatureBase64: v.string()
2441
+ });
2442
+ var vHsAccept = v.object({
2443
+ t: v.literal("accept" /* accept */),
2444
+ signatureBase64: v.optional(v.string())
2445
+ });
2446
+ var vHsReject = v.object({
2447
+ t: v.literal("reject" /* reject */),
2448
+ reason: v.string()
2449
+ });
2450
+ var vHandshakeMessage = v.variant("t", [vHsHello, vHsWelcome, vHsProve, vHsAccept, vHsReject]);
2451
+ function encodeHandshakeMessage(message) {
2452
+ return JSON.stringify(message);
2453
+ }
2454
+ function decodeHandshakeMessage(raw) {
2455
+ let parsed;
2456
+ try {
2457
+ parsed = JSON.parse(raw);
2458
+ } catch {
2459
+ return;
2460
+ }
2461
+ const result = v.safeParse(vHandshakeMessage, parsed);
2462
+ return result.success ? result.output : undefined;
2463
+ }
2464
+ function runtimeLinkId(coordinate) {
2465
+ return `runtime::${new RuntimeCoordinate(coordinate).stringId}`;
2466
+ }
2467
+ function coordId(coordinate) {
2468
+ return new RuntimeCoordinate(coordinate).stringId;
2469
+ }
2470
+ function sessionSalt(clientNonce, serverNonce) {
2471
+ return `${clientNonce}::${serverNonce}`;
2472
+ }
2473
+ function handshakeInfo(dictionaryVersion) {
2474
+ return `${HANDSHAKE_PROTOCOL}::${dictionaryVersion}`;
2475
+ }
2476
+ function buildHandshakeChallenge(parts) {
2477
+ return JSON.stringify([
2478
+ HANDSHAKE_PROTOCOL,
2479
+ parts.securityLevel,
2480
+ parts.dictionaryVersion,
2481
+ parts.clientCoordId,
2482
+ parts.serverCoordId,
2483
+ parts.clientNonce,
2484
+ parts.serverNonce,
2485
+ parts.clientVerifyKey,
2486
+ parts.serverVerifyKey,
2487
+ parts.clientExchangeKey ?? "_",
2488
+ parts.serverExchangeKey ?? "_"
2489
+ ]);
2490
+ }
2491
+ function reject(reason) {
2492
+ return { t: "reject" /* reject */, reason };
2493
+ }
2494
+ function tofuPinKey(client) {
2495
+ return `${client.envId}::${client.perId ?? client.insId ?? "_"}`;
2496
+ }
2497
+ function createInMemoryTofuVerifyKeyResolver() {
2498
+ const pinned = new Map;
2499
+ return {
2500
+ async resolve({ client, verifyPublicKey }) {
2501
+ const key = tofuPinKey(client);
2502
+ const existing = pinned.get(key);
2503
+ if (existing == null) {
2504
+ pinned.set(key, verifyPublicKey);
2505
+ return { trusted: true };
2506
+ }
2507
+ if (existing === verifyPublicKey)
2508
+ return { trusted: true };
2509
+ return { trusted: false, reason: "verify key changed for client identity (pin mismatch)" };
2510
+ }
2511
+ };
2512
+ }
2513
+ function createStorageTofuVerifyKeyResolver(storageAdapter) {
2514
+ const storage = createTypedStorage({ storageAdapter });
2515
+ return {
2516
+ async resolve({ client, verifyPublicKey }) {
2517
+ const key = tofuPinKey(client);
2518
+ const existing = (await storage.getJson("pins"))?.[key];
2519
+ if (existing == null) {
2520
+ await storage.updateJsonWithDef("pins", {}, (current) => ({
2521
+ ...current,
2522
+ [key]: verifyPublicKey
2523
+ }));
2524
+ return { trusted: true };
2525
+ }
2526
+ if (existing === verifyPublicKey)
2527
+ return { trusted: true };
2528
+ return { trusted: false, reason: "verify key changed for client identity (pin mismatch)" };
2529
+ }
2530
+ };
2531
+ }
2532
+ function createClientHandshake(config) {
2533
+ const { link, localCoordinate, dictionaryVersion, securityLevel } = config;
2534
+ const wantsEncryption = securityLevel === "encrypted" /* encrypted */;
2535
+ const clientNonce = nanoid5();
2536
+ let pending;
2537
+ return {
2538
+ async createHello() {
2539
+ return {
2540
+ t: "hello" /* hello */,
2541
+ protocol: HANDSHAKE_PROTOCOL,
2542
+ securityLevel,
2543
+ dictionaryVersion,
2544
+ client: localCoordinate,
2545
+ clientNonce,
2546
+ verifyPublicKey: await link.getLocalVerifyPublicKey(),
2547
+ exchangePublicKey: wantsEncryption ? await link.getLocalExchangePublicKey() : undefined
2548
+ };
2549
+ },
2550
+ async onWelcome(welcome) {
2551
+ if (welcome.dictionaryVersion !== dictionaryVersion) {
2552
+ throw new Error("[ws-handshake] server dictionary version mismatch");
2553
+ }
2554
+ if (welcome.securityLevel !== securityLevel) {
2555
+ throw new Error("[ws-handshake] server security level mismatch");
2556
+ }
2557
+ if (wantsEncryption && welcome.exchangePublicKey == null) {
2558
+ throw new Error("[ws-handshake] server did not provide an exchange key for encryption");
2559
+ }
2560
+ const linkedServerId = runtimeLinkId(welcome.server);
2561
+ await link.linkClient({
2562
+ linkedClientId: linkedServerId,
2563
+ verifyPublicKey: welcome.verifyPublicKey,
2564
+ ...wantsEncryption ? {
2565
+ exchangePublicKey: welcome.exchangePublicKey,
2566
+ saltString: sessionSalt(clientNonce, welcome.serverNonce),
2567
+ infoString: handshakeInfo(dictionaryVersion),
2568
+ bindVerifyKeysIntoDerivation: true
2569
+ } : {}
2570
+ });
2571
+ const challenge = buildHandshakeChallenge({
2572
+ securityLevel,
2573
+ dictionaryVersion,
2574
+ clientCoordId: coordId(localCoordinate),
2575
+ serverCoordId: coordId(welcome.server),
2576
+ clientNonce,
2577
+ serverNonce: welcome.serverNonce,
2578
+ clientVerifyKey: await link.getLocalVerifyPublicKey(),
2579
+ serverVerifyKey: welcome.verifyPublicKey,
2580
+ clientExchangeKey: wantsEncryption ? await link.getLocalExchangePublicKey() : undefined,
2581
+ serverExchangeKey: welcome.exchangePublicKey
2582
+ });
2583
+ pending = { linkedServerId, server: welcome.server, challenge };
2584
+ return {
2585
+ t: "prove" /* prove */,
2586
+ signatureBase64: (await link.signChallenge([challenge])).signatureBase64
2587
+ };
2588
+ },
2589
+ async onAccept(accept) {
2590
+ if (pending == null)
2591
+ throw new Error("[ws-handshake] accept before welcome");
2592
+ if (accept.signatureBase64 != null) {
2593
+ const valid = await link.verifyChallengeFromLinkedClient({
2594
+ linkedClientId: pending.linkedServerId,
2595
+ challenge: pending.challenge,
2596
+ signatureBase64: accept.signatureBase64
2597
+ });
2598
+ if (!valid)
2599
+ throw new Error("[ws-handshake] server signature invalid");
2600
+ }
2601
+ return { linkedClientId: pending.linkedServerId, remote: pending.server, securityLevel };
2602
+ }
2603
+ };
2604
+ }
2605
+ function createServerHandshake(config) {
2606
+ const { link, localCoordinate, dictionaryVersion } = config;
2607
+ const allowedLevels = Array.isArray(config.securityLevel) ? config.securityLevel : [config.securityLevel];
2608
+ const verifyKeyResolver = config.verifyKeyResolver ?? createInMemoryTofuVerifyKeyResolver();
2609
+ const serverNonce = nanoid5();
2610
+ let pending;
2611
+ let result;
2612
+ return {
2613
+ async onHello(hello) {
2614
+ if (hello.protocol !== HANDSHAKE_PROTOCOL)
2615
+ return reject("unsupported handshake protocol");
2616
+ if (hello.dictionaryVersion !== dictionaryVersion)
2617
+ return reject("dictionary version mismatch");
2618
+ const negotiatedLevel = hello.securityLevel;
2619
+ if (negotiatedLevel === "none" /* none */ || !allowedLevels.includes(negotiatedLevel)) {
2620
+ return reject("security level not allowed");
2621
+ }
2622
+ const wantsEncryption = negotiatedLevel === "encrypted" /* encrypted */;
2623
+ if (wantsEncryption && hello.exchangePublicKey == null) {
2624
+ return reject("missing exchange key for encryption");
2625
+ }
2626
+ const linkedClientId = runtimeLinkId(hello.client);
2627
+ await link.linkClient({
2628
+ linkedClientId,
2629
+ verifyPublicKey: hello.verifyPublicKey,
2630
+ ...wantsEncryption ? {
2631
+ exchangePublicKey: hello.exchangePublicKey,
2632
+ saltString: sessionSalt(hello.clientNonce, serverNonce),
2633
+ infoString: handshakeInfo(dictionaryVersion),
2634
+ bindVerifyKeysIntoDerivation: true
2635
+ } : {}
2636
+ });
2637
+ const serverVerifyKey = await link.getLocalVerifyPublicKey();
2638
+ const serverExchangeKey = wantsEncryption ? await link.getLocalExchangePublicKey() : undefined;
2639
+ const keyMaterial = wantsEncryption && hello.exchangePublicKey != null ? {
2640
+ verifyPublicKey: hello.verifyPublicKey,
2641
+ exchangePublicKey: hello.exchangePublicKey,
2642
+ saltString: sessionSalt(hello.clientNonce, serverNonce),
2643
+ infoString: handshakeInfo(dictionaryVersion),
2644
+ bindVerifyKeysIntoDerivation: true
2645
+ } : undefined;
2646
+ pending = {
2647
+ client: hello.client,
2648
+ linkedClientId,
2649
+ clientVerifyKey: hello.verifyPublicKey,
2650
+ negotiatedLevel,
2651
+ keyMaterial,
2652
+ challenge: buildHandshakeChallenge({
2653
+ securityLevel: negotiatedLevel,
2654
+ dictionaryVersion,
2655
+ clientCoordId: coordId(hello.client),
2656
+ serverCoordId: coordId(localCoordinate),
2657
+ clientNonce: hello.clientNonce,
2658
+ serverNonce,
2659
+ clientVerifyKey: hello.verifyPublicKey,
2660
+ serverVerifyKey,
2661
+ clientExchangeKey: hello.exchangePublicKey,
2662
+ serverExchangeKey
2663
+ })
2664
+ };
2665
+ return {
2666
+ t: "welcome" /* welcome */,
2667
+ securityLevel: negotiatedLevel,
2668
+ dictionaryVersion,
2669
+ server: localCoordinate,
2670
+ serverNonce,
2671
+ verifyPublicKey: serverVerifyKey,
2672
+ exchangePublicKey: serverExchangeKey
2673
+ };
2674
+ },
2675
+ async onProve(prove) {
2676
+ if (pending == null)
2677
+ return reject("prove before hello");
2678
+ const signatureValid = await link.verifyChallengeFromLinkedClient({
2679
+ linkedClientId: pending.linkedClientId,
2680
+ challenge: pending.challenge,
2681
+ signatureBase64: prove.signatureBase64
2682
+ });
2683
+ if (!signatureValid)
2684
+ return reject("invalid client signature");
2685
+ const trust = await verifyKeyResolver.resolve({
2686
+ client: pending.client,
2687
+ verifyPublicKey: pending.clientVerifyKey
2688
+ });
2689
+ if (!trust.trusted)
2690
+ return reject(trust.reason ?? "client verify key not trusted");
2691
+ result = {
2692
+ linkedClientId: pending.linkedClientId,
2693
+ remote: pending.client,
2694
+ securityLevel: pending.negotiatedLevel,
2695
+ encryptionKeyMaterial: pending.keyMaterial
2696
+ };
2697
+ return {
2698
+ t: "accept" /* accept */,
2699
+ signatureBase64: (await link.signChallenge([pending.challenge])).signatureBase64
2700
+ };
2701
+ },
2702
+ getResult() {
2703
+ return result;
2704
+ }
2705
+ };
2706
+ }
2707
+ // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsAdapter.ts
2708
+ import { pack as pack2, unpack as unpack2 } from "msgpackr";
2709
+
2710
+ // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWireCodec.ts
2711
+ var PayloadTypeToInt = {
2712
+ ["request" /* request */]: 0,
2713
+ ["result" /* result */]: 1,
2714
+ ["progress" /* progress */]: 2
2715
+ };
2716
+ var ReversePayloadType = [
2717
+ "request" /* request */,
2718
+ "result" /* result */,
2719
+ "progress" /* progress */
2720
+ ];
2721
+ function buildActionRouteDictionary(domains) {
2722
+ const routeToInt = new Map;
2723
+ const intToRoute = [];
2724
+ for (const dom of domains) {
2725
+ for (const actionId of Object.keys(dom.actionSchema)) {
2726
+ const routeKey = `${dom.domain}:${actionId}`;
2727
+ if (routeToInt.has(routeKey))
2728
+ continue;
2729
+ routeToInt.set(routeKey, intToRoute.length);
2730
+ intToRoute.push({ domain: dom.domain, id: actionId, allDomains: dom.allDomains });
2731
+ }
2732
+ }
2733
+ return { routeToInt, intToRoute };
2734
+ }
2735
+ function extractWirePayload(json) {
2736
+ if (json.type === "request" /* request */)
2737
+ return json.input;
2738
+ if (json.type === "result" /* result */)
2739
+ return json.result;
2740
+ if (json.type === "progress" /* progress */)
2741
+ return json.progress;
2742
+ return;
2743
+ }
2744
+ function assembleWireJson(routeMeta, payloadType, time, context, payloadData) {
2745
+ const base = {
2746
+ form: "data" /* data */,
2747
+ domain: routeMeta.domain,
2748
+ id: routeMeta.id,
2749
+ allDomains: routeMeta.allDomains,
2750
+ time,
2751
+ context
2752
+ };
2753
+ if (payloadType === "request" /* request */) {
2754
+ return { ...base, type: "request" /* request */, input: payloadData, inputHash: "" };
2755
+ }
2756
+ if (payloadType === "result" /* result */) {
2757
+ return { ...base, type: "result" /* result */, result: payloadData, outputHash: "" };
2758
+ }
2759
+ return { ...base, type: "progress" /* progress */, progress: payloadData };
2760
+ }
2761
+
2762
+ // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsAdapter.ts
2763
+ var ENVELOPE = {
2764
+ route: 0,
2765
+ type: 1,
2766
+ time: 2,
2767
+ cuid: 3,
2768
+ originClient: 4,
2769
+ payload: 5
2770
+ };
2771
+ var ENVELOPE_LENGTH = 6;
2772
+ function createBinaryWsAdapter(domains) {
2773
+ const { routeToInt, intToRoute } = buildActionRouteDictionary(domains);
2774
+ return {
2775
+ outgoing: (input) => {
2776
+ const json = input.action.toJsonObject();
2777
+ const routeKey = `${json.domain}:${json.id}`;
2778
+ const routeInt = routeToInt.get(routeKey);
2779
+ if (routeInt == null) {
2780
+ throw new Error(`[binary-ws] Cannot pack unregistered action route: ${routeKey}`);
2781
+ }
2782
+ const envelope = new Array(ENVELOPE_LENGTH);
2783
+ envelope[ENVELOPE.route] = routeInt;
2784
+ envelope[ENVELOPE.type] = PayloadTypeToInt[json.type];
2785
+ envelope[ENVELOPE.time] = json.time;
2786
+ envelope[ENVELOPE.cuid] = json.context.cuid;
2787
+ envelope[ENVELOPE.originClient] = json.context.originClient;
2788
+ envelope[ENVELOPE.payload] = extractWirePayload(json);
2789
+ return pack2(envelope);
2790
+ },
2791
+ incoming: (frame) => {
2792
+ let buffer;
2793
+ if (frame instanceof ArrayBuffer) {
2794
+ buffer = new Uint8Array(frame);
2795
+ } else if (frame instanceof Uint8Array) {
2796
+ buffer = frame;
2797
+ } else {
2798
+ return;
2799
+ }
2800
+ try {
2801
+ const envelope = unpack2(buffer);
2802
+ if (!Array.isArray(envelope) || envelope.length !== ENVELOPE_LENGTH)
2803
+ return;
2804
+ const routeMeta = intToRoute[envelope[ENVELOPE.route]];
2805
+ const payloadType = ReversePayloadType[envelope[ENVELOPE.type]];
2806
+ if (routeMeta == null || payloadType == null)
2807
+ return;
2808
+ const time = envelope[ENVELOPE.time];
2809
+ const context = {
2810
+ cuid: envelope[ENVELOPE.cuid],
2811
+ timeCreated: time,
2812
+ routing: [],
2813
+ originClient: envelope[ENVELOPE.originClient]
2814
+ };
2815
+ return assembleWireJson(routeMeta, payloadType, time, context, envelope[ENVELOPE.payload]);
2816
+ } catch (e) {
2817
+ console.error("[binary-ws] Failed to unpack binary action frame", e);
2818
+ return;
2819
+ }
2820
+ }
2821
+ };
2822
+ }
2823
+ // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsSessionFactory.ts
2824
+ import { pack as pack3, unpack as unpack3 } from "msgpackr";
2825
+ import { nanoid as nanoid6 } from "nanoid";
2826
+ var ENVELOPE2 = {
2827
+ route: 0,
2828
+ type: 1,
2829
+ corr: 2,
2830
+ time: 3,
2831
+ originClient: 4,
2832
+ payload: 5
2833
+ };
2834
+ var ENVELOPE_LENGTH2 = 6;
2835
+ var DEFAULT_CORRELATION_TTL_MS = 5 * 60000;
2836
+ function isKnownIdentity(coordinate) {
2837
+ return coordinate != null && coordinate.envId !== UNSET_RUNTIME_ENV_ID;
2838
+ }
2839
+ function pruneExpired(map, now, ttlMs) {
2840
+ for (const [key, entry] of map) {
2841
+ if (now - entry.time <= ttlMs)
2842
+ break;
2843
+ map.delete(key);
2844
+ }
2845
+ }
2846
+ function createBinaryWsSessionFactory(domains, options) {
2847
+ const { routeToInt, intToRoute } = buildActionRouteDictionary(domains);
2848
+ const unknownIdentity = RuntimeCoordinate.unknown.toJsonObject();
2849
+ const ttlMs = options?.correlationTtlMs ?? DEFAULT_CORRELATION_TTL_MS;
2850
+ return () => {
2851
+ let outCounter = 0;
2852
+ const corrToCuid = new Map;
2853
+ const cuidToCorr = new Map;
2854
+ let selfIdentity;
2855
+ let peerIdentity;
2856
+ return {
2857
+ outgoing: (input) => {
2858
+ const json = input.action.toJsonObject();
2859
+ const routeKey = `${json.domain}:${json.id}`;
2860
+ const routeInt = routeToInt.get(routeKey);
2861
+ if (routeInt == null) {
2862
+ throw new Error(`[binary-ws] Cannot pack unregistered action route: ${routeKey}`);
2863
+ }
2864
+ const now = Date.now();
2865
+ pruneExpired(corrToCuid, now, ttlMs);
2866
+ pruneExpired(cuidToCorr, now, ttlMs);
2867
+ let corr;
2868
+ let wireIdentity;
2869
+ if (json.type === "request" /* request */) {
2870
+ corr = outCounter++;
2871
+ corrToCuid.set(corr, { value: json.context.cuid, time: now });
2872
+ if (selfIdentity == null && isKnownIdentity(json.context.originClient)) {
2873
+ selfIdentity = json.context.originClient;
2874
+ wireIdentity = json.context.originClient;
2875
+ }
2876
+ } else {
2877
+ corr = cuidToCorr.get(json.context.cuid)?.value ?? -1;
2878
+ if (json.type === "result" /* result */)
2879
+ cuidToCorr.delete(json.context.cuid);
2880
+ }
2881
+ const envelope = new Array(ENVELOPE_LENGTH2);
2882
+ envelope[ENVELOPE2.route] = routeInt;
2883
+ envelope[ENVELOPE2.type] = PayloadTypeToInt[json.type];
2884
+ envelope[ENVELOPE2.corr] = corr;
2885
+ envelope[ENVELOPE2.time] = json.time;
2886
+ envelope[ENVELOPE2.originClient] = wireIdentity;
2887
+ envelope[ENVELOPE2.payload] = extractWirePayload(json);
2888
+ return pack3(envelope);
2889
+ },
2890
+ incoming: (frame) => {
2891
+ let buffer;
2892
+ if (frame instanceof ArrayBuffer) {
2893
+ buffer = new Uint8Array(frame);
2894
+ } else if (frame instanceof Uint8Array) {
2895
+ buffer = frame;
2896
+ } else {
2897
+ return;
2898
+ }
2899
+ try {
2900
+ const envelope = unpack3(buffer);
2901
+ if (!Array.isArray(envelope) || envelope.length !== ENVELOPE_LENGTH2)
2902
+ return;
2903
+ const routeMeta = intToRoute[envelope[ENVELOPE2.route]];
2904
+ const payloadType = ReversePayloadType[envelope[ENVELOPE2.type]];
2905
+ if (routeMeta == null || payloadType == null)
2906
+ return;
2907
+ const now = Date.now();
2908
+ pruneExpired(corrToCuid, now, ttlMs);
2909
+ pruneExpired(cuidToCorr, now, ttlMs);
2910
+ const corr = envelope[ENVELOPE2.corr];
2911
+ const time = envelope[ENVELOPE2.time];
2912
+ const wireIdentity = envelope[ENVELOPE2.originClient];
2913
+ let cuid;
2914
+ let originClient;
2915
+ if (payloadType === "request" /* request */) {
2916
+ cuid = nanoid6();
2917
+ cuidToCorr.set(cuid, { value: corr, time: now });
2918
+ if (isKnownIdentity(wireIdentity))
2919
+ peerIdentity = wireIdentity;
2920
+ originClient = peerIdentity ?? unknownIdentity;
2921
+ } else {
2922
+ cuid = corrToCuid.get(corr)?.value ?? nanoid6();
2923
+ if (payloadType === "result" /* result */)
2924
+ corrToCuid.delete(corr);
2925
+ originClient = selfIdentity ?? unknownIdentity;
2926
+ }
2927
+ const context = { cuid, timeCreated: time, routing: [], originClient };
2928
+ return assembleWireJson(routeMeta, payloadType, time, context, envelope[ENVELOPE2.payload]);
2929
+ } catch (e) {
2930
+ console.error("[binary-ws] Failed to unpack binary action session frame", e);
2931
+ return;
2932
+ }
2933
+ }
2934
+ };
2935
+ };
2936
+ }
2937
+ // src/utils/decodeActionFrame.ts
2938
+ function decodeActionFrame(frame, decoder) {
2939
+ const decoded = decoder?.incoming?.(frame) ?? (typeof frame === "string" ? parseJsonActionFrame(frame) : undefined);
2940
+ return decoded != null && isActionPayload_Any_JsonObject(decoded) ? decoded : undefined;
2941
+ }
2942
+ function parseJsonActionFrame(message) {
2943
+ try {
2944
+ const json = JSON.parse(message);
2945
+ return isActionPayload_Any_JsonObject(json) ? json : undefined;
2946
+ } catch {
2947
+ return;
2948
+ }
2949
+ }
2950
+
2342
2951
  // src/ActionRuntime/Handler/ExternalClient/Transport/helpers/createUnsetTransportResolvers.ts
2343
2952
  var createUnsetTransportResolvers = (type) => ({
2344
2953
  onIncomingActionDataJson: (json) => {
@@ -2347,6 +2956,20 @@ var createUnsetTransportResolvers = (type) => ({
2347
2956
  });
2348
2957
 
2349
2958
  // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/ws_util.ts
2959
+ function sendFrame(ws, data) {
2960
+ if (typeof data === "string" || data instanceof ArrayBuffer) {
2961
+ ws.send(data);
2962
+ return;
2963
+ }
2964
+ ws.send(new Uint8Array(data));
2965
+ }
2966
+ function toFrameBytes(frame) {
2967
+ if (typeof frame === "string")
2968
+ return new TextEncoder().encode(frame);
2969
+ if (frame instanceof ArrayBuffer)
2970
+ return new Uint8Array(frame);
2971
+ return frame;
2972
+ }
2350
2973
  function shortWs(url) {
2351
2974
  try {
2352
2975
  const u = new URL(url);
@@ -2357,6 +2980,8 @@ function shortWs(url) {
2357
2980
  }
2358
2981
 
2359
2982
  // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketConnection.ts
2983
+ var HANDSHAKE_TIMEOUT_MS = 15000;
2984
+
2360
2985
  class WebSocketConnection extends TransportConnection {
2361
2986
  resolvers;
2362
2987
  _abortSet = new Set;
@@ -2375,16 +3000,13 @@ class WebSocketConnection extends TransportConnection {
2375
3000
  }
2376
3001
  if (transportStatusInfo.status === "ready" /* ready */) {
2377
3002
  const ws = transportStatusInfo.readyData.ws;
2378
- if (ws.readyState !== WebSocket.OPEN) {
3003
+ if (ws.readyState !== WebSocket.OPEN || this._isSecure(transportStatusInfo.readyData)) {
3004
+ const readyData = transportStatusInfo.readyData;
2379
3005
  const initialization = async () => {
2380
- await new Promise((resolve, reject) => {
2381
- ws.addEventListener("open", () => resolve(), { once: true });
2382
- ws.addEventListener("error", (event) => reject(event), { once: true });
2383
- ws.addEventListener("close", (event) => reject(new Error(`WebSocket closed before open: code=${event.code}`)), { once: true });
2384
- });
3006
+ await this._awaitOpen(ws);
2385
3007
  return {
2386
3008
  status: "ready" /* ready */,
2387
- readyData: this._finalizeTransportMethods(transportStatusInfo.readyData)
3009
+ readyData: await this._finalize(readyData)
2388
3010
  };
2389
3011
  };
2390
3012
  return {
@@ -2397,15 +3019,10 @@ class WebSocketConnection extends TransportConnection {
2397
3019
  if (transportStatusInfo.status === "initializing" /* initializing */) {
2398
3020
  const promiseForReadyData = transportStatusInfo.initializationPromise.then(async (result) => {
2399
3021
  if (result.status === "ready" /* ready */) {
2400
- const ws = result.readyData.ws;
2401
- await new Promise((resolve, reject) => {
2402
- ws.addEventListener("open", () => resolve(), { once: true });
2403
- ws.addEventListener("error", (event) => reject(event), { once: true });
2404
- ws.addEventListener("close", (event) => reject(new Error(`WebSocket closed before open: code=${event.code}`)), { once: true });
2405
- });
3022
+ await this._awaitOpen(result.readyData.ws);
2406
3023
  return {
2407
3024
  status: "ready" /* ready */,
2408
- readyData: this._finalizeTransportMethods(result.readyData)
3025
+ readyData: await this._finalize(result.readyData)
2409
3026
  };
2410
3027
  }
2411
3028
  return result;
@@ -2432,11 +3049,120 @@ class WebSocketConnection extends TransportConnection {
2432
3049
  summary: `ws ${shortWs(this._liveSocketUrl)}`
2433
3050
  };
2434
3051
  }
3052
+ _isSecure(wsData) {
3053
+ return wsData.secureChannel != null && wsData.secureChannel.securityLevel !== "none" /* none */;
3054
+ }
3055
+ _awaitOpen(ws) {
3056
+ if (ws.readyState === WebSocket.OPEN)
3057
+ return Promise.resolve();
3058
+ return new Promise((resolve, reject2) => {
3059
+ ws.addEventListener("open", () => resolve(), { once: true });
3060
+ ws.addEventListener("error", (event) => reject2(event), { once: true });
3061
+ ws.addEventListener("close", (event) => reject2(new Error(`WebSocket closed before open: code=${event.code}`)), { once: true });
3062
+ });
3063
+ }
3064
+ _finalize(wsData) {
3065
+ return this._isSecure(wsData) ? this._finalizeSecureMethods(wsData) : this._finalizeTransportMethods(wsData);
3066
+ }
2435
3067
  _finalizeTransportMethods(wsData) {
2436
3068
  const ws = wsData.ws;
2437
3069
  const disconnectListeners = [];
2438
- if (ws.url != null && ws.url !== "")
2439
- this._liveSocketUrl = ws.url;
3070
+ this._captureSocketUrl(ws);
3071
+ this._attachLifecycle(ws, disconnectListeners);
3072
+ ws.addEventListener("message", async (event) => {
3073
+ const frame = await this._normalizeFrame(event.data);
3074
+ if (frame !== undefined)
3075
+ await this._handleIncomingActionFrame(frame, wsData, undefined);
3076
+ });
3077
+ return this._buildSendMethods(ws, wsData, undefined, disconnectListeners);
3078
+ }
3079
+ async _finalizeSecureMethods(wsData) {
3080
+ const ws = wsData.ws;
3081
+ const disconnectListeners = [];
3082
+ this._captureSocketUrl(ws);
3083
+ this._attachLifecycle(ws, disconnectListeners);
3084
+ let active = false;
3085
+ let crypto;
3086
+ const handshakeQueue = [];
3087
+ const handshakeWaiters = [];
3088
+ const pendingActionFrames = [];
3089
+ ws.addEventListener("message", async (event) => {
3090
+ const frame = await this._normalizeFrame(event.data);
3091
+ if (frame === undefined)
3092
+ return;
3093
+ if (active) {
3094
+ await this._handleIncomingActionFrame(frame, wsData, crypto);
3095
+ return;
3096
+ }
3097
+ if (typeof frame === "string") {
3098
+ const message = decodeHandshakeMessage(frame);
3099
+ if (message != null) {
3100
+ const waiter = handshakeWaiters.shift();
3101
+ if (waiter != null)
3102
+ waiter(message);
3103
+ else
3104
+ handshakeQueue.push(message);
3105
+ return;
3106
+ }
3107
+ }
3108
+ pendingActionFrames.push(frame);
3109
+ });
3110
+ const nextHandshakeMessage = () => {
3111
+ const queued = handshakeQueue.shift();
3112
+ if (queued != null)
3113
+ return Promise.resolve(queued);
3114
+ return new Promise((resolve, reject2) => {
3115
+ const timeout = setTimeout(() => reject2(new Error("[ws-handshake] timed out waiting for server reply")), HANDSHAKE_TIMEOUT_MS);
3116
+ handshakeWaiters.push((message) => {
3117
+ clearTimeout(timeout);
3118
+ resolve(message);
3119
+ });
3120
+ });
3121
+ };
3122
+ crypto = await this._runClientHandshake(ws, wsData.secureChannel, nextHandshakeMessage);
3123
+ active = true;
3124
+ for (const frame of pendingActionFrames)
3125
+ await this._handleIncomingActionFrame(frame, wsData, crypto);
3126
+ pendingActionFrames.length = 0;
3127
+ return this._buildSendMethods(ws, wsData, crypto, disconnectListeners);
3128
+ }
3129
+ async _runClientHandshake(ws, secure, nextHandshakeMessage) {
3130
+ await secure.link.initialize();
3131
+ const handshake = createClientHandshake({
3132
+ link: secure.link,
3133
+ localCoordinate: secure.localCoordinate,
3134
+ dictionaryVersion: secure.dictionaryVersion,
3135
+ securityLevel: secure.securityLevel
3136
+ });
3137
+ sendFrame(ws, encodeHandshakeMessage(await handshake.createHello()));
3138
+ const welcome = await nextHandshakeMessage();
3139
+ if (welcome.t === "reject" /* reject */) {
3140
+ throw new Error(`[ws-handshake] rejected by server: ${welcome.reason}`);
3141
+ }
3142
+ if (welcome.t !== "welcome" /* welcome */) {
3143
+ throw new Error(`[ws-handshake] expected welcome, got ${welcome.t}`);
3144
+ }
3145
+ sendFrame(ws, encodeHandshakeMessage(await handshake.onWelcome(welcome)));
3146
+ const accept = await nextHandshakeMessage();
3147
+ if (accept.t === "reject" /* reject */) {
3148
+ throw new Error(`[ws-handshake] rejected by server: ${accept.reason}`);
3149
+ }
3150
+ if (accept.t !== "accept" /* accept */) {
3151
+ throw new Error(`[ws-handshake] expected accept, got ${accept.t}`);
3152
+ }
3153
+ const result = await handshake.onAccept(accept);
3154
+ return result.securityLevel === "encrypted" /* encrypted */ ? createActionFrameCrypto({ link: secure.link, linkedClientId: result.linkedClientId }) : undefined;
3155
+ }
3156
+ _buildSendMethods(ws, wsData, crypto, disconnectListeners) {
3157
+ let sendChain = Promise.resolve();
3158
+ const enqueueSend = (frame) => {
3159
+ if (crypto == null) {
3160
+ sendFrame(ws, frame);
3161
+ return;
3162
+ }
3163
+ const bytes = toFrameBytes(frame);
3164
+ sendChain = sendChain.then(() => crypto.encryptFrame(bytes)).then((encrypted) => sendFrame(ws, encrypted)).catch((err4) => console.error("[ws] failed to encrypt/send frame", err4));
3165
+ };
2440
3166
  const sendActionData = (inputs) => {
2441
3167
  const { action, runningAction, timeout } = inputs;
2442
3168
  if (action.type === "request" /* request */) {
@@ -2453,16 +3179,49 @@ class WebSocketConnection extends TransportConnection {
2453
3179
  }
2454
3180
  ]);
2455
3181
  }
2456
- ws.send(wsData.formatMessage?.outgoing(inputs) ?? JSON.stringify(inputs.action.toJsonObject()));
3182
+ enqueueSend(wsData.formatMessage?.outgoing(inputs) ?? JSON.stringify(inputs.action.toJsonObject()));
2457
3183
  };
2458
- ws.addEventListener("message", (event) => {
2459
- if (typeof event.data === "string") {
2460
- const rawJson = wsData.formatMessage?.incoming?.(event.data) ?? this._parseActionMessage(event.data);
2461
- if (rawJson != null && isActionPayload_Any_JsonObject(rawJson)) {
2462
- this.resolvers.onIncomingActionDataJson(rawJson);
2463
- }
3184
+ return {
3185
+ sendActionData,
3186
+ updateRunConfig: wsData.updateRunConfig,
3187
+ addOnDisconnectListener: (cb) => {
3188
+ disconnectListeners.push(cb);
3189
+ },
3190
+ sendReturnData: (payload, clients) => {
3191
+ const formatted = clients != null ? wsData.formatMessage?.outgoing({ action: payload, ...clients }) : undefined;
3192
+ enqueueSend(formatted ?? JSON.stringify(payload.toJsonObject()));
2464
3193
  }
2465
- });
3194
+ };
3195
+ }
3196
+ async _handleIncomingActionFrame(frame, wsData, crypto) {
3197
+ let decoded = frame;
3198
+ if (crypto != null) {
3199
+ try {
3200
+ decoded = await crypto.decryptFrame(frame);
3201
+ } catch (err4) {
3202
+ console.error("[ws] failed to decrypt incoming frame", err4);
3203
+ return;
3204
+ }
3205
+ }
3206
+ const rawJson = decodeActionFrame(decoded, wsData.formatMessage);
3207
+ if (rawJson != null) {
3208
+ this.resolvers.onIncomingActionDataJson(rawJson);
3209
+ }
3210
+ }
3211
+ async _normalizeFrame(data) {
3212
+ if (typeof data === "string" || data instanceof ArrayBuffer || data instanceof Uint8Array) {
3213
+ return data;
3214
+ }
3215
+ if (typeof Blob !== "undefined" && data instanceof Blob) {
3216
+ return await data.arrayBuffer();
3217
+ }
3218
+ return;
3219
+ }
3220
+ _captureSocketUrl(ws) {
3221
+ if (ws.url != null && ws.url !== "")
3222
+ this._liveSocketUrl = ws.url;
3223
+ }
3224
+ _attachLifecycle(ws, disconnectListeners) {
2466
3225
  ws.addEventListener("close", (event) => {
2467
3226
  console.error("WebSocket closed:", event);
2468
3227
  for (const cb of disconnectListeners)
@@ -2477,25 +3236,6 @@ class WebSocketConnection extends TransportConnection {
2477
3236
  originalError: event instanceof Error ? event : undefined
2478
3237
  }));
2479
3238
  });
2480
- return {
2481
- sendActionData,
2482
- updateRunConfig: wsData.updateRunConfig,
2483
- addOnDisconnectListener: (cb) => {
2484
- disconnectListeners.push(cb);
2485
- },
2486
- sendReturnData: (payload) => {
2487
- ws.send(JSON.stringify(payload.toJsonObject()));
2488
- }
2489
- };
2490
- }
2491
- _parseActionMessage(message) {
2492
- let json;
2493
- try {
2494
- json = JSON.parse(message);
2495
- } catch {
2496
- return;
2497
- }
2498
- return isActionPayload_Any_JsonObject(json) ? json : undefined;
2499
3239
  }
2500
3240
  _abortAll(error) {
2501
3241
  const snapshot = [...this._abortSet];
@@ -2529,8 +3269,9 @@ class WebSocketTransport extends Transport {
2529
3269
  status: "ready" /* ready */,
2530
3270
  readyData: {
2531
3271
  ws: options.createWebSocket(input),
2532
- formatMessage: options.formatMessage,
2533
- updateRunConfig: options.updateRunConfig
3272
+ formatMessage: options.createFormatMessage?.() ?? options.formatMessage,
3273
+ updateRunConfig: options.updateRunConfig,
3274
+ secureChannel: options.security
2534
3275
  }
2535
3276
  });
2536
3277
  }
@@ -2550,7 +3291,322 @@ class WebSocketTransport extends Transport {
2550
3291
  };
2551
3292
  }
2552
3293
  }
3294
+ // src/ActionRuntime/Handler/Server/ActionServerHandler.ts
3295
+ class ActionServerHandler extends ActionExternalClientHandler {
3296
+ _formatMessage;
3297
+ _createFormatMessage;
3298
+ _send;
3299
+ _serverTimeout;
3300
+ _onConnectionBound;
3301
+ _incomingListeners = [];
3302
+ _security;
3303
+ _allowedLevels;
3304
+ _noneAllowed;
3305
+ _handshakeMode;
3306
+ _connByClient = new Map;
3307
+ _clientByConn = new Map;
3308
+ _connEncoding = new Map;
3309
+ _codecByConn = new Map;
3310
+ _handshakeByConn = new Map;
3311
+ _cryptoByConn = new Map;
3312
+ _authedConns = new Set;
3313
+ _plainConns = new Set;
3314
+ _inboundChainByConn = new Map;
3315
+ _outboundChainByConn = new Map;
3316
+ constructor(options) {
3317
+ super({ runtimeCoordinate: options.clientEnv, transports: [] });
3318
+ this._formatMessage = options.formatMessage;
3319
+ this._createFormatMessage = options.createFormatMessage;
3320
+ this._send = options.send;
3321
+ this._serverTimeout = options.defaultTimeout ?? DEFAULT_TRANSPORT_TIMEOUT;
3322
+ this._onConnectionBound = options.onConnectionBound;
3323
+ this._security = options.security;
3324
+ this._allowedLevels = options.security == null ? [] : Array.isArray(options.security.securityLevel) ? options.security.securityLevel : [options.security.securityLevel];
3325
+ this._noneAllowed = this._allowedLevels.includes("none" /* none */);
3326
+ this._handshakeMode = this._allowedLevels.some((level) => level !== "none" /* none */);
3327
+ }
3328
+ _codecFor(connection) {
3329
+ if (this._createFormatMessage != null) {
3330
+ let codec = this._codecByConn.get(connection);
3331
+ if (codec == null) {
3332
+ codec = this._createFormatMessage();
3333
+ this._codecByConn.set(connection, codec);
3334
+ }
3335
+ return codec;
3336
+ }
3337
+ if (this._formatMessage != null)
3338
+ return this._formatMessage;
3339
+ throw err_nice_transport.fromId("not_found" /* not_found */, {
3340
+ actionId: "server-handler-codec (provide formatMessage or createFormatMessage)"
3341
+ });
3342
+ }
3343
+ _setIncomingActionDataListener(listener) {
3344
+ this._incomingListeners.push(listener);
3345
+ }
3346
+ receive(connection, frame) {
3347
+ if (this._security == null || !this._handshakeMode) {
3348
+ this._receivePlain(connection, frame);
3349
+ return;
3350
+ }
3351
+ const previous = this._inboundChainByConn.get(connection) ?? Promise.resolve();
3352
+ const next = previous.then(() => this._receiveSecure(connection, frame)).catch((err4) => console.error("[ws-server] failed to process inbound frame", err4));
3353
+ this._inboundChainByConn.set(connection, next);
3354
+ }
3355
+ _receivePlain(connection, frame) {
3356
+ const wire = decodeActionFrame(frame, this._codecFor(connection));
3357
+ if (wire == null)
3358
+ return;
3359
+ const encoding = typeof frame === "string" ? "json" : "binary";
3360
+ this._connEncoding.set(connection, encoding);
3361
+ if (wire.type === "request" /* request */) {
3362
+ this._resolveRequestIdentity(connection, wire, encoding);
3363
+ }
3364
+ for (const listener of this._incomingListeners)
3365
+ listener(wire);
3366
+ }
3367
+ async _receiveSecure(connection, frame) {
3368
+ const security = this._security;
3369
+ if (security == null)
3370
+ return;
3371
+ if (this._plainConns.has(connection)) {
3372
+ this._receivePlain(connection, frame);
3373
+ return;
3374
+ }
3375
+ if (!this._authedConns.has(connection)) {
3376
+ const message = typeof frame === "string" ? decodeHandshakeMessage(frame) : undefined;
3377
+ if (message == null) {
3378
+ if (this._noneAllowed) {
3379
+ this._plainConns.add(connection);
3380
+ this._receivePlain(connection, frame);
3381
+ }
3382
+ return;
3383
+ }
3384
+ await security.link.initialize();
3385
+ let handshake = this._handshakeByConn.get(connection);
3386
+ if (handshake == null) {
3387
+ handshake = createServerHandshake({
3388
+ link: security.link,
3389
+ localCoordinate: security.localCoordinate,
3390
+ dictionaryVersion: security.dictionaryVersion,
3391
+ securityLevel: security.securityLevel,
3392
+ verifyKeyResolver: security.verifyKeyResolver
3393
+ });
3394
+ this._handshakeByConn.set(connection, handshake);
3395
+ }
3396
+ if (message.t === "hello" /* hello */) {
3397
+ this._send(connection, encodeHandshakeMessage(await handshake.onHello(message)));
3398
+ } else if (message.t === "prove" /* prove */) {
3399
+ const reply = await handshake.onProve(message);
3400
+ this._send(connection, encodeHandshakeMessage(reply));
3401
+ const result = handshake.getResult();
3402
+ if (reply.t === "accept" /* accept */ && result != null) {
3403
+ this._completeServerHandshake(connection, result);
3404
+ }
3405
+ }
3406
+ return;
3407
+ }
3408
+ let bytes = frame;
3409
+ const cryptoReady = this._cryptoByConn.get(connection);
3410
+ if (cryptoReady != null) {
3411
+ try {
3412
+ bytes = await (await cryptoReady).decryptFrame(frame);
3413
+ } catch (err4) {
3414
+ console.error("[ws-server] failed to decrypt inbound frame", err4);
3415
+ return;
3416
+ }
3417
+ }
3418
+ const wire = decodeActionFrame(bytes, this._codecFor(connection));
3419
+ if (wire == null)
3420
+ return;
3421
+ if (wire.type === "request" /* request */) {
3422
+ const bound = this._clientByConn.get(connection);
3423
+ if (bound != null)
3424
+ wire.context.originClient = bound.toJsonObject();
3425
+ }
3426
+ for (const listener of this._incomingListeners)
3427
+ listener(wire);
3428
+ }
3429
+ _completeServerHandshake(connection, result) {
3430
+ const clientCoord = new RuntimeCoordinate(result.remote);
3431
+ this._bindConnection(connection, clientCoord);
3432
+ this._connEncoding.set(connection, "binary");
3433
+ this._authedConns.add(connection);
3434
+ this._handshakeByConn.delete(connection);
3435
+ if (result.securityLevel === "encrypted" /* encrypted */ && this._security != null) {
3436
+ this._cryptoByConn.set(connection, Promise.resolve(createActionFrameCrypto({
3437
+ link: this._security.link,
3438
+ linkedClientId: result.linkedClientId
3439
+ })));
3440
+ }
3441
+ this._onConnectionBound?.(connection, {
3442
+ client: clientCoord.toJsonObject(),
3443
+ encoding: "binary",
3444
+ secure: {
3445
+ securityLevel: result.securityLevel,
3446
+ linkedClientId: result.linkedClientId,
3447
+ keyMaterial: result.encryptionKeyMaterial
3448
+ }
3449
+ });
3450
+ }
3451
+ _resolveRequestIdentity(connection, wire, encoding) {
3452
+ const wireOrigin = wire.context.originClient;
3453
+ if (wireOrigin != null && wireOrigin.envId !== UNSET_RUNTIME_ENV_ID) {
3454
+ const clientCoord = new RuntimeCoordinate(wireOrigin);
3455
+ const isNewBinding = this._clientByConn.get(connection)?.stringId !== clientCoord.stringId;
3456
+ this._bindConnection(connection, clientCoord);
3457
+ if (isNewBinding) {
3458
+ this._onConnectionBound?.(connection, { client: clientCoord.toJsonObject(), encoding });
3459
+ }
3460
+ return;
3461
+ }
3462
+ const bound = this._clientByConn.get(connection);
3463
+ if (bound != null)
3464
+ wire.context.originClient = bound.toJsonObject();
3465
+ }
3466
+ rehydrateConnection(connection, binding) {
3467
+ this._bindConnection(connection, new RuntimeCoordinate(binding.client));
3468
+ this._connEncoding.set(connection, binding.encoding);
3469
+ const secure = binding.secure;
3470
+ if (secure == null)
3471
+ return;
3472
+ this._authedConns.add(connection);
3473
+ if (secure.securityLevel === "encrypted" /* encrypted */ && secure.keyMaterial != null && this._security != null) {
3474
+ const security = this._security;
3475
+ const { linkedClientId, keyMaterial } = secure;
3476
+ const cryptoReady = security.link.initialize().then(() => security.link.linkClient({
3477
+ linkedClientId,
3478
+ verifyPublicKey: keyMaterial.verifyPublicKey,
3479
+ exchangePublicKey: keyMaterial.exchangePublicKey,
3480
+ saltString: keyMaterial.saltString,
3481
+ infoString: keyMaterial.infoString,
3482
+ bindVerifyKeysIntoDerivation: keyMaterial.bindVerifyKeysIntoDerivation
3483
+ })).then(() => createActionFrameCrypto({ link: security.link, linkedClientId }));
3484
+ this._cryptoByConn.set(connection, cryptoReady);
3485
+ const gate = cryptoReady.then(() => {}, (err4) => console.error("[ws-server] failed to restore encrypted session", err4));
3486
+ this._inboundChainByConn.set(connection, gate);
3487
+ this._outboundChainByConn.set(connection, gate);
3488
+ }
3489
+ }
3490
+ toHandlerRouteItem() {
3491
+ return {
3492
+ type: this.handlerType,
3493
+ client: this.externalClient,
3494
+ transType: "custom" /* custom */,
3495
+ transOrd: 0
3496
+ };
3497
+ }
3498
+ dropConnection(connection) {
3499
+ const coord = this._clientByConn.get(connection);
3500
+ if (coord != null && this._connByClient.get(coord.stringId) === connection) {
3501
+ this._connByClient.delete(coord.stringId);
3502
+ }
3503
+ this._clientByConn.delete(connection);
3504
+ this._connEncoding.delete(connection);
3505
+ this._codecByConn.delete(connection);
3506
+ this._handshakeByConn.delete(connection);
3507
+ this._cryptoByConn.delete(connection);
3508
+ this._authedConns.delete(connection);
3509
+ this._plainConns.delete(connection);
3510
+ this._inboundChainByConn.delete(connection);
3511
+ this._outboundChainByConn.delete(connection);
3512
+ }
3513
+ getConnectionForClient(client) {
3514
+ return this._connByClient.get(client.stringId);
3515
+ }
3516
+ pushToClient(runtime2, target, request, options) {
3517
+ const connection = this._resolveConnection(target);
3518
+ return this._dispatch(runtime2, connection, request, options?.timeout);
3519
+ }
3520
+ async sendReturnPayload(payload, config) {
3521
+ const connection = this._connByClient.get(payload.context.originClient.stringId);
3522
+ if (connection == null)
3523
+ return false;
3524
+ this._sendPayload(connection, payload, config.targetLocalRuntime.coordinate);
3525
+ return true;
3526
+ }
3527
+ async handleActionRequest(action, config) {
3528
+ const runtime2 = config?.targetLocalRuntime ?? ActionRuntime.getDefault();
3529
+ const connection = this._resolveSingleConnection();
3530
+ return this._dispatch(runtime2, connection, action, config?.timeout);
3531
+ }
3532
+ _dispatch(runtime2, connection, action, timeout) {
3533
+ const timeoutMs = timeout ?? this._serverTimeout;
3534
+ action.context._setOriginClient(runtime2.coordinate);
3535
+ action.context.addRouteItem({
3536
+ runtime: runtime2.coordinate,
3537
+ handler: this.toHandlerRouteItem(),
3538
+ time: Date.now()
3539
+ });
3540
+ const runningAction = new RunningAction({
3541
+ context: action.context,
3542
+ request: action,
3543
+ parentCuid: peekHandlerCuid(),
3544
+ callSite: action._callSite
3545
+ });
3546
+ runtime2.registerRunningAction(runningAction);
3547
+ const timeoutId = setTimeout(() => {
3548
+ runningAction._abort(err_nice_transport.fromId("timeout" /* timeout */, { timeout: timeoutMs }));
3549
+ }, timeoutMs);
3550
+ runningAction.addUpdateListeners([
3551
+ (update) => {
3552
+ if (update.type === "finished" /* finished */)
3553
+ clearTimeout(timeoutId);
3554
+ }
3555
+ ]);
3556
+ try {
3557
+ this._sendPayload(connection, action, runtime2.coordinate);
3558
+ } catch (err4) {
3559
+ runningAction._abort(err4);
3560
+ }
3561
+ return runningAction;
3562
+ }
3563
+ _sendPayload(connection, payload, localClient) {
3564
+ const encoding = this._connEncoding.get(connection) ?? "binary";
3565
+ const frame = encoding === "json" ? JSON.stringify(payload.toJsonObject()) : this._codecFor(connection).outgoing({
3566
+ action: payload,
3567
+ localClient,
3568
+ externalClient: this.externalClient
3569
+ });
3570
+ const cryptoReady = this._cryptoByConn.get(connection);
3571
+ if (cryptoReady == null) {
3572
+ this._send(connection, frame);
3573
+ return;
3574
+ }
3575
+ const bytes = toFrameBytes(frame);
3576
+ const previous = this._outboundChainByConn.get(connection) ?? Promise.resolve();
3577
+ const next = previous.then(() => cryptoReady).then((crypto) => crypto.encryptFrame(bytes)).then((encrypted) => this._send(connection, encrypted)).catch((err4) => console.error("[ws-server] failed to encrypt/send frame", err4));
3578
+ this._outboundChainByConn.set(connection, next);
3579
+ }
3580
+ _bindConnection(connection, client) {
3581
+ this._connByClient.set(client.stringId, connection);
3582
+ this._clientByConn.set(connection, client);
3583
+ }
3584
+ _resolveConnection(target) {
3585
+ if (target instanceof RuntimeCoordinate) {
3586
+ const connection = this._connByClient.get(target.stringId);
3587
+ if (connection == null) {
3588
+ throw err_nice_transport.fromId("not_found" /* not_found */, {
3589
+ actionId: target.stringId
3590
+ });
3591
+ }
3592
+ return connection;
3593
+ }
3594
+ return target;
3595
+ }
3596
+ _resolveSingleConnection() {
3597
+ if (this._clientByConn.size !== 1) {
3598
+ throw err_nice_transport.fromId("not_found" /* not_found */, {
3599
+ actionId: "server-handler-target (use pushToClient with an explicit connection or client coordinate)"
3600
+ });
3601
+ }
3602
+ return this._clientByConn.keys().next().value;
3603
+ }
3604
+ }
3605
+ var createServerHandler = (options) => {
3606
+ return new ActionServerHandler(options);
3607
+ };
2553
3608
  export {
3609
+ runtimeLinkId,
2554
3610
  isActionPayload_Result_JsonObject,
2555
3611
  isActionPayload_Request_JsonObject,
2556
3612
  isActionPayload_Any_JsonObject,
@@ -2558,9 +3614,20 @@ export {
2558
3614
  err_nice_transport,
2559
3615
  err_nice_external_client,
2560
3616
  err_nice_action,
3617
+ encodeHandshakeMessage,
3618
+ decodeHandshakeMessage,
3619
+ decodeActionFrame,
3620
+ createStorageTofuVerifyKeyResolver,
3621
+ createServerHandshake,
3622
+ createServerHandler,
2561
3623
  createLocalHandler,
3624
+ createInMemoryTofuVerifyKeyResolver,
2562
3625
  createExternalClientHandler,
3626
+ createClientHandshake,
3627
+ createBinaryWsSessionFactory,
3628
+ createBinaryWsAdapter,
2563
3629
  createActionRootDomain,
3630
+ createActionFrameCrypto,
2564
3631
  actionSchema,
2565
3632
  WebSocketTransport,
2566
3633
  Transport,
@@ -2569,15 +3636,18 @@ export {
2569
3636
  HttpTransport,
2570
3637
  ETransportType,
2571
3638
  ETransportStatus,
3639
+ ESecurityLevel,
2572
3640
  ERunningActionUpdateType,
2573
3641
  ERunningActionState,
2574
3642
  ERunningActionFinishedType,
3643
+ EHandshakeMessageType,
2575
3644
  EErrId_NiceTransport_WebSocket,
2576
3645
  EErrId_NiceTransport,
2577
3646
  EErrId_NiceAction,
2578
3647
  EActionProgressType,
2579
3648
  EActionPayloadType,
2580
3649
  CustomTransport,
3650
+ ActionServerHandler,
2581
3651
  ActionSchema,
2582
3652
  ActionRuntime,
2583
3653
  ActionRootDomain,