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