@nice-code/action 0.5.5 → 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/README.md +35 -0
- 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
|
@@ -83,7 +83,13 @@ export interface ITransportDispatchAction<P> extends ITransportMethod_SendAction
|
|
|
83
83
|
params: P;
|
|
84
84
|
}
|
|
85
85
|
export type TSendActionDataMethod = (input: ITransportMethod_SendActionData_Input) => void;
|
|
86
|
-
export type TSendReturnDataMethod = (payload: TActionPayload_Any_Instance<any, any
|
|
86
|
+
export type TSendReturnDataMethod = (payload: TActionPayload_Any_Instance<any, any>,
|
|
87
|
+
/**
|
|
88
|
+
* The local/external client pair this payload is being returned over. Bidirectional transports use
|
|
89
|
+
* it to build the full route params their outgoing formatter expects (e.g. binary packing). Optional
|
|
90
|
+
* so existing return-data implementations keep type-checking.
|
|
91
|
+
*/
|
|
92
|
+
clients?: ITransportRouteClientParams) => void;
|
|
87
93
|
export interface IActionTransportReadyData_Methods extends IActionTransportReadyData_Base {
|
|
88
94
|
sendActionData: TSendActionDataMethod;
|
|
89
95
|
/**
|
|
@@ -1,11 +1,39 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClientCryptoKeyLink } from "@nice-code/util";
|
|
2
|
+
import type { TActionPayload_Any_JsonObject } from "../../../../../ActionDefinition/Action/Payload/ActionPayload.types";
|
|
3
|
+
import type { IRuntimeCoordinate } from "../../../../RuntimeCoordinate";
|
|
2
4
|
import type { ETransportType, IActionTransportDef, IActionTransportInitialized, IActionTransportReadyData_Base, ITransportRouteActionParams } from "../Transport.types";
|
|
5
|
+
import type { ESecurityLevel } from "./actionWsHandshake";
|
|
6
|
+
/**
|
|
7
|
+
* Client-side secure-channel config for a WebSocket. When present (and `securityLevel !== none`), the
|
|
8
|
+
* connection runs the {@link createClientHandshake} handshake during initialization — authenticating
|
|
9
|
+
* the identity and, for the `encrypted` level, deriving a shared key that encrypts every action frame.
|
|
10
|
+
*/
|
|
11
|
+
export interface IWsClientSecureChannel {
|
|
12
|
+
securityLevel: ESecurityLevel;
|
|
13
|
+
/** This client's crypto identity (verify + exchange key pairs, optionally persisted). */
|
|
14
|
+
link: ClientCryptoKeyLink;
|
|
15
|
+
/** This client's runtime coordinate — its authenticated identity to the server. */
|
|
16
|
+
localCoordinate: IRuntimeCoordinate;
|
|
17
|
+
/** Wire dictionary version; the server rejects the handshake on a mismatch. */
|
|
18
|
+
dictionaryVersion: string;
|
|
19
|
+
}
|
|
3
20
|
export interface IActionTransportReadyData_Ws extends IActionTransportReadyData_Base {
|
|
4
21
|
formatMessage?: {
|
|
5
|
-
|
|
6
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Pack an outgoing action payload. Return a `string` for text frames (JSON) or a binary
|
|
24
|
+
* `Uint8Array`/`ArrayBuffer` for optimized binary frames (e.g. msgpackr).
|
|
25
|
+
*/
|
|
26
|
+
outgoing: (input: ITransportRouteActionParams) => string | Uint8Array | ArrayBuffer;
|
|
27
|
+
/**
|
|
28
|
+
* Unpack an incoming frame back into the wire JSON object the runtime hydrates + validates.
|
|
29
|
+
* Return `undefined` to defer to the connection's built-in JSON parser — this is how binary
|
|
30
|
+
* adapters stay backward compatible with plain-JSON clients on the same socket.
|
|
31
|
+
*/
|
|
32
|
+
incoming?: (input: string | ArrayBuffer | Uint8Array | Blob) => TActionPayload_Any_JsonObject<any, any> | undefined;
|
|
7
33
|
};
|
|
8
34
|
ws: WebSocket;
|
|
35
|
+
/** Optional authenticated/encrypted channel; the connection runs the handshake during init. */
|
|
36
|
+
secureChannel?: IWsClientSecureChannel;
|
|
9
37
|
}
|
|
10
38
|
export interface IActionTransportInitialized_Ws extends IActionTransportInitialized<ITransportRouteActionParams, IActionTransportReadyData_Ws> {
|
|
11
39
|
}
|
|
@@ -10,7 +10,24 @@ export declare class WebSocketConnection extends TransportConnection<ETransportT
|
|
|
10
10
|
protected _getCacheKey(_input: ITransportRouteActionParams): string;
|
|
11
11
|
protected _processTransportStatus(input: ITransportRouteActionParams): TTransportStatusInfo<IActionTransportReadyData_Methods>;
|
|
12
12
|
getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo | undefined;
|
|
13
|
+
private _isSecure;
|
|
14
|
+
private _awaitOpen;
|
|
15
|
+
/** Non-secure connections finalize synchronously; secure ones run the handshake first. */
|
|
16
|
+
private _finalize;
|
|
13
17
|
_finalizeTransportMethods(wsData: IActionTransportReadyData_Ws): IActionTransportReadyData_Methods;
|
|
14
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Secure path: a single message listener feeds the handshake until it completes, then routes action
|
|
20
|
+
* frames (decrypting for the `encrypted` level). Frames that arrive in the gap between accept and
|
|
21
|
+
* activation are buffered and flushed, so nothing is lost.
|
|
22
|
+
*/
|
|
23
|
+
private _finalizeSecureMethods;
|
|
24
|
+
private _runClientHandshake;
|
|
25
|
+
private _buildSendMethods;
|
|
26
|
+
/** Decode (and, when encrypted, decrypt) one inbound action frame and hand it to the runtime. */
|
|
27
|
+
private _handleIncomingActionFrame;
|
|
28
|
+
/** Accept text + binary frames (ArrayBuffer / Uint8Array / Blob); Blobs are converted to a buffer. */
|
|
29
|
+
private _normalizeFrame;
|
|
30
|
+
private _captureSocketUrl;
|
|
31
|
+
private _attachLifecycle;
|
|
15
32
|
private _abortAll;
|
|
16
33
|
}
|
package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketTransport.d.ts
CHANGED
|
@@ -3,8 +3,14 @@ import { ETransportType, type ITransportRouteActionParams, type ITransportRouteI
|
|
|
3
3
|
import type { IActionTransportInitialized_Ws, IActionTransportReadyData_Ws } from "./TransportWebSocket.types";
|
|
4
4
|
import { WebSocketConnection } from "./WebSocketConnection";
|
|
5
5
|
interface IWebSocketTransportSharedOptions {
|
|
6
|
-
/** Custom (de)serialization of action payloads on the wire. */
|
|
6
|
+
/** Custom (de)serialization of action payloads on the wire (shared across all sockets). */
|
|
7
7
|
formatMessage?: IActionTransportReadyData_Ws["formatMessage"];
|
|
8
|
+
/**
|
|
9
|
+
* Per-socket codec factory — called once for each socket so stateful codecs (e.g. the
|
|
10
|
+
* `createBinaryWsSessionFactory` session, which holds per-connection correlation + identity state)
|
|
11
|
+
* get their own instance and reset cleanly on reconnect. Takes precedence over `formatMessage`.
|
|
12
|
+
*/
|
|
13
|
+
createFormatMessage?: () => IActionTransportReadyData_Ws["formatMessage"];
|
|
8
14
|
updateRunConfig?: TUpdateActionRunConfig;
|
|
9
15
|
/**
|
|
10
16
|
* Keys that identify a reusable socket, so a single socket is shared across actions to the same
|
|
@@ -13,6 +19,11 @@ interface IWebSocketTransportSharedOptions {
|
|
|
13
19
|
getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
|
|
14
20
|
/** Override the devtools route info for a specific action. */
|
|
15
21
|
getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
|
|
22
|
+
/**
|
|
23
|
+
* Secure-channel config. When set (and `securityLevel !== none`), the connection runs the
|
|
24
|
+
* authenticated handshake during initialization and, at the `encrypted` level, encrypts every frame.
|
|
25
|
+
*/
|
|
26
|
+
security?: IActionTransportReadyData_Ws["secureChannel"];
|
|
16
27
|
}
|
|
17
28
|
export interface IWebSocketTransportSocketOptions extends IWebSocketTransportSharedOptions {
|
|
18
29
|
/** Open (or reuse) the WebSocket for an action — keep it simple or derive it per action. */
|
package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionFrameCrypto.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ClientCryptoKeyLink, TTypeAndId } from "@nice-code/util";
|
|
2
|
+
/**
|
|
3
|
+
* Async AES-GCM transform for the `encrypted` security level. It wraps the opaque binary frame a
|
|
4
|
+
* session codec produces (it does NOT look inside it), encrypting on the way out and decrypting on the
|
|
5
|
+
* way in with the shared key established by the handshake.
|
|
6
|
+
*
|
|
7
|
+
* It is deliberately separate from the (synchronous) session `formatMessage`: WebCrypto is always
|
|
8
|
+
* Promise-based, so encryption has to happen at the transport's async I/O boundary — the connection
|
|
9
|
+
* encrypts after `session.outgoing()` and decrypts before `session.incoming()`. The `authenticated`
|
|
10
|
+
* and `none` levels use no crypto transform at all (frames go out as the session produced them).
|
|
11
|
+
*
|
|
12
|
+
* Wire shape of an encrypted frame: `pack([nonceBytes, ciphertextBytes])` — msgpack carries the two
|
|
13
|
+
* binary fields with a couple of bytes of overhead, no base64 inflation.
|
|
14
|
+
*/
|
|
15
|
+
export interface IActionFrameCrypto {
|
|
16
|
+
/** Encrypt one session frame for sending. */
|
|
17
|
+
encryptFrame(frame: Uint8Array): Promise<Uint8Array>;
|
|
18
|
+
/** Decrypt one received frame back to the session frame. Throws on a non-binary / malformed /
|
|
19
|
+
* tampered frame — the caller (transport) decides how to react (drop / close). */
|
|
20
|
+
decryptFrame(frame: string | ArrayBuffer | Uint8Array): Promise<Uint8Array>;
|
|
21
|
+
}
|
|
22
|
+
export interface IActionFrameCryptoConfig {
|
|
23
|
+
link: ClientCryptoKeyLink;
|
|
24
|
+
/** The handshake-established link id for the remote (key + connection-registry id). */
|
|
25
|
+
linkedClientId: TTypeAndId;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
|
|
29
|
+
* level. Keyed by the link + `linkedClientId`, so it reuses the cached shared AES-GCM key.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createActionFrameCrypto({ link, linkedClientId, }: IActionFrameCryptoConfig): IActionFrameCrypto;
|
package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWireCodec.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { IActionContext_Data_JsonObject } from "../../../../../ActionDefinition/Action/Context/ActionContext.types";
|
|
2
|
+
import { EActionPayloadType, type TActionPayload_Any_JsonObject } from "../../../../../ActionDefinition/Action/Payload/ActionPayload.types";
|
|
3
|
+
import type { ActionDomain } from "../../../../../ActionDefinition/Domain/ActionDomain";
|
|
4
|
+
import type { TPossibleDomainIdList } from "../../../../../ActionDefinition/Domain/ActionDomain.types";
|
|
5
|
+
/**
|
|
6
|
+
* Shared building blocks for the binary action codecs (the stateless {@link createBinaryWsAdapter} and
|
|
7
|
+
* the per-connection `createBinaryWsSessionFactory`). Both map a `domain:id` route to a tiny integer
|
|
8
|
+
* and reduce the verbose JSON wire to a positional tuple — they only differ in how much context they
|
|
9
|
+
* carry per frame, so the dictionary + payload (de)assembly live here.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Tiny integer codes for the payload type, so the verbose `"request"`/`"result"`/`"progress"`
|
|
13
|
+
* strings never hit the wire. The index in {@link ReversePayloadType} must line up with the value.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PayloadTypeToInt: Record<EActionPayloadType.request | EActionPayloadType.result | EActionPayloadType.progress, number>;
|
|
16
|
+
export declare const ReversePayloadType: readonly [EActionPayloadType.request, EActionPayloadType.result, EActionPayloadType.progress];
|
|
17
|
+
export interface IActionRouteMeta {
|
|
18
|
+
domain: string;
|
|
19
|
+
id: string;
|
|
20
|
+
allDomains: TPossibleDomainIdList;
|
|
21
|
+
}
|
|
22
|
+
export interface IActionRouteDictionary {
|
|
23
|
+
/** `domain:id` → wire integer. */
|
|
24
|
+
routeToInt: Map<string, number>;
|
|
25
|
+
/** wire integer → route metadata for reconstruction. */
|
|
26
|
+
intToRoute: IActionRouteMeta[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the positional `domain:id` ↔ integer dictionary. Both ends of a channel MUST build it from
|
|
30
|
+
* the same domains in the same order — the mapping is positional, so a mismatch routes to the wrong
|
|
31
|
+
* action. Add new transported domains to the end of the list.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildActionRouteDictionary(domains: ActionDomain<any>[]): IActionRouteDictionary;
|
|
34
|
+
/** Pull the type-specific payload (`input` / `result` / `progress`) out of a wire JSON object. */
|
|
35
|
+
export declare function extractWirePayload(json: TActionPayload_Any_JsonObject<any, any>): unknown;
|
|
36
|
+
/**
|
|
37
|
+
* Reassemble a full wire JSON object from its decoded parts. `inputHash`/`outputHash` are emitted
|
|
38
|
+
* empty — the hydration constructors recompute them — and the result still satisfies
|
|
39
|
+
* `isActionPayload_Any_JsonObject` so it flows through validation like a JSON frame.
|
|
40
|
+
*/
|
|
41
|
+
export declare function assembleWireJson(routeMeta: IActionRouteMeta, payloadType: (typeof ReversePayloadType)[number], time: number, context: IActionContext_Data_JsonObject, payloadData: any): TActionPayload_Any_JsonObject<any, any>;
|
package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWsHandshake.d.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { type ClientCryptoKeyLink, type StorageAdapter, type TSerializedCryptoKeyData_Ed25519_Raw, type TSerializedCryptoKeyData_X25519_Raw, type TTypeAndId } from "@nice-code/util";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
import { type IRuntimeCoordinate } from "../../../../RuntimeCoordinate";
|
|
4
|
+
/** How much the channel protects after the handshake — chosen by the consumer (perf vs security). */
|
|
5
|
+
export declare enum ESecurityLevel {
|
|
6
|
+
/** No handshake; identity is self-asserted (fastest, dev / trusted networks). */
|
|
7
|
+
none = "none",
|
|
8
|
+
/** Handshake authenticates identity (sign/verify + key pin); frames stay plaintext over TLS. */
|
|
9
|
+
authenticated = "authenticated",
|
|
10
|
+
/** Authenticated handshake + every frame AES-GCM encrypted with the derived shared key. */
|
|
11
|
+
encrypted = "encrypted"
|
|
12
|
+
}
|
|
13
|
+
export declare enum EHandshakeMessageType {
|
|
14
|
+
hello = "hello",
|
|
15
|
+
welcome = "welcome",
|
|
16
|
+
prove = "prove",
|
|
17
|
+
accept = "accept",
|
|
18
|
+
reject = "reject"
|
|
19
|
+
}
|
|
20
|
+
declare const vHsHello: v.ObjectSchema<{
|
|
21
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.hello, undefined>;
|
|
22
|
+
readonly protocol: v.StringSchema<undefined>;
|
|
23
|
+
readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
|
|
24
|
+
readonly dictionaryVersion: v.StringSchema<undefined>;
|
|
25
|
+
readonly client: v.ObjectSchema<{
|
|
26
|
+
readonly envId: v.StringSchema<undefined>;
|
|
27
|
+
readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
28
|
+
readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
29
|
+
}, undefined>;
|
|
30
|
+
readonly clientNonce: v.StringSchema<undefined>;
|
|
31
|
+
readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
|
|
32
|
+
readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
|
|
33
|
+
}, undefined>;
|
|
34
|
+
declare const vHsWelcome: v.ObjectSchema<{
|
|
35
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.welcome, undefined>;
|
|
36
|
+
readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
|
|
37
|
+
readonly dictionaryVersion: v.StringSchema<undefined>;
|
|
38
|
+
readonly server: v.ObjectSchema<{
|
|
39
|
+
readonly envId: v.StringSchema<undefined>;
|
|
40
|
+
readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
41
|
+
readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
42
|
+
}, undefined>;
|
|
43
|
+
readonly serverNonce: v.StringSchema<undefined>;
|
|
44
|
+
readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
|
|
45
|
+
readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
|
|
46
|
+
}, undefined>;
|
|
47
|
+
declare const vHsProve: v.ObjectSchema<{
|
|
48
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.prove, undefined>;
|
|
49
|
+
readonly signatureBase64: v.StringSchema<undefined>;
|
|
50
|
+
}, undefined>;
|
|
51
|
+
declare const vHsAccept: v.ObjectSchema<{
|
|
52
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.accept, undefined>;
|
|
53
|
+
readonly signatureBase64: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
54
|
+
}, undefined>;
|
|
55
|
+
declare const vHsReject: v.ObjectSchema<{
|
|
56
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.reject, undefined>;
|
|
57
|
+
readonly reason: v.StringSchema<undefined>;
|
|
58
|
+
}, undefined>;
|
|
59
|
+
declare const vHandshakeMessage: v.VariantSchema<"t", [v.ObjectSchema<{
|
|
60
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.hello, undefined>;
|
|
61
|
+
readonly protocol: v.StringSchema<undefined>;
|
|
62
|
+
readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
|
|
63
|
+
readonly dictionaryVersion: v.StringSchema<undefined>;
|
|
64
|
+
readonly client: v.ObjectSchema<{
|
|
65
|
+
readonly envId: v.StringSchema<undefined>;
|
|
66
|
+
readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
67
|
+
readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
68
|
+
}, undefined>;
|
|
69
|
+
readonly clientNonce: v.StringSchema<undefined>;
|
|
70
|
+
readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
|
|
71
|
+
readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
|
|
72
|
+
}, undefined>, v.ObjectSchema<{
|
|
73
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.welcome, undefined>;
|
|
74
|
+
readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
|
|
75
|
+
readonly dictionaryVersion: v.StringSchema<undefined>;
|
|
76
|
+
readonly server: v.ObjectSchema<{
|
|
77
|
+
readonly envId: v.StringSchema<undefined>;
|
|
78
|
+
readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
79
|
+
readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
80
|
+
}, undefined>;
|
|
81
|
+
readonly serverNonce: v.StringSchema<undefined>;
|
|
82
|
+
readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
|
|
83
|
+
readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
|
|
84
|
+
}, undefined>, v.ObjectSchema<{
|
|
85
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.prove, undefined>;
|
|
86
|
+
readonly signatureBase64: v.StringSchema<undefined>;
|
|
87
|
+
}, undefined>, v.ObjectSchema<{
|
|
88
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.accept, undefined>;
|
|
89
|
+
readonly signatureBase64: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
90
|
+
}, undefined>, v.ObjectSchema<{
|
|
91
|
+
readonly t: v.LiteralSchema<EHandshakeMessageType.reject, undefined>;
|
|
92
|
+
readonly reason: v.StringSchema<undefined>;
|
|
93
|
+
}, undefined>], undefined>;
|
|
94
|
+
export type THsHello = v.InferOutput<typeof vHsHello>;
|
|
95
|
+
export type THsWelcome = v.InferOutput<typeof vHsWelcome>;
|
|
96
|
+
export type THsProve = v.InferOutput<typeof vHsProve>;
|
|
97
|
+
export type THsAccept = v.InferOutput<typeof vHsAccept>;
|
|
98
|
+
export type THsReject = v.InferOutput<typeof vHsReject>;
|
|
99
|
+
export type THandshakeMessage = v.InferOutput<typeof vHandshakeMessage>;
|
|
100
|
+
/** Serialize a handshake message for the wire (handshake frames are JSON — they aren't the hot path). */
|
|
101
|
+
export declare function encodeHandshakeMessage(message: THandshakeMessage): string;
|
|
102
|
+
/** Parse + structurally validate an incoming handshake frame; `undefined` if it isn't one. */
|
|
103
|
+
export declare function decodeHandshakeMessage(raw: string): THandshakeMessage | undefined;
|
|
104
|
+
/** Stable link id for a runtime coordinate — the key both the crypto link and the connection use. */
|
|
105
|
+
export declare function runtimeLinkId(coordinate: IRuntimeCoordinate): TTypeAndId;
|
|
106
|
+
/**
|
|
107
|
+
* Everything needed to re-derive the shared AES-GCM key for an `encrypted` link after a restart —
|
|
108
|
+
* the remote's public keys + the HKDF salt/info used at handshake time. The local (server) key pair is
|
|
109
|
+
* recovered from its own persisted `ClientCryptoKeyLink` storage, so re-linking with this material
|
|
110
|
+
* yields the identical shared key without a fresh handshake.
|
|
111
|
+
*/
|
|
112
|
+
export interface IHandshakeEncryptionKeyMaterial {
|
|
113
|
+
verifyPublicKey: TSerializedCryptoKeyData_Ed25519_Raw;
|
|
114
|
+
exchangePublicKey: TSerializedCryptoKeyData_X25519_Raw;
|
|
115
|
+
saltString: string;
|
|
116
|
+
infoString: string;
|
|
117
|
+
bindVerifyKeysIntoDerivation: boolean;
|
|
118
|
+
}
|
|
119
|
+
/** Outcome of a completed handshake — what the transport/handler needs to wire the channel. */
|
|
120
|
+
export interface IHandshakeResult {
|
|
121
|
+
/** The crypto-link id (and connection-registry key) for the authenticated remote. */
|
|
122
|
+
linkedClientId: TTypeAndId;
|
|
123
|
+
/** The remote's authenticated coordinate. */
|
|
124
|
+
remote: IRuntimeCoordinate;
|
|
125
|
+
securityLevel: ESecurityLevel;
|
|
126
|
+
/** For the `encrypted` level: material to restore the shared key after eviction (persist it). */
|
|
127
|
+
encryptionKeyMaterial?: IHandshakeEncryptionKeyMaterial;
|
|
128
|
+
}
|
|
129
|
+
export interface IClientVerifyKeyResolveInput {
|
|
130
|
+
client: IRuntimeCoordinate;
|
|
131
|
+
verifyPublicKey: TSerializedCryptoKeyData_Ed25519_Raw;
|
|
132
|
+
}
|
|
133
|
+
export interface IClientVerifyKeyResolver {
|
|
134
|
+
/**
|
|
135
|
+
* Decide whether a presented verify key is trusted for a client identity. The signature is already
|
|
136
|
+
* verified by the time this runs, so this is purely the identity-pinning decision. Swap in a
|
|
137
|
+
* persistent / pre-provisioned implementation without touching the protocol.
|
|
138
|
+
*/
|
|
139
|
+
resolve(input: IClientVerifyKeyResolveInput): Promise<{
|
|
140
|
+
trusted: boolean;
|
|
141
|
+
reason?: string;
|
|
142
|
+
}>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* In-memory trust-on-first-use resolver: trusts (and pins) the first verify key seen for a client
|
|
146
|
+
* identity, then rejects a different key for that identity. The default; replace with a storage-backed
|
|
147
|
+
* resolver for cross-restart pinning (see Step 5).
|
|
148
|
+
*/
|
|
149
|
+
export declare function createInMemoryTofuVerifyKeyResolver(): IClientVerifyKeyResolver;
|
|
150
|
+
/**
|
|
151
|
+
* Storage-backed trust-on-first-use resolver: pins survive process restarts / Durable Object eviction
|
|
152
|
+
* (e.g. back it with `createDurableObjectStorageAdapter`). Same policy as the in-memory variant — trust
|
|
153
|
+
* + pin the first verify key per client identity, reject a different one thereafter.
|
|
154
|
+
*/
|
|
155
|
+
export declare function createStorageTofuVerifyKeyResolver(storageAdapter: StorageAdapter): IClientVerifyKeyResolver;
|
|
156
|
+
export interface IClientHandshakeConfig {
|
|
157
|
+
link: ClientCryptoKeyLink;
|
|
158
|
+
localCoordinate: IRuntimeCoordinate;
|
|
159
|
+
dictionaryVersion: string;
|
|
160
|
+
securityLevel: ESecurityLevel;
|
|
161
|
+
}
|
|
162
|
+
export declare function createClientHandshake(config: IClientHandshakeConfig): {
|
|
163
|
+
createHello(): Promise<THsHello>;
|
|
164
|
+
onWelcome(welcome: THsWelcome): Promise<THsProve>;
|
|
165
|
+
onAccept(accept: THsAccept): Promise<IHandshakeResult>;
|
|
166
|
+
};
|
|
167
|
+
export interface IServerHandshakeConfig {
|
|
168
|
+
link: ClientCryptoKeyLink;
|
|
169
|
+
localCoordinate: IRuntimeCoordinate;
|
|
170
|
+
dictionaryVersion: string;
|
|
171
|
+
/**
|
|
172
|
+
* The level(s) this server accepts. A single level is strict (the client must match). An array is a
|
|
173
|
+
* negotiable allowed set — the server adopts whichever level the client requests, as long as it's in
|
|
174
|
+
* the set (lets one backend serve `authenticated` and `encrypted` clients at once). `none` in the set
|
|
175
|
+
* is handled by the transport/handler (a `none` client never reaches the handshake).
|
|
176
|
+
*/
|
|
177
|
+
securityLevel: ESecurityLevel | readonly ESecurityLevel[];
|
|
178
|
+
/** Trust decision for a client's verify key. Defaults to in-memory TOFU. */
|
|
179
|
+
verifyKeyResolver?: IClientVerifyKeyResolver;
|
|
180
|
+
}
|
|
181
|
+
export declare function createServerHandshake(config: IServerHandshakeConfig): {
|
|
182
|
+
onHello(hello: THsHello): Promise<THsWelcome | THsReject>;
|
|
183
|
+
onProve(prove: THsProve): Promise<THsAccept | THsReject>;
|
|
184
|
+
/** The completed handshake result once `onProve` has accepted, else `undefined`. */
|
|
185
|
+
getResult(): IHandshakeResult | undefined;
|
|
186
|
+
};
|
|
187
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ActionDomain } from "../../../../../ActionDefinition/Domain/ActionDomain";
|
|
2
|
+
import type { IActionTransportReadyData_Ws } from "./TransportWebSocket.types";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a *stateless* `formatMessage` pipeline for {@link WebSocketTransport}, packing action
|
|
5
|
+
* payloads into a compact msgpackr binary frame instead of JSON. The `domain`/`id` route collapses to
|
|
6
|
+
* a single integer drawn from a shared dictionary; `form`/`type`, the recomputable
|
|
7
|
+
* `inputHash`/`outputHash`, and the per-frame `context.routing`/`context.timeCreated` are all dropped
|
|
8
|
+
* (see {@link ENVELOPE}).
|
|
9
|
+
*
|
|
10
|
+
* No validation runs here: `incoming` blindly reconstructs the wire JSON shape and hands it back to
|
|
11
|
+
* the connection, which flows into `ActionRuntime` → `domain.hydrateAnyAction()` where the Valibot
|
|
12
|
+
* schemas validate it exactly as they would for a JSON frame.
|
|
13
|
+
*
|
|
14
|
+
* Both ends of the socket MUST construct the adapter with the same domains in the same order — the
|
|
15
|
+
* integer dictionary is positional. Mismatched dictionaries will route to the wrong action.
|
|
16
|
+
*
|
|
17
|
+
* Because `incoming` returns `undefined` for text frames, a binary server can still serve plain-JSON
|
|
18
|
+
* clients on the same runtime (the connection falls back to its built-in JSON parser).
|
|
19
|
+
*/
|
|
20
|
+
export declare function createBinaryWsAdapter(domains: ActionDomain<any>[]): NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ActionDomain } from "../../../../../ActionDefinition/Domain/ActionDomain";
|
|
2
|
+
import type { IActionTransportReadyData_Ws } from "./TransportWebSocket.types";
|
|
3
|
+
type TFormatMessage = NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
|
|
4
|
+
export interface IBinaryWsSessionOptions {
|
|
5
|
+
/** Override how long an unresolved correlation is retained before being swept (ms). */
|
|
6
|
+
correlationTtlMs?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Builds a factory of *stateful, per-connection* codecs for {@link WebSocketTransport} /
|
|
10
|
+
* `ActionServerHandler` — the maximally compact binary wire. Call the returned factory once per live
|
|
11
|
+
* connection (each socket on the client, each accepted connection on the server) so every channel
|
|
12
|
+
* gets its own correlation + identity state.
|
|
13
|
+
*
|
|
14
|
+
* On top of everything {@link createBinaryWsAdapter} drops, a session also drops:
|
|
15
|
+
* - **`cuid`** — replaced by a per-connection integer correlation id. The initiator maps it to its
|
|
16
|
+
* real cuid; the responder echoes it; each side reconstructs the cuid from its own map. Correlation
|
|
17
|
+
* only needs to be unique per socket, so a counter suffices.
|
|
18
|
+
* - **`originClient` after the first request** — the first request each side sends carries its
|
|
19
|
+
* identity; the peer remembers it and injects it into later frames. Replies omit it entirely (a
|
|
20
|
+
* reply carries the initiator's own origin, which the initiator already knows).
|
|
21
|
+
*
|
|
22
|
+
* Both ends MUST build the factory from the same domains in the same order (positional dictionary).
|
|
23
|
+
* Text frames still return `undefined` from `incoming`, so JSON clients remain interoperable.
|
|
24
|
+
*
|
|
25
|
+
* Hibernation note: after a server connection is evicted its session resets, so a still-connected
|
|
26
|
+
* client (whose session persists) will keep omitting `originClient`. The server must therefore restore
|
|
27
|
+
* the connection→client binding from its own store (see `ActionServerHandler.rehydrateConnection`) and
|
|
28
|
+
* inject `originClient` from there — the session alone can't recover it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createBinaryWsSessionFactory(domains: ActionDomain<any>[], options?: IBinaryWsSessionOptions): () => TFormatMessage;
|
|
31
|
+
export {};
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send a text or binary frame over a socket. A binary formatter may hand back a `Uint8Array` whose
|
|
3
|
+
* backing buffer is typed as `ArrayBufferLike` (msgpackr pools buffers / may be `SharedArrayBuffer`),
|
|
4
|
+
* which `WebSocket.send`'s `BufferSource` parameter rejects — copy it into a fresh `ArrayBuffer`-backed
|
|
5
|
+
* view so the type (and the bytes) are safe to send.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sendFrame(ws: WebSocket, data: string | Uint8Array | ArrayBuffer): void;
|
|
8
|
+
/** Normalize any frame form to bytes (for the AES-GCM layer, which works on `Uint8Array`). */
|
|
9
|
+
export declare function toFrameBytes(frame: string | Uint8Array | ArrayBuffer): Uint8Array;
|
|
1
10
|
/** Compact a WebSocket URL to `host/pathname` for devtools display, falling back to the raw url. */
|
|
2
11
|
export declare function shortWs(url: string): string;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { ClientCryptoKeyLink, TTypeAndId } from "@nice-code/util";
|
|
2
|
+
import type { IActionRouteItemHandler } from "../../../ActionDefinition/Action/Payload/ActionPayload.types";
|
|
3
|
+
import { type TActionPayload_Any_Instance, type TActionPayload_Any_JsonObject } from "../../../ActionDefinition/Action/Payload/ActionPayload.types";
|
|
4
|
+
import type { ActionPayload_Request } from "../../../ActionDefinition/Action/Payload/ActionPayload_Request";
|
|
5
|
+
import { RunningAction } from "../../../ActionDefinition/Action/RunningAction";
|
|
6
|
+
import type { IActionDomain } from "../../../ActionDefinition/Domain/ActionDomain.types";
|
|
7
|
+
import { ActionRuntime } from "../../ActionRuntime";
|
|
8
|
+
import { type IRuntimeCoordinate, RuntimeCoordinate } from "../../RuntimeCoordinate";
|
|
9
|
+
import type { IHandleActionOptions } from "../ActionHandler.types";
|
|
10
|
+
import { ActionExternalClientHandler } from "../ExternalClient/ActionExternalClientHandler";
|
|
11
|
+
import { ESecurityLevel, type IClientVerifyKeyResolver, type IHandshakeEncryptionKeyMaterial } from "../ExternalClient/Transport/WebSocket/actionWsHandshake";
|
|
12
|
+
import type { IActionTransportReadyData_Ws } from "../ExternalClient/Transport/WebSocket/TransportWebSocket.types";
|
|
13
|
+
/** The codec shape `ActionServerHandler` uses to pack/unpack frames — same as the WS transport's. */
|
|
14
|
+
export type TActionChannelFormatMessage = NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
|
|
15
|
+
/** How a connection encodes its frames, remembered so we answer each client in its own dialect. */
|
|
16
|
+
export type TActionConnectionEncoding = "json" | "binary";
|
|
17
|
+
/** A connection's restorable identity — what to persist so a binding survives transport eviction. */
|
|
18
|
+
export interface IActionServerConnectionBinding {
|
|
19
|
+
/** Full client coordinate, so `originClient` can be re-injected into frames that omit it. */
|
|
20
|
+
client: IRuntimeCoordinate;
|
|
21
|
+
encoding: TActionConnectionEncoding;
|
|
22
|
+
/**
|
|
23
|
+
* Secure-session state (set once a connection's handshake completes). Persist it alongside the
|
|
24
|
+
* binding so an authenticated/encrypted connection resumes after eviction without re-handshaking —
|
|
25
|
+
* the `keyMaterial` lets the server re-derive the shared key from its own persisted identity.
|
|
26
|
+
*/
|
|
27
|
+
secure?: {
|
|
28
|
+
securityLevel: ESecurityLevel;
|
|
29
|
+
linkedClientId: TTypeAndId;
|
|
30
|
+
keyMaterial?: IHandshakeEncryptionKeyMaterial;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Server-side secure-channel config. When set, each connection negotiates a level from
|
|
35
|
+
* {@link securityLevel}: an `authenticated`/`encrypted` client must complete the handshake (and is then
|
|
36
|
+
* bound to its *authenticated* coordinate) before any action frame is accepted. A `none` client (only
|
|
37
|
+
* when `none` is in the allowed set) is accepted as-is with a self-asserted identity. For the
|
|
38
|
+
* `encrypted` level the codec source should be a session factory (`createFormatMessage`).
|
|
39
|
+
*/
|
|
40
|
+
export interface IActionServerSecurity {
|
|
41
|
+
/**
|
|
42
|
+
* Accepted level(s). A single level is strict; an array is a negotiable allowed set — the server
|
|
43
|
+
* adopts whichever level each client requests (e.g. `[none, authenticated, encrypted]` serves all
|
|
44
|
+
* three over one endpoint).
|
|
45
|
+
*/
|
|
46
|
+
securityLevel: ESecurityLevel | readonly ESecurityLevel[];
|
|
47
|
+
/** This server's crypto identity (verify + exchange key pairs, optionally persisted). */
|
|
48
|
+
link: ClientCryptoKeyLink;
|
|
49
|
+
/** This server's coordinate — its identity to clients during the handshake. */
|
|
50
|
+
localCoordinate: IRuntimeCoordinate;
|
|
51
|
+
/** Wire dictionary version; the handshake rejects a client on a mismatch. */
|
|
52
|
+
dictionaryVersion: string;
|
|
53
|
+
/** Trust decision for a client's verify key (defaults to in-memory TOFU inside the handshake). */
|
|
54
|
+
verifyKeyResolver?: IClientVerifyKeyResolver;
|
|
55
|
+
}
|
|
56
|
+
interface IActionServerHandlerBaseOptions<TConn> {
|
|
57
|
+
/**
|
|
58
|
+
* Coordinate of the *connecting clients* (typically env-only, e.g. `RuntimeCoordinate.env("web_app")`).
|
|
59
|
+
* The runtime's return-path dispatch scores incoming actions' `originClient` against this to pick
|
|
60
|
+
* this handler for sending results/pushes back over the right channel.
|
|
61
|
+
*/
|
|
62
|
+
clientEnv: RuntimeCoordinate;
|
|
63
|
+
/** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
|
|
64
|
+
send: (connection: TConn, frame: string | Uint8Array | ArrayBuffer) => void;
|
|
65
|
+
/** Timeout (ms) applied to server-initiated actions awaiting a client response. */
|
|
66
|
+
defaultTimeout?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Called once when a connection is first bound to a client identity. Use it to persist the binding
|
|
69
|
+
* for transports that can resume after eviction — e.g. a Durable Object's hibernatable WebSocket:
|
|
70
|
+
* `(ws, binding) => ws.serializeAttachment(binding)` — then replay it via {@link ActionServerHandler.rehydrateConnection}
|
|
71
|
+
* when the channel comes back.
|
|
72
|
+
*/
|
|
73
|
+
onConnectionBound?: (connection: TConn, binding: IActionServerConnectionBinding) => void;
|
|
74
|
+
/**
|
|
75
|
+
* Enable the authenticated (optionally encrypted) handshake. When omitted, connections are trusted
|
|
76
|
+
* as-is (identity self-asserted) — fine for dev / trusted networks.
|
|
77
|
+
*/
|
|
78
|
+
security?: IActionServerSecurity;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Provide exactly one codec source:
|
|
82
|
+
* - `formatMessage` — a single shared codec for every connection (stateless, e.g. `createBinaryWsAdapter`).
|
|
83
|
+
* - `createFormatMessage` — a per-connection factory for stateful codecs (e.g.
|
|
84
|
+
* `createBinaryWsSessionFactory`, whose sessions hold correlation + identity state). Required for the
|
|
85
|
+
* leanest binary wire; the handler creates and caches one codec per connection.
|
|
86
|
+
*/
|
|
87
|
+
export type IActionServerHandlerOptions<TConn> = IActionServerHandlerBaseOptions<TConn> & ({
|
|
88
|
+
formatMessage: TActionChannelFormatMessage;
|
|
89
|
+
createFormatMessage?: never;
|
|
90
|
+
} | {
|
|
91
|
+
createFormatMessage: () => TActionChannelFormatMessage;
|
|
92
|
+
formatMessage?: never;
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* Server-side handler for backends that accept many client connections over a single open channel
|
|
96
|
+
* (WebSockets, Durable Objects, …). It is transport-agnostic: you feed it inbound frames with
|
|
97
|
+
* {@link receive} and tell it how to write outbound frames via the `send` option.
|
|
98
|
+
*
|
|
99
|
+
* Add it alongside your local execution handler:
|
|
100
|
+
* ```ts
|
|
101
|
+
* const serverHandler = createServerHandler({ clientEnv, formatMessage, send: (ws, f) => ws.send(f) });
|
|
102
|
+
* runtime.addHandlers([localHandler, serverHandler]);
|
|
103
|
+
* // per inbound message (e.g. a Durable Object's webSocketMessage):
|
|
104
|
+
* serverHandler.receive(ws, message);
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* Inbound requests route to your local handler; the runtime's return dispatch then calls this
|
|
108
|
+
* handler back (it is an external handler keyed to `clientEnv`) to send the result to the originating
|
|
109
|
+
* connection. The handler keeps a per-connection identity registry so each result lands on the right
|
|
110
|
+
* socket, and remembers each connection's encoding so binary and JSON clients can share the channel.
|
|
111
|
+
*
|
|
112
|
+
* It registers an empty action router, so it is never chosen to *execute* an inbound request — only
|
|
113
|
+
* to ferry results/pushes back out.
|
|
114
|
+
*/
|
|
115
|
+
export declare class ActionServerHandler<TConn = unknown> extends ActionExternalClientHandler {
|
|
116
|
+
private readonly _formatMessage?;
|
|
117
|
+
private readonly _createFormatMessage?;
|
|
118
|
+
private readonly _send;
|
|
119
|
+
private readonly _serverTimeout;
|
|
120
|
+
private readonly _onConnectionBound?;
|
|
121
|
+
/** Incoming-data listeners installed by the runtime (`resolveIncomingActionPayload`). */
|
|
122
|
+
private readonly _incomingListeners;
|
|
123
|
+
private readonly _security?;
|
|
124
|
+
/** Normalized accepted levels; whether `none` (plain) is allowed; whether any level needs a handshake. */
|
|
125
|
+
private readonly _allowedLevels;
|
|
126
|
+
private readonly _noneAllowed;
|
|
127
|
+
private readonly _handshakeMode;
|
|
128
|
+
private readonly _connByClient;
|
|
129
|
+
private readonly _clientByConn;
|
|
130
|
+
private readonly _connEncoding;
|
|
131
|
+
private readonly _codecByConn;
|
|
132
|
+
private readonly _handshakeByConn;
|
|
133
|
+
private readonly _cryptoByConn;
|
|
134
|
+
private readonly _authedConns;
|
|
135
|
+
private readonly _plainConns;
|
|
136
|
+
private readonly _inboundChainByConn;
|
|
137
|
+
private readonly _outboundChainByConn;
|
|
138
|
+
constructor(options: IActionServerHandlerOptions<TConn>);
|
|
139
|
+
/**
|
|
140
|
+
* The codec for a connection: a per-connection session (cached) when a factory was provided, else
|
|
141
|
+
* the single shared `formatMessage`.
|
|
142
|
+
*/
|
|
143
|
+
private _codecFor;
|
|
144
|
+
_setIncomingActionDataListener(listener: (json: TActionPayload_Any_JsonObject<any>) => void): void;
|
|
145
|
+
/**
|
|
146
|
+
* Feed one inbound frame from a connection into the runtime. Decodes text or binary, binds the
|
|
147
|
+
* connection to the requesting client's identity, then routes it (requests execute locally;
|
|
148
|
+
* results/progress resolve pending server-initiated actions).
|
|
149
|
+
*/
|
|
150
|
+
receive(connection: TConn, frame: string | ArrayBuffer | Uint8Array): void;
|
|
151
|
+
private _receivePlain;
|
|
152
|
+
private _receiveSecure;
|
|
153
|
+
private _completeServerHandshake;
|
|
154
|
+
/**
|
|
155
|
+
* Ensure an inbound request carries the client's identity and that this connection is bound to it,
|
|
156
|
+
* so its result can be routed back. A session codec omits `originClient` after the first request, so
|
|
157
|
+
* when it's missing we restore it from the (possibly rehydrated) binding instead. (Plain mode only;
|
|
158
|
+
* secure mode binds the authenticated coordinate at handshake time.)
|
|
159
|
+
*/
|
|
160
|
+
private _resolveRequestIdentity;
|
|
161
|
+
/**
|
|
162
|
+
* Restore a connection→client binding without an inbound frame — for transports that resume after
|
|
163
|
+
* eviction. Pair it with the {@link IActionServerHandlerOptions.onConnectionBound} hook: persist
|
|
164
|
+
* the binding there, then replay each live connection here when the channel comes back (e.g. a
|
|
165
|
+
* Durable Object iterating `ctx.getWebSockets()` as it wakes from hibernation).
|
|
166
|
+
*/
|
|
167
|
+
rehydrateConnection(connection: TConn, binding: IActionServerConnectionBinding): void;
|
|
168
|
+
toHandlerRouteItem(): IActionRouteItemHandler;
|
|
169
|
+
/** Forget a connection (call on socket close) so stale entries don't misroute later results. */
|
|
170
|
+
dropConnection(connection: TConn): void;
|
|
171
|
+
/** Live connection for a client coordinate, if currently registered. */
|
|
172
|
+
getConnectionForClient(client: RuntimeCoordinate): TConn | undefined;
|
|
173
|
+
/**
|
|
174
|
+
* Send (and optionally await) a server-initiated action to a specific connected client. Pass the
|
|
175
|
+
* connection token directly (e.g. the `ws`) or a client `RuntimeCoordinate` to look one up.
|
|
176
|
+
*/
|
|
177
|
+
pushToClient<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(runtime: ActionRuntime, target: TConn | RuntimeCoordinate, request: ActionPayload_Request<DOM, ID>, options?: {
|
|
178
|
+
timeout?: number;
|
|
179
|
+
}): RunningAction<DOM, ID>;
|
|
180
|
+
sendReturnPayload(payload: TActionPayload_Any_Instance<any, any>, config: {
|
|
181
|
+
targetLocalRuntime: ActionRuntime;
|
|
182
|
+
}): Promise<boolean>;
|
|
183
|
+
handleActionRequest<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: ActionPayload_Request<DOM, ID>, config?: IHandleActionOptions): Promise<RunningAction<DOM, ID>>;
|
|
184
|
+
private _dispatch;
|
|
185
|
+
private _sendPayload;
|
|
186
|
+
private _bindConnection;
|
|
187
|
+
private _resolveConnection;
|
|
188
|
+
private _resolveSingleConnection;
|
|
189
|
+
}
|
|
190
|
+
export declare const createServerHandler: <TConn = unknown>(options: IActionServerHandlerOptions<TConn>) => ActionServerHandler<TConn>;
|
|
191
|
+
export {};
|