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