@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.
- package/build/devtools/browser/index.js +45 -9
- package/build/devtools/server/index.js +132 -114
- package/build/index.js +1107 -46
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Transport.types.d.ts +7 -1
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/TransportWebSocket.types.d.ts +31 -3
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketConnection.d.ts +18 -1
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketTransport.d.ts +12 -1
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionFrameCrypto.d.ts +31 -0
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWireCodec.d.ts +41 -0
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWsHandshake.d.ts +187 -0
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsAdapter.d.ts +20 -0
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsSessionFactory.d.ts +31 -0
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/ws_util.d.ts +9 -0
- package/build/types/ActionRuntime/Handler/Server/ActionServerHandler.d.ts +191 -0
- package/build/types/devtools/browser/components/SectionLabel.d.ts +1 -1
- package/build/types/devtools/server/index.d.ts +2 -2
- package/build/types/index.d.ts +7 -0
- package/build/types/utils/decodeActionFrame.d.ts +17 -0
- 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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
2439
|
-
|
|
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
|
-
|
|
3173
|
+
enqueueSend(wsData.formatMessage?.outgoing(inputs) ?? JSON.stringify(inputs.action.toJsonObject()));
|
|
2457
3174
|
};
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
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,
|