@nice-code/action 0.23.0 → 0.24.0
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 +18 -21
- package/build/{ActionPayload.types-B-OSg09t.d.mts → AcceptorHandler-11-QMdx2.d.mts} +967 -1346
- package/build/{ActionPayload.types-DIOeVapm.d.cts → AcceptorHandler-CxD0c1BE.d.cts} +967 -1346
- package/build/{ActionDevtoolsCore-BjbhFqc0.d.mts → ActionDevtoolsCore-37JP4bOG.d.cts} +2 -2
- package/build/{ActionDevtoolsCore-kk7oZBv9.d.cts → ActionDevtoolsCore-Cgq-go1R.d.mts} +2 -2
- package/build/advanced/index.cjs +115 -0
- package/build/advanced/index.cjs.map +1 -0
- package/build/advanced/index.d.cts +344 -0
- package/build/advanced/index.d.mts +344 -0
- package/build/advanced/index.mjs +88 -0
- package/build/advanced/index.mjs.map +1 -0
- package/build/{httpAcceptorCarrier-hYPuoNuP.cjs → createHibernatableWsServerAdapter-BNi4k9j3.cjs} +258 -470
- package/build/createHibernatableWsServerAdapter-BNi4k9j3.cjs.map +1 -0
- package/build/{httpAcceptorCarrier-DJVxzDVd.mjs → createHibernatableWsServerAdapter-C07RfUTH.mjs} +233 -421
- package/build/createHibernatableWsServerAdapter-C07RfUTH.mjs.map +1 -0
- package/build/devtools/browser/index.d.cts +1 -1
- package/build/devtools/browser/index.d.mts +1 -1
- package/build/devtools/server/index.d.cts +1 -1
- package/build/devtools/server/index.d.mts +1 -1
- package/build/httpAcceptorCarrier-C3S_bDkL.cjs +454 -0
- package/build/httpAcceptorCarrier-C3S_bDkL.cjs.map +1 -0
- package/build/httpAcceptorCarrier-DPBEuewS.mjs +401 -0
- package/build/httpAcceptorCarrier-DPBEuewS.mjs.map +1 -0
- package/build/index.cjs +69 -449
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +2 -2
- package/build/index.d.mts +2 -2
- package/build/index.mjs +13 -365
- package/build/index.mjs.map +1 -1
- package/build/platform/cloudflare/index.cjs +1 -1
- package/build/platform/cloudflare/index.cjs.map +1 -1
- package/build/platform/cloudflare/index.d.cts +3 -3
- package/build/platform/cloudflare/index.d.mts +3 -3
- package/build/platform/cloudflare/index.mjs +1 -1
- package/build/platform/cloudflare/index.mjs.map +1 -1
- package/build/react-query/index.d.cts +1 -1
- package/build/react-query/index.d.mts +1 -1
- package/package.json +15 -4
- package/build/httpAcceptorCarrier-DJVxzDVd.mjs.map +0 -1
- package/build/httpAcceptorCarrier-hYPuoNuP.cjs.map +0 -1
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { $t as IActionTransportInitialized, An as TransportConnection, Bn as IHandshakeEncryptionKeyMaterial, Bt as IActionWireFormat, Cn as TSendActionDataMethod, Ct as createHibernatableWsServerAdapter, Dn as TTransportStatusInfo, Dt as createConnectionStateStore, En as TTransportInitializationFinishedInfo, Et as IConnectionStateStoreOptions, Fn as EHandshakeMessageType, Hn as IServerHandshakeConfig, In as ESecurityLevel, Jn as decodeHandshakeMessage, Jt as createConnectorHandler, Kn as createServerHandshake, Ln as IClientHandshakeConfig, Mn as Transport, Nn as ISecureAcceptorHandlerOptions, O as TActionPayload_Any_JsonObject, On as TTransportStatusInfo_GetTransport_Output, Pn as createSecureAcceptorHandler, Qt as IActionTransportDef, Rt as IBinaryWireSessionOptions, Sn as TOnResolveIncomingResponseJson, St as IHibernatableWsServerAdapterOptions, Tn as TTransportCache, Tt as IConnectionAttachment, Un as THandshakeMessage, Ut as IExchangeCarrier, Vn as IHandshakeResult, Vt as IDuplexCarrier, Wn as createClientHandshake, Xt as ETransportShape, Yn as encodeHandshakeMessage, Yt as PeerLinkHandler, Zt as ETransportStatus, _n as TOnResolveAnyIncomingActionData, a as TAcceptorConnectionCaseFn, an as ITransportDispatchAction, bn as TOnResolveIncomingRequestJson, c as createAcceptorHandler, cn as ITransportRouteClientParams, dn as ITransportStatusInfo_Failed, en as IActionTransportReady, fn as ITransportStatusInfo_Initializing, gn as TGetTransportFn, hn as IUpdateActionRunConfig_Output, hr as IRuntimeCoordinate, i as TAcceptorCaseFn, in as ISecureClientConfig, jn as ITransportConnectionContext, kn as TUpdateActionRunConfig, l as ActionDomain, ln as ITransportRouteInfo, mn as ITransportStatusInfo_Unsupported, n as IAcceptorConnectionBinding, nn as IActionTransportReadyData_Methods, o as TActionChannelFormatMessage, on as ITransportMethod_SendActionData_Input, pn as ITransportStatusInfo_Ready, qt as ConnectorHandler, r as IAcceptorHandlerOptions, rn as IActionTransportResolvers, s as TActionConnectionEncoding, sn as ITransportRouteActionParams, t as AcceptorHandler, tn as IActionTransportReadyData_Base, u as ActionRuntime, un as ITransportStatusInfo_Base, vn as TOnResolveAnyIncomingActionData_Json, wn as TSendReturnDataMethod, wt as ConnectionStateStore, xn as TOnResolveIncomingResponse, xt as IDuplexConnectionRouter, yn as TOnResolveIncomingRequest, zn as IClientVerifyKeyResolver, zt as createBinaryWireSessionFactory } from "../AcceptorHandler-11-QMdx2.mjs";
|
|
2
|
+
import { ClientCryptoKeyLink, TTypeAndId } from "@nice-code/util";
|
|
3
|
+
|
|
4
|
+
//#region src/ActionRuntime/Transport/SecureSession/exchangeAcceptor.d.ts
|
|
5
|
+
/** Acceptor secure config for the exchange (HTTP) endpoint — same identity an `AcceptorHandler` uses. */
|
|
6
|
+
interface IExchangeAcceptorSecurity {
|
|
7
|
+
/** This acceptor's crypto identity (verify + exchange key pairs, optionally persisted). */
|
|
8
|
+
link: ClientCryptoKeyLink;
|
|
9
|
+
/** This acceptor's coordinate — its identity to clients during the handshake. */
|
|
10
|
+
localCoordinate: IRuntimeCoordinate;
|
|
11
|
+
/** Wire dictionary version; the handshake rejects a client on a mismatch. */
|
|
12
|
+
dictionaryVersion: string;
|
|
13
|
+
/** Accepted level(s) — a single level is strict, an array is a negotiable allowed set. */
|
|
14
|
+
securityLevel: ESecurityLevel | readonly ESecurityLevel[];
|
|
15
|
+
/** Trust decision for a client's verify key (defaults to in-memory TOFU inside the handshake). */
|
|
16
|
+
verifyKeyResolver?: IClientVerifyKeyResolver;
|
|
17
|
+
}
|
|
18
|
+
interface IExchangeAcceptorConfig {
|
|
19
|
+
security: IExchangeAcceptorSecurity;
|
|
20
|
+
/** The runtime that executes an inbound action wire and produces its result. */
|
|
21
|
+
runtime: ActionRuntime;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Acceptor (accept-in) side of the secure exchange protocol — the HTTP counterpart to
|
|
25
|
+
* {@link AcceptorSecureSession}. Each POST body is one {@link decodeExchangeRequest} envelope; the
|
|
26
|
+
* acceptor drives the server handshake over the two `hs` POSTs (correlated by `hsid`, since stateless
|
|
27
|
+
* requests can't rely on channel ordering), mints a session **token** on accept, and on every later `act`
|
|
28
|
+
* POST resolves the session by token, decrypts the body (at `encrypted`), routes it through the runtime,
|
|
29
|
+
* and returns the (encrypted) result inline as the reply.
|
|
30
|
+
*
|
|
31
|
+
* Sessions and in-flight handshakes are held in memory — fine for a single-instance server. (Surviving a
|
|
32
|
+
* Durable-Object eviction would persist each token's `keyMaterial` and re-derive the key on a miss, the
|
|
33
|
+
* same primitive `AcceptorSecureSession.rehydrate` uses; left as a follow-up.)
|
|
34
|
+
*/
|
|
35
|
+
declare class ExchangeAcceptor {
|
|
36
|
+
private readonly _security;
|
|
37
|
+
private readonly _runtime;
|
|
38
|
+
private readonly _allowedLevels;
|
|
39
|
+
private readonly _noneAllowed;
|
|
40
|
+
private readonly _pendingHandshakes;
|
|
41
|
+
private readonly _sessions;
|
|
42
|
+
constructor(config: IExchangeAcceptorConfig);
|
|
43
|
+
/** Process one POST body (an exchange envelope), returning the reply body to send back. */
|
|
44
|
+
handlePost(body: string): Promise<string>;
|
|
45
|
+
private _handleHandshake;
|
|
46
|
+
private _handleAction;
|
|
47
|
+
private _err;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/ActionRuntime/Handler/PeerLink/Acceptor/createActionFetchHandler.d.ts
|
|
51
|
+
interface IActionFetchHandlerOptions {
|
|
52
|
+
/**
|
|
53
|
+
* CORS headers merged onto every response (a preflight `OPTIONS` is answered `204` with them).
|
|
54
|
+
* Defaults to permissive `*`; pass `false` to attach no CORS headers at all.
|
|
55
|
+
*/
|
|
56
|
+
cors?: Record<string, string> | false;
|
|
57
|
+
/** Which requests carry an action wire on `POST`. Default: pathname ends with `/action`. */
|
|
58
|
+
isActionPath?: (url: URL) => boolean;
|
|
59
|
+
/** Which requests are WebSocket upgrades. Default: pathname ends with `/ws`. */
|
|
60
|
+
isWebSocketPath?: (url: URL) => boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Whether a request is a WebSocket upgrade for this endpoint, given the whole request (not just the
|
|
63
|
+
* URL). When set it *replaces* the default gate (an `Upgrade: websocket` header on an
|
|
64
|
+
* {@link isWebSocketPath} match) — use it when the discriminant needs a header or method, not only the
|
|
65
|
+
* path. Only consulted when {@link onWebSocketUpgrade} is present.
|
|
66
|
+
*/
|
|
67
|
+
isWebSocketUpgrade?: (request: Request, url: URL) => boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Perform the transport-specific WebSocket upgrade (e.g. a Durable Object's
|
|
70
|
+
* `new WebSocketPair()` + `ctx.acceptWebSocket()` returning a `101`). Omit for HTTP-only endpoints.
|
|
71
|
+
* Its response is returned as-is — a `101` upgrade carries no CORS headers.
|
|
72
|
+
*/
|
|
73
|
+
onWebSocketUpgrade?: (request: Request, url: URL) => Response | Promise<Response>;
|
|
74
|
+
/** Forwarded to `ActionPayload_Result.toHttpResponse` — use the error's HTTP status (default true). */
|
|
75
|
+
useErrorStatus?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Enable the secure exchange protocol (handshake + token sessions + body encryption) on the `/action`
|
|
78
|
+
* endpoint, mirroring an `AcceptorHandler`'s `security`. The matching connector is a secure HTTP
|
|
79
|
+
* transport (`connectChannel(..., { transports: [{ carrier: httpCarrier(...) }] })`). When omitted, the
|
|
80
|
+
* endpoint speaks the plain protocol (the raw action wire is POSTed and the result is the response body).
|
|
81
|
+
*/
|
|
82
|
+
security?: IExchangeAcceptorSecurity;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build the `fetch` handler a server/Durable-Object exposes for action traffic, folding in the
|
|
86
|
+
* boilerplate every endpoint repeats: CORS (incl. the `OPTIONS` preflight), routing the `/action`
|
|
87
|
+
* `POST` body through the runtime (`handleActionPayloadWire` → `waitForResultPayload` →
|
|
88
|
+
* `toHttpResponse`), an optional WebSocket-upgrade hook, and a `404` fallback.
|
|
89
|
+
*
|
|
90
|
+
* It only touches web-standard `Request`/`Response`, so it stays transport-agnostic — the one
|
|
91
|
+
* environment-specific bit (the WS upgrade) is injected via {@link IActionFetchHandlerOptions.onWebSocketUpgrade}:
|
|
92
|
+
* ```ts
|
|
93
|
+
* this.fetchHandler = createActionFetchHandler(this.runtime, {
|
|
94
|
+
* onWebSocketUpgrade: () => {
|
|
95
|
+
* const pair = new WebSocketPair();
|
|
96
|
+
* this.ctx.acceptWebSocket(pair[1]);
|
|
97
|
+
* return new Response(null, { status: 101, webSocket: pair[0] });
|
|
98
|
+
* },
|
|
99
|
+
* });
|
|
100
|
+
* // async fetch(request) { return this.fetchHandler(request); }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare function createActionFetchHandler(runtime: ActionRuntime, options?: IActionFetchHandlerOptions): (request: Request) => Promise<Response>;
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/ActionRuntime/Transport/codec/createBinaryWireAdapter.d.ts
|
|
106
|
+
/**
|
|
107
|
+
* Builds a *stateless* `formatMessage` pipeline for {@link LinkTransport}, packing action
|
|
108
|
+
* payloads into a compact msgpackr binary frame instead of JSON. The `domain`/`id` route collapses to
|
|
109
|
+
* a single integer drawn from a shared dictionary; `form`/`type`, the recomputable
|
|
110
|
+
* `inputHash`/`outputHash`, and the per-frame `context.routing`/`context.timeCreated` are all dropped
|
|
111
|
+
* (see {@link ENVELOPE}).
|
|
112
|
+
*
|
|
113
|
+
* No validation runs here: `incoming` blindly reconstructs the wire JSON shape and hands it back to
|
|
114
|
+
* the connection, which flows into `ActionRuntime` → `domain.hydrateAnyAction()` where the Valibot
|
|
115
|
+
* schemas validate it exactly as they would for a JSON frame.
|
|
116
|
+
*
|
|
117
|
+
* Both ends of the socket MUST construct the adapter with the same domains in the same order — the
|
|
118
|
+
* integer dictionary is positional. Mismatched dictionaries will route to the wrong action.
|
|
119
|
+
*
|
|
120
|
+
* Because `incoming` returns `undefined` for text frames, a binary server can still serve plain-JSON
|
|
121
|
+
* clients on the same runtime (the connection falls back to its built-in JSON parser).
|
|
122
|
+
*/
|
|
123
|
+
declare function createBinaryWireAdapter(domains: ActionDomain<any>[]): IActionWireFormat;
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/ActionRuntime/Transport/crypto/actionFrameCrypto.d.ts
|
|
126
|
+
/**
|
|
127
|
+
* Async AES-GCM transform for the `encrypted` security level. It wraps the opaque binary frame a
|
|
128
|
+
* session codec produces (it does NOT look inside it), encrypting on the way out and decrypting on the
|
|
129
|
+
* way in with the shared key established by the handshake.
|
|
130
|
+
*
|
|
131
|
+
* It is deliberately separate from the (synchronous) session `formatMessage`: WebCrypto is always
|
|
132
|
+
* Promise-based, so encryption has to happen at the transport's async I/O boundary — the connection
|
|
133
|
+
* encrypts after `session.outgoing()` and decrypts before `session.incoming()`. The `authenticated`
|
|
134
|
+
* and `none` levels use no crypto transform at all (frames go out as the session produced them).
|
|
135
|
+
*
|
|
136
|
+
* Wire shape of an encrypted frame: `pack([nonceBytes, ciphertextBytes])` — msgpack carries the two
|
|
137
|
+
* binary fields with a couple of bytes of overhead, no base64 inflation.
|
|
138
|
+
*/
|
|
139
|
+
interface IActionFrameCrypto {
|
|
140
|
+
/** Encrypt one session frame for sending. */
|
|
141
|
+
encryptFrame(frame: Uint8Array): Promise<Uint8Array>;
|
|
142
|
+
/** Decrypt one received frame back to the session frame. Throws on a non-binary / malformed /
|
|
143
|
+
* tampered frame — the caller (transport) decides how to react (drop / close). */
|
|
144
|
+
decryptFrame(frame: string | ArrayBuffer | Uint8Array): Promise<Uint8Array>;
|
|
145
|
+
}
|
|
146
|
+
interface IActionFrameCryptoConfig {
|
|
147
|
+
link: ClientCryptoKeyLink;
|
|
148
|
+
/** The handshake-established link id for the remote (key + connection-registry id). */
|
|
149
|
+
linkedClientId: TTypeAndId;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
|
|
153
|
+
* level. Keyed by the link + `linkedClientId`, so it reuses the cached shared AES-GCM key.
|
|
154
|
+
*/
|
|
155
|
+
declare function createActionFrameCrypto({
|
|
156
|
+
link,
|
|
157
|
+
linkedClientId
|
|
158
|
+
}: IActionFrameCryptoConfig): IActionFrameCrypto;
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/ActionRuntime/Transport/Exchange/TransportExchange.types.d.ts
|
|
161
|
+
interface IActionTransportReadyData_Exchange extends IActionTransportReadyData_Base {
|
|
162
|
+
/** The live request/reply carrier this connection drives its session over. */
|
|
163
|
+
carrier: IExchangeCarrier;
|
|
164
|
+
/** Optional authenticated/encrypted config; the handshake runs once at bring-up when set. */
|
|
165
|
+
secureChannel?: ISecureClientConfig;
|
|
166
|
+
}
|
|
167
|
+
interface IActionTransportInitialized_Exchange extends IActionTransportInitialized<ITransportRouteActionParams, IActionTransportReadyData_Exchange> {}
|
|
168
|
+
interface IActionTransportDef_Exchange extends IActionTransportDef<ETransportShape.exchange, IActionTransportInitialized_Exchange> {}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/ActionRuntime/Transport/Exchange/ExchangeConnection.d.ts
|
|
171
|
+
/**
|
|
172
|
+
* Carrier-agnostic live connection for the exchange (request → single reply) shape — the HTTP
|
|
173
|
+
* counterpart to {@link LinkConnection}. It owns only the bring-up (run the secure handshake on first
|
|
174
|
+
* use); the request/reply lifecycle + crypto live in the shared `establishExchangeSession`.
|
|
175
|
+
*/
|
|
176
|
+
declare class ExchangeConnection extends TransportConnection<ETransportShape.exchange, ITransportRouteActionParams, IActionTransportReadyData_Exchange, IActionTransportInitialized_Exchange, IActionTransportDef_Exchange> {
|
|
177
|
+
constructor(def: Omit<IActionTransportDef_Exchange, "type">);
|
|
178
|
+
protected _getCacheKey(input: ITransportRouteActionParams): string;
|
|
179
|
+
protected _needsAsyncBringUp(data: IActionTransportReadyData_Exchange): boolean;
|
|
180
|
+
protected _finalizeReady(data: IActionTransportReadyData_Exchange): IActionTransportReadyData_Methods | Promise<IActionTransportReadyData_Methods>;
|
|
181
|
+
_finalizeTransportMethods(data: IActionTransportReadyData_Exchange): IActionTransportReadyData_Methods;
|
|
182
|
+
private _sessionContext;
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/ActionRuntime/Transport/Exchange/ExchangeTransport.d.ts
|
|
186
|
+
interface IExchangeTransportOptions {
|
|
187
|
+
/** Open (or reuse) the exchange carrier for an action — e.g. `httpCarrier(...).open`. */
|
|
188
|
+
openCarrier: (input: ITransportRouteActionParams) => IExchangeCarrier;
|
|
189
|
+
/** Secure config; when set (and `securityLevel !== none`) the handshake runs once at bring-up. */
|
|
190
|
+
security?: ISecureClientConfig;
|
|
191
|
+
updateRunConfig?: TUpdateActionRunConfig;
|
|
192
|
+
/** Keys identifying a reusable session, so one carrier is shared across actions to the same peer. */
|
|
193
|
+
getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
|
|
194
|
+
/**
|
|
195
|
+
* Optional availability gate. When it returns `false`, the manager skips this transport for that action
|
|
196
|
+
* (reporting `unsupported`) and falls through to the next — without opening the carrier or computing its
|
|
197
|
+
* cache key. Re-evaluated per dispatch, so the transport can become available later with no reconnect.
|
|
198
|
+
*/
|
|
199
|
+
available?: (input: ITransportRouteActionParams) => boolean;
|
|
200
|
+
/** Short label for the devtools chip (defaults to "exchange"). */
|
|
201
|
+
label?: string;
|
|
202
|
+
getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* A carrier-agnostic exchange (request → single reply) transport: it drives nice-action's secure session
|
|
206
|
+
* over any {@link IExchangeCarrier} (HTTP being the one built-in). The duplex counterpart is
|
|
207
|
+
* {@link LinkTransport}; this is the no-push half — its reply rides the response to its own request, so it
|
|
208
|
+
* can't deliver an unsolicited frame (the runtime never picks it for the return path).
|
|
209
|
+
*/
|
|
210
|
+
declare class ExchangeTransport extends Transport<ETransportShape.exchange> {
|
|
211
|
+
private readonly options;
|
|
212
|
+
readonly type = ETransportShape.exchange;
|
|
213
|
+
constructor(options: IExchangeTransportOptions);
|
|
214
|
+
static create(options: IExchangeTransportOptions): ExchangeTransport;
|
|
215
|
+
_createConnection(_ctx: ITransportConnectionContext): ExchangeConnection;
|
|
216
|
+
getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/ActionRuntime/Transport/Link/TransportLink.types.d.ts
|
|
220
|
+
/** The per-connection codec (positional binary wire / JSON fallback) the carrier's session uses. */
|
|
221
|
+
type TLinkFormatMessage = IActionWireFormat;
|
|
222
|
+
interface IActionTransportReadyData_Link extends IActionTransportReadyData_Base {
|
|
223
|
+
/** The live carrier this connection drives its session over. */
|
|
224
|
+
channel: IDuplexCarrier;
|
|
225
|
+
formatMessage?: TLinkFormatMessage;
|
|
226
|
+
/** Optional authenticated/encrypted channel; the connection runs the handshake during init. */
|
|
227
|
+
secureChannel?: ISecureClientConfig;
|
|
228
|
+
}
|
|
229
|
+
interface IActionTransportInitialized_Link extends IActionTransportInitialized<ITransportRouteActionParams, IActionTransportReadyData_Link> {}
|
|
230
|
+
interface IActionTransportDef_Link extends IActionTransportDef<ETransportShape.duplex, IActionTransportInitialized_Link> {}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/ActionRuntime/Transport/Link/LinkConnection.d.ts
|
|
233
|
+
/**
|
|
234
|
+
* Carrier-agnostic live connection. It owns only the *bring-up* (open the carrier, then run the secure
|
|
235
|
+
* session); the session itself — handshake, frame crypto, codec, send/receive — lives in the shared
|
|
236
|
+
* {@link finalizeSecureLinkMethods}/{@link finalizePlainLinkMethods}, so a WebSocket, a WebRTC data
|
|
237
|
+
* channel, a Bluetooth characteristic, and an in-memory pipe all run the identical secure layer.
|
|
238
|
+
*/
|
|
239
|
+
declare class LinkConnection extends TransportConnection<ETransportShape.duplex, ITransportRouteActionParams, IActionTransportReadyData_Link, IActionTransportInitialized_Link, IActionTransportDef_Link> {
|
|
240
|
+
private resolvers;
|
|
241
|
+
constructor(def: Omit<IActionTransportDef_Link, "type">, resolvers?: IActionTransportResolvers);
|
|
242
|
+
protected _getCacheKey(input: ITransportRouteActionParams): string;
|
|
243
|
+
protected _needsAsyncBringUp(): boolean;
|
|
244
|
+
protected _awaitCarrierReady(data: IActionTransportReadyData_Link): Promise<void>;
|
|
245
|
+
protected _finalizeReady(data: IActionTransportReadyData_Link): IActionTransportReadyData_Methods | Promise<IActionTransportReadyData_Methods>;
|
|
246
|
+
private _sessionContext;
|
|
247
|
+
_finalizeTransportMethods(data: IActionTransportReadyData_Link): IActionTransportReadyData_Methods;
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/ActionRuntime/Transport/Link/LinkTransport.d.ts
|
|
251
|
+
interface ILinkTransportOptions {
|
|
252
|
+
/**
|
|
253
|
+
* Open (or reuse) the carrier for an action — a WebSocket adapter, a WebRTC data channel, a Bluetooth
|
|
254
|
+
* characteristic, an in-memory pipe, anything that satisfies {@link IDuplexCarrier}.
|
|
255
|
+
*/
|
|
256
|
+
openChannel: (input: ITransportRouteActionParams) => IDuplexCarrier;
|
|
257
|
+
/** Shared codec for every channel (stateless). */
|
|
258
|
+
formatMessage?: TLinkFormatMessage;
|
|
259
|
+
/**
|
|
260
|
+
* Per-channel codec factory — called once per opened channel so stateful codecs (e.g. the binary
|
|
261
|
+
* session) get their own instance. Takes precedence over `formatMessage`.
|
|
262
|
+
*/
|
|
263
|
+
createFormatMessage?: () => TLinkFormatMessage;
|
|
264
|
+
/** Secure-channel config; when set (and `securityLevel !== none`) the handshake runs on init. */
|
|
265
|
+
security?: ISecureClientConfig;
|
|
266
|
+
updateRunConfig?: TUpdateActionRunConfig;
|
|
267
|
+
/** Keys identifying a reusable channel, so one carrier is shared across actions to the same peer. */
|
|
268
|
+
getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
|
|
269
|
+
/**
|
|
270
|
+
* Optional availability gate. When it returns `false`, the manager skips this transport for that action
|
|
271
|
+
* (reporting `unsupported`) and falls through to the next — without opening the carrier or computing its
|
|
272
|
+
* cache key. Re-evaluated per dispatch, so the transport can become available later with no reconnect.
|
|
273
|
+
*/
|
|
274
|
+
available?: (input: ITransportRouteActionParams) => boolean;
|
|
275
|
+
/** Short label for the devtools chip (defaults to "link"). */
|
|
276
|
+
label?: string;
|
|
277
|
+
getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* A carrier-agnostic transport: it drives nice-action's secure session + action routing over any
|
|
281
|
+
* {@link IDuplexCarrier}. The WebSocket transport is the special case that opens a `WebSocket`;
|
|
282
|
+
* this opens whatever `openChannel` returns, so the identical secure layer works over WebRTC, Bluetooth,
|
|
283
|
+
* or an in-memory pipe. Reported with an overridable carrier label in the devtools (defaults to "link").
|
|
284
|
+
*/
|
|
285
|
+
declare class LinkTransport extends Transport<ETransportShape.duplex> {
|
|
286
|
+
private readonly options;
|
|
287
|
+
readonly type = ETransportShape.duplex;
|
|
288
|
+
constructor(options: ILinkTransportOptions);
|
|
289
|
+
static create(options: ILinkTransportOptions): LinkTransport;
|
|
290
|
+
_createConnection(ctx: ITransportConnectionContext): LinkConnection;
|
|
291
|
+
getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/ActionRuntime/Transport/SecureSession/exchangeProtocol.d.ts
|
|
295
|
+
/**
|
|
296
|
+
* The application-level envelope for secure action traffic over an {@link IExchangeCarrier} (HTTP). An
|
|
297
|
+
* exchange carrier only moves one request frame → one reply frame with no unsolicited push, so the
|
|
298
|
+
* handshake and the per-action token + crypto all ride in this envelope (a JSON string body) rather than
|
|
299
|
+
* on a persistent channel. The three security levels share it:
|
|
300
|
+
*
|
|
301
|
+
* - `none` — no handshake, no token: an `act` envelope carries the plaintext wire both ways.
|
|
302
|
+
* - `authenticated` — a one-time handshake yields a session `token`; each later `act` carries it +
|
|
303
|
+
* the plaintext wire.
|
|
304
|
+
* - `encrypted` — same, but the wire is AES-GCM ciphertext, base64 in the `c` field.
|
|
305
|
+
*
|
|
306
|
+
* The handshake runs as two `hs` exchanges (hello→welcome, prove→accept) correlated by a client-chosen
|
|
307
|
+
* `hsid`, since stateless requests can't rely on channel ordering. The `accept` reply carries the token.
|
|
308
|
+
*/
|
|
309
|
+
type TWireJson = TActionPayload_Any_JsonObject<any, any>;
|
|
310
|
+
/** Connector → acceptor request envelope. */
|
|
311
|
+
type TExchangeRequest = {
|
|
312
|
+
k: "hs";
|
|
313
|
+
hsid: string;
|
|
314
|
+
m: string;
|
|
315
|
+
} | {
|
|
316
|
+
k: "act";
|
|
317
|
+
t?: string;
|
|
318
|
+
w: TWireJson;
|
|
319
|
+
} | {
|
|
320
|
+
k: "act";
|
|
321
|
+
t?: string;
|
|
322
|
+
c: string;
|
|
323
|
+
};
|
|
324
|
+
/** Acceptor → connector reply envelope. */
|
|
325
|
+
type TExchangeReply = {
|
|
326
|
+
k: "hs";
|
|
327
|
+
m: string;
|
|
328
|
+
t?: string;
|
|
329
|
+
} | {
|
|
330
|
+
k: "act";
|
|
331
|
+
w: TWireJson;
|
|
332
|
+
} | {
|
|
333
|
+
k: "act";
|
|
334
|
+
c: string;
|
|
335
|
+
} | {
|
|
336
|
+
k: "err";
|
|
337
|
+
message: string;
|
|
338
|
+
};
|
|
339
|
+
declare function encodeExchange(envelope: TExchangeRequest | TExchangeReply): string;
|
|
340
|
+
declare function decodeExchangeRequest(raw: string): TExchangeRequest | undefined;
|
|
341
|
+
declare function decodeExchangeReply(raw: string): TExchangeReply | undefined;
|
|
342
|
+
//#endregion
|
|
343
|
+
export { AcceptorHandler, ConnectionStateStore, ConnectorHandler, EHandshakeMessageType, ETransportShape, ETransportStatus, ExchangeAcceptor, ExchangeTransport, type IAcceptorConnectionBinding, type IAcceptorHandlerOptions, type IActionFetchHandlerOptions, type IActionFrameCrypto, type IActionFrameCryptoConfig, IActionTransportDef, IActionTransportInitialized, IActionTransportReady, IActionTransportReadyData_Base, type IActionTransportReadyData_Exchange, type IActionTransportReadyData_Link, IActionTransportReadyData_Methods, IActionTransportResolvers, type IActionWireFormat, type IBinaryWireSessionOptions, type IClientHandshakeConfig, type IConnectionAttachment, type IConnectionStateStoreOptions, type IDuplexConnectionRouter, type IExchangeAcceptorConfig, type IExchangeAcceptorSecurity, type IExchangeTransportOptions, type IHandshakeEncryptionKeyMaterial, type IHandshakeResult, type IHibernatableWsServerAdapterOptions, type ILinkTransportOptions, type ISecureAcceptorHandlerOptions, ISecureClientConfig, type IServerHandshakeConfig, type ITransportConnectionContext, ITransportDispatchAction, ITransportMethod_SendActionData_Input, ITransportRouteActionParams, ITransportRouteClientParams, ITransportRouteInfo, ITransportStatusInfo_Base, ITransportStatusInfo_Failed, ITransportStatusInfo_Initializing, ITransportStatusInfo_Ready, ITransportStatusInfo_Unsupported, IUpdateActionRunConfig_Output, LinkTransport, PeerLinkHandler, type TAcceptorCaseFn, type TAcceptorConnectionCaseFn, type TActionChannelFormatMessage, type TActionConnectionEncoding, type TExchangeReply, type TExchangeRequest, TGetTransportFn, type THandshakeMessage, type TLinkFormatMessage, TOnResolveAnyIncomingActionData, TOnResolveAnyIncomingActionData_Json, TOnResolveIncomingRequest, TOnResolveIncomingRequestJson, TOnResolveIncomingResponse, TOnResolveIncomingResponseJson, TSendActionDataMethod, TSendReturnDataMethod, TTransportCache, TTransportInitializationFinishedInfo, TTransportStatusInfo, TTransportStatusInfo_GetTransport_Output, TUpdateActionRunConfig, Transport, createAcceptorHandler, createActionFetchHandler, createActionFrameCrypto, createBinaryWireAdapter, createBinaryWireSessionFactory, createClientHandshake, createConnectionStateStore, createConnectorHandler, createHibernatableWsServerAdapter, createSecureAcceptorHandler, createServerHandshake, decodeExchangeReply, decodeExchangeRequest, decodeHandshakeMessage, encodeExchange, encodeHandshakeMessage };
|
|
344
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { A as encodeHandshakeMessage, C as EHandshakeMessageType, D as createServerHandshake, F as ConnectorHandler, I as createConnectorHandler, L as PeerLinkHandler, R as ETransportShape, T as createClientHandshake, _ as extractWirePayload, a as ExchangeAcceptor, b as createAcceptorHandler, c as decodeExchangeReply, d as Transport, f as createBinaryWireSessionFactory, g as buildActionRouteDictionary, h as assembleWireJson, i as createActionFetchHandler, k as decodeHandshakeMessage, l as decodeExchangeRequest, m as ReversePayloadType, n as ConnectionStateStore, o as LinkTransport, p as PayloadTypeToInt, r as createConnectionStateStore, s as ExchangeTransport, t as createHibernatableWsServerAdapter, u as encodeExchange, v as createSecureAcceptorHandler, x as createActionFrameCrypto, y as AcceptorHandler, z as ETransportStatus } from "../createHibernatableWsServerAdapter-C07RfUTH.mjs";
|
|
2
|
+
import { pack, unpack } from "msgpackr";
|
|
3
|
+
//#region src/ActionRuntime/Transport/codec/createBinaryWireAdapter.ts
|
|
4
|
+
/**
|
|
5
|
+
* Positional layout of the stateless binary envelope. A flat tuple (rather than an object) strips the
|
|
6
|
+
* repeated `domain`/`id`/`form`/`type` and context key names from every frame, and we carry only the
|
|
7
|
+
* context fields the receiver can't recompute: `cuid` (correlation) and `originClient` (return
|
|
8
|
+
* routing).
|
|
9
|
+
*
|
|
10
|
+
* [ routeInt, typeInt, time, cuid, originClient, payloadData ]
|
|
11
|
+
*
|
|
12
|
+
* Dropped vs the JSON wire: `form`/`type` strings, `inputHash`/`outputHash` (recomputed on hydrate),
|
|
13
|
+
* `context.timeCreated` (reconstructed from `time`) and `context.routing` (rebuilt empty — the
|
|
14
|
+
* receiver re-stamps its own route items as it handles the action). For the leanest possible frames
|
|
15
|
+
* (integer correlation, identity dropped after a handshake), use `createBinaryWireSessionFactory`.
|
|
16
|
+
*/
|
|
17
|
+
const ENVELOPE = {
|
|
18
|
+
route: 0,
|
|
19
|
+
type: 1,
|
|
20
|
+
time: 2,
|
|
21
|
+
cuid: 3,
|
|
22
|
+
originClient: 4,
|
|
23
|
+
payload: 5
|
|
24
|
+
};
|
|
25
|
+
const ENVELOPE_LENGTH = 6;
|
|
26
|
+
/**
|
|
27
|
+
* Builds a *stateless* `formatMessage` pipeline for {@link LinkTransport}, packing action
|
|
28
|
+
* payloads into a compact msgpackr binary frame instead of JSON. The `domain`/`id` route collapses to
|
|
29
|
+
* a single integer drawn from a shared dictionary; `form`/`type`, the recomputable
|
|
30
|
+
* `inputHash`/`outputHash`, and the per-frame `context.routing`/`context.timeCreated` are all dropped
|
|
31
|
+
* (see {@link ENVELOPE}).
|
|
32
|
+
*
|
|
33
|
+
* No validation runs here: `incoming` blindly reconstructs the wire JSON shape and hands it back to
|
|
34
|
+
* the connection, which flows into `ActionRuntime` → `domain.hydrateAnyAction()` where the Valibot
|
|
35
|
+
* schemas validate it exactly as they would for a JSON frame.
|
|
36
|
+
*
|
|
37
|
+
* Both ends of the socket MUST construct the adapter with the same domains in the same order — the
|
|
38
|
+
* integer dictionary is positional. Mismatched dictionaries will route to the wrong action.
|
|
39
|
+
*
|
|
40
|
+
* Because `incoming` returns `undefined` for text frames, a binary server can still serve plain-JSON
|
|
41
|
+
* clients on the same runtime (the connection falls back to its built-in JSON parser).
|
|
42
|
+
*/
|
|
43
|
+
function createBinaryWireAdapter(domains) {
|
|
44
|
+
const { routeToInt, intToRoute } = buildActionRouteDictionary(domains);
|
|
45
|
+
return {
|
|
46
|
+
outgoing: (input) => {
|
|
47
|
+
const json = input.action.toJsonObject();
|
|
48
|
+
const routeKey = `${json.domain}:${json.id}`;
|
|
49
|
+
const routeInt = routeToInt.get(routeKey);
|
|
50
|
+
if (routeInt == null) throw new Error(`[binary-wire] Cannot pack unregistered action route: ${routeKey}`);
|
|
51
|
+
const envelope = new Array(ENVELOPE_LENGTH);
|
|
52
|
+
envelope[ENVELOPE.route] = routeInt;
|
|
53
|
+
envelope[ENVELOPE.type] = PayloadTypeToInt[json.type];
|
|
54
|
+
envelope[ENVELOPE.time] = json.time;
|
|
55
|
+
envelope[ENVELOPE.cuid] = json.context.cuid;
|
|
56
|
+
envelope[ENVELOPE.originClient] = json.context.originClient;
|
|
57
|
+
envelope[ENVELOPE.payload] = extractWirePayload(json);
|
|
58
|
+
return pack(envelope);
|
|
59
|
+
},
|
|
60
|
+
incoming: (frame) => {
|
|
61
|
+
let buffer;
|
|
62
|
+
if (frame instanceof ArrayBuffer) buffer = new Uint8Array(frame);
|
|
63
|
+
else if (frame instanceof Uint8Array) buffer = frame;
|
|
64
|
+
else return;
|
|
65
|
+
try {
|
|
66
|
+
const envelope = unpack(buffer);
|
|
67
|
+
if (!Array.isArray(envelope) || envelope.length !== ENVELOPE_LENGTH) return void 0;
|
|
68
|
+
const routeMeta = intToRoute[envelope[ENVELOPE.route]];
|
|
69
|
+
const payloadType = ReversePayloadType[envelope[ENVELOPE.type]];
|
|
70
|
+
if (routeMeta == null || payloadType == null) return void 0;
|
|
71
|
+
const time = envelope[ENVELOPE.time];
|
|
72
|
+
return assembleWireJson(routeMeta, payloadType, time, {
|
|
73
|
+
cuid: envelope[ENVELOPE.cuid],
|
|
74
|
+
timeCreated: time,
|
|
75
|
+
routing: [],
|
|
76
|
+
originClient: envelope[ENVELOPE.originClient]
|
|
77
|
+
}, envelope[ENVELOPE.payload]);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error("[binary-wire] Failed to unpack binary action frame", e);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { AcceptorHandler, ConnectionStateStore, ConnectorHandler, EHandshakeMessageType, ETransportShape, ETransportStatus, ExchangeAcceptor, ExchangeTransport, LinkTransport, PeerLinkHandler, Transport, createAcceptorHandler, createActionFetchHandler, createActionFrameCrypto, createBinaryWireAdapter, createBinaryWireSessionFactory, createClientHandshake, createConnectionStateStore, createConnectorHandler, createHibernatableWsServerAdapter, createSecureAcceptorHandler, createServerHandshake, decodeExchangeReply, decodeExchangeRequest, decodeHandshakeMessage, encodeExchange, encodeHandshakeMessage };
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/ActionRuntime/Transport/codec/createBinaryWireAdapter.ts"],"sourcesContent":["import { pack, unpack } from \"msgpackr\";\r\nimport type { ActionDomain } from \"../../../ActionDefinition/Domain/ActionDomain\";\r\nimport type { ITransportRouteActionParams } from \"../Transport.types\";\r\nimport {\r\n assembleWireJson,\r\n buildActionRouteDictionary,\r\n extractWirePayload,\r\n type IActionWireFormat,\r\n PayloadTypeToInt,\r\n ReversePayloadType,\r\n} from \"./actionWireCodec\";\r\n\r\n/**\r\n * Positional layout of the stateless binary envelope. A flat tuple (rather than an object) strips the\r\n * repeated `domain`/`id`/`form`/`type` and context key names from every frame, and we carry only the\r\n * context fields the receiver can't recompute: `cuid` (correlation) and `originClient` (return\r\n * routing).\r\n *\r\n * [ routeInt, typeInt, time, cuid, originClient, payloadData ]\r\n *\r\n * Dropped vs the JSON wire: `form`/`type` strings, `inputHash`/`outputHash` (recomputed on hydrate),\r\n * `context.timeCreated` (reconstructed from `time`) and `context.routing` (rebuilt empty — the\r\n * receiver re-stamps its own route items as it handles the action). For the leanest possible frames\r\n * (integer correlation, identity dropped after a handshake), use `createBinaryWireSessionFactory`.\r\n */\r\nconst ENVELOPE = {\r\n route: 0,\r\n type: 1,\r\n time: 2,\r\n cuid: 3,\r\n originClient: 4,\r\n payload: 5,\r\n} as const;\r\nconst ENVELOPE_LENGTH = 6;\r\n\r\n/**\r\n * Builds a *stateless* `formatMessage` pipeline for {@link LinkTransport}, packing action\r\n * payloads into a compact msgpackr binary frame instead of JSON. The `domain`/`id` route collapses to\r\n * a single integer drawn from a shared dictionary; `form`/`type`, the recomputable\r\n * `inputHash`/`outputHash`, and the per-frame `context.routing`/`context.timeCreated` are all dropped\r\n * (see {@link ENVELOPE}).\r\n *\r\n * No validation runs here: `incoming` blindly reconstructs the wire JSON shape and hands it back to\r\n * the connection, which flows into `ActionRuntime` → `domain.hydrateAnyAction()` where the Valibot\r\n * schemas validate it exactly as they would for a JSON frame.\r\n *\r\n * Both ends of the socket MUST construct the adapter with the same domains in the same order — the\r\n * integer dictionary is positional. Mismatched dictionaries will route to the wrong action.\r\n *\r\n * Because `incoming` returns `undefined` for text frames, a binary server can still serve plain-JSON\r\n * clients on the same runtime (the connection falls back to its built-in JSON parser).\r\n */\r\nexport function createBinaryWireAdapter(domains: ActionDomain<any>[]): IActionWireFormat {\r\n const { routeToInt, intToRoute } = buildActionRouteDictionary(domains);\r\n\r\n return {\r\n outgoing: (input: ITransportRouteActionParams): Uint8Array => {\r\n const json = input.action.toJsonObject();\r\n const routeKey = `${json.domain}:${json.id}`;\r\n const routeInt = routeToInt.get(routeKey);\r\n\r\n if (routeInt == null) {\r\n throw new Error(`[binary-wire] Cannot pack unregistered action route: ${routeKey}`);\r\n }\r\n\r\n const envelope = new Array(ENVELOPE_LENGTH);\r\n envelope[ENVELOPE.route] = routeInt;\r\n envelope[ENVELOPE.type] = PayloadTypeToInt[json.type];\r\n envelope[ENVELOPE.time] = json.time;\r\n envelope[ENVELOPE.cuid] = json.context.cuid;\r\n envelope[ENVELOPE.originClient] = json.context.originClient;\r\n envelope[ENVELOPE.payload] = extractWirePayload(json);\r\n\r\n return pack(envelope);\r\n },\r\n\r\n incoming: (frame: string | ArrayBuffer | Uint8Array | Blob) => {\r\n // Only binary frames are ours. Text frames fall through to the JSON parser; Blobs should have\r\n // been converted to a buffer by the connection before reaching us — if not, we can't unpack\r\n // them synchronously, so defer.\r\n let buffer: Uint8Array;\r\n if (frame instanceof ArrayBuffer) {\r\n buffer = new Uint8Array(frame);\r\n } else if (frame instanceof Uint8Array) {\r\n buffer = frame;\r\n } else {\r\n return undefined;\r\n }\r\n\r\n try {\r\n const envelope = unpack(buffer);\r\n\r\n if (!Array.isArray(envelope) || envelope.length !== ENVELOPE_LENGTH) return undefined;\r\n\r\n const routeMeta = intToRoute[envelope[ENVELOPE.route]];\r\n const payloadType = ReversePayloadType[envelope[ENVELOPE.type]];\r\n if (routeMeta == null || payloadType == null) return undefined;\r\n\r\n const time = envelope[ENVELOPE.time];\r\n // Rebuild the context: `routing` starts empty (the receiver re-stamps its own hops) and\r\n // `timeCreated` is approximated by the payload `time` — neither affects hydration/validation.\r\n const context = {\r\n cuid: envelope[ENVELOPE.cuid],\r\n timeCreated: time,\r\n routing: [],\r\n originClient: envelope[ENVELOPE.originClient],\r\n };\r\n\r\n return assembleWireJson(routeMeta, payloadType, time, context, envelope[ENVELOPE.payload]);\r\n } catch (e) {\r\n console.error(\"[binary-wire] Failed to unpack binary action frame\", e);\r\n return undefined;\r\n }\r\n },\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,MAAM,WAAW;CACf,OAAO;CACP,MAAM;CACN,MAAM;CACN,MAAM;CACN,cAAc;CACd,SAAS;AACX;AACA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;AAmBxB,SAAgB,wBAAwB,SAAiD;CACvF,MAAM,EAAE,YAAY,eAAe,2BAA2B,OAAO;CAErE,OAAO;EACL,WAAW,UAAmD;GAC5D,MAAM,OAAO,MAAM,OAAO,aAAa;GACvC,MAAM,WAAW,GAAG,KAAK,OAAO,GAAG,KAAK;GACxC,MAAM,WAAW,WAAW,IAAI,QAAQ;GAExC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,wDAAwD,UAAU;GAGpF,MAAM,WAAW,IAAI,MAAM,eAAe;GAC1C,SAAS,SAAS,SAAS;GAC3B,SAAS,SAAS,QAAQ,iBAAiB,KAAK;GAChD,SAAS,SAAS,QAAQ,KAAK;GAC/B,SAAS,SAAS,QAAQ,KAAK,QAAQ;GACvC,SAAS,SAAS,gBAAgB,KAAK,QAAQ;GAC/C,SAAS,SAAS,WAAW,mBAAmB,IAAI;GAEpD,OAAO,KAAK,QAAQ;EACtB;EAEA,WAAW,UAAoD;GAI7D,IAAI;GACJ,IAAI,iBAAiB,aACnB,SAAS,IAAI,WAAW,KAAK;QACxB,IAAI,iBAAiB,YAC1B,SAAS;QAET;GAGF,IAAI;IACF,MAAM,WAAW,OAAO,MAAM;IAE9B,IAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,iBAAiB,OAAO,KAAA;IAE5E,MAAM,YAAY,WAAW,SAAS,SAAS;IAC/C,MAAM,cAAc,mBAAmB,SAAS,SAAS;IACzD,IAAI,aAAa,QAAQ,eAAe,MAAM,OAAO,KAAA;IAErD,MAAM,OAAO,SAAS,SAAS;IAU/B,OAAO,iBAAiB,WAAW,aAAa,MAAM;KANpD,MAAM,SAAS,SAAS;KACxB,aAAa;KACb,SAAS,CAAC;KACV,cAAc,SAAS,SAAS;IAG0B,GAAG,SAAS,SAAS,QAAQ;GAC3F,SAAS,GAAG;IACV,QAAQ,MAAM,sDAAsD,CAAC;IACrE;GACF;EACF;CACF;AACF"}
|