@nice-code/action 0.25.0 → 0.26.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.
Files changed (39) hide show
  1. package/README.md +52 -0
  2. package/build/{AcceptorHandler-BizUtq4u.d.mts → AcceptorHandler-CLbwu2Pa.d.mts} +74 -16
  3. package/build/{AcceptorHandler-CxPfZtIl.d.cts → AcceptorHandler-Du292dpC.d.cts} +74 -16
  4. package/build/{ActionDevtoolsCore-D9KBBI2V.d.cts → ActionDevtoolsCore-DGwzONZT.d.mts} +2 -2
  5. package/build/{ActionDevtoolsCore-xZjAtB4H.d.mts → ActionDevtoolsCore-dH4K4w3B.d.cts} +2 -2
  6. package/build/advanced/index.cjs +1 -1
  7. package/build/advanced/index.d.cts +12 -6
  8. package/build/advanced/index.d.mts +12 -6
  9. package/build/advanced/index.mjs +1 -1
  10. package/build/{createHibernatableWsServerAdapter-BkjESd01.mjs → createHibernatableWsServerAdapter-BD5n-Ev9.mjs} +176 -75
  11. package/build/createHibernatableWsServerAdapter-BD5n-Ev9.mjs.map +1 -0
  12. package/build/{createHibernatableWsServerAdapter-FSDWrxoF.cjs → createHibernatableWsServerAdapter-j96U9vgo.cjs} +175 -74
  13. package/build/{createHibernatableWsServerAdapter-BkjESd01.mjs.map → createHibernatableWsServerAdapter-j96U9vgo.cjs.map} +1 -1
  14. package/build/devtools/browser/index.cjs.map +1 -1
  15. package/build/devtools/browser/index.d.cts +1 -1
  16. package/build/devtools/browser/index.d.mts +1 -1
  17. package/build/devtools/browser/index.mjs.map +1 -1
  18. package/build/devtools/server/index.d.cts +1 -1
  19. package/build/devtools/server/index.d.mts +1 -1
  20. package/build/{httpAcceptorCarrier-BQYaXI9j.cjs → httpAcceptorCarrier-By0Qa__L.cjs} +2 -2
  21. package/build/httpAcceptorCarrier-By0Qa__L.cjs.map +1 -0
  22. package/build/{httpAcceptorCarrier-DWqsCz3h.mjs → httpAcceptorCarrier-moSmtBxr.mjs} +2 -2
  23. package/build/httpAcceptorCarrier-moSmtBxr.mjs.map +1 -0
  24. package/build/index.cjs +2 -2
  25. package/build/index.cjs.map +1 -1
  26. package/build/index.d.cts +1 -1
  27. package/build/index.d.mts +1 -1
  28. package/build/index.mjs +2 -2
  29. package/build/index.mjs.map +1 -1
  30. package/build/platform/cloudflare/index.cjs +1 -1
  31. package/build/platform/cloudflare/index.d.cts +1 -1
  32. package/build/platform/cloudflare/index.d.mts +1 -1
  33. package/build/platform/cloudflare/index.mjs +1 -1
  34. package/build/react-query/index.d.cts +1 -1
  35. package/build/react-query/index.d.mts +1 -1
  36. package/package.json +5 -4
  37. package/build/createHibernatableWsServerAdapter-FSDWrxoF.cjs.map +0 -1
  38. package/build/httpAcceptorCarrier-BQYaXI9j.cjs.map +0 -1
  39. package/build/httpAcceptorCarrier-DWqsCz3h.mjs.map +0 -1
@@ -3,7 +3,7 @@ import { nanoid } from "nanoid";
3
3
  import { NiceError, castNiceError, err, err_nice, isNiceErrorObject } from "@nice-code/error";
4
4
  import { extractMessageFromStandardSchema } from "@nice-code/common-errors";
5
5
  import { runtime } from "std-env";
6
- import { ClientCryptoKeyLink, createTypedStorage } from "@nice-code/util";
6
+ import { ClientCryptoKeyLink, createTypedStorage, decryptBytesWithAesGcmKey, encryptBytesWithAesGcmKey } from "@nice-code/util";
7
7
  import * as v from "valibot";
8
8
  import { pack, unpack } from "msgpackr";
9
9
  const UNSET_RUNTIME_ENV_ID = "_unset_";
@@ -1732,6 +1732,16 @@ function createInMemoryTofuVerifyKeyResolver() {
1732
1732
  * Storage-backed trust-on-first-use resolver: pins survive process restarts / Durable Object eviction
1733
1733
  * (e.g. back it with `createDurableObjectStorageAdapter`). Same policy as the in-memory variant — trust
1734
1734
  * + pin the first verify key per client identity, reject a different one thereafter.
1735
+ *
1736
+ * Fail-closed by construction: a thrown storage read (`getJson`) or first-pin write (`updateJsonWithDef`)
1737
+ * propagates out of `resolve`, which makes `onProve` reject the handshake — a storage error can never be
1738
+ * mistaken for "first use" and silently trusted. (A genuine `undefined`/absent read is the only path to
1739
+ * a fresh pin; the underlying adapters never coerce a thrown read to `undefined`.) Keep it that way: do
1740
+ * not wrap the storage calls in a `try/catch` that swallows the error.
1741
+ *
1742
+ * On an *eventually-consistent* store a stale "absent" read re-pins the **same** verify key the client
1743
+ * just presented (it is signature-verified before this runs), so the worst case is a harmless re-write,
1744
+ * never a weakened trust decision. Cross-isolate-strong pinning still wants a strongly-consistent store.
1735
1745
  */
1736
1746
  function createStorageTofuVerifyKeyResolver(storageAdapter) {
1737
1747
  const storage = createTypedStorage({ storageAdapter });
@@ -1785,6 +1795,13 @@ function createClientHandshake(config) {
1785
1795
  bindVerifyKeysIntoDerivation: true
1786
1796
  } : {}
1787
1797
  });
1798
+ const encryptionKeyMaterial = wantsEncryption && welcome.exchangePublicKey != null ? {
1799
+ verifyPublicKey: welcome.verifyPublicKey,
1800
+ exchangePublicKey: welcome.exchangePublicKey,
1801
+ saltString: sessionSalt(clientNonce, welcome.serverNonce),
1802
+ infoString: handshakeInfo(dictionaryVersion),
1803
+ bindVerifyKeysIntoDerivation: true
1804
+ } : void 0;
1788
1805
  const challenge = buildHandshakeChallenge({
1789
1806
  securityLevel,
1790
1807
  dictionaryVersion,
@@ -1800,7 +1817,8 @@ function createClientHandshake(config) {
1800
1817
  pending = {
1801
1818
  linkedServerId,
1802
1819
  server: welcome.server,
1803
- challenge
1820
+ challenge,
1821
+ encryptionKeyMaterial
1804
1822
  };
1805
1823
  return {
1806
1824
  t: "prove",
@@ -1819,7 +1837,8 @@ function createClientHandshake(config) {
1819
1837
  return {
1820
1838
  linkedClientId: pending.linkedServerId,
1821
1839
  remote: pending.server,
1822
- securityLevel
1840
+ securityLevel,
1841
+ encryptionKeyMaterial: pending.encryptionKeyMaterial
1823
1842
  };
1824
1843
  }
1825
1844
  };
@@ -1914,6 +1933,16 @@ function createServerHandshake(config) {
1914
1933
  /** The completed handshake result once `onProve` has accepted, else `undefined`. */
1915
1934
  getResult() {
1916
1935
  return result;
1936
+ },
1937
+ exportPending() {
1938
+ return pending;
1939
+ },
1940
+ async restorePending(restored) {
1941
+ pending = restored;
1942
+ await link.linkClient({
1943
+ linkedClientId: restored.linkedClientId,
1944
+ verifyPublicKey: restored.clientVerifyKey
1945
+ });
1917
1946
  }
1918
1947
  };
1919
1948
  }
@@ -1944,14 +1973,16 @@ function parseJsonActionFrame(message) {
1944
1973
  const ENCRYPTED_ENVELOPE_LENGTH = 2;
1945
1974
  /**
1946
1975
  * Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
1947
- * level. Keyed by the link + `linkedClientId`, so it reuses the cached shared AES-GCM key.
1976
+ * level. The shared key is derived once from {@link IActionFrameCryptoConfig.keyMaterial} and captured
1977
+ * for this connection alone, decoupling it from the link's shared key cache.
1948
1978
  */
1949
- function createActionFrameCrypto({ link, linkedClientId }) {
1979
+ function createActionFrameCrypto({ link, keyMaterial }) {
1980
+ const keyPromise = link.deriveSharedAesGcmKey(keyMaterial);
1950
1981
  return {
1951
1982
  async encryptFrame(frame) {
1952
- const { nonce, ciphertext } = await link.encryptBytesForLinkedClient({
1953
- linkedClientId,
1954
- dataToEncrypt: frame
1983
+ const { nonce, ciphertext } = await encryptBytesWithAesGcmKey({
1984
+ dataToEncrypt: frame,
1985
+ aesGcmKey: await keyPromise
1955
1986
  });
1956
1987
  return pack([nonce, ciphertext]);
1957
1988
  },
@@ -1961,12 +1992,12 @@ function createActionFrameCrypto({ link, linkedClientId }) {
1961
1992
  if (!Array.isArray(envelope) || envelope.length !== ENCRYPTED_ENVELOPE_LENGTH) throw new Error("[ws-crypto] malformed encrypted frame envelope");
1962
1993
  const [nonce, ciphertext] = envelope;
1963
1994
  if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) throw new Error("[ws-crypto] malformed encrypted frame fields");
1964
- return await link.decryptBytesFromLinkedClient({
1965
- linkedClientId,
1995
+ return await decryptBytesWithAesGcmKey({
1966
1996
  dataToDecrypt: {
1967
1997
  nonce,
1968
1998
  ciphertext
1969
- }
1999
+ },
2000
+ aesGcmKey: await keyPromise
1970
2001
  });
1971
2002
  }
1972
2003
  };
@@ -2077,9 +2108,9 @@ var AcceptorSecureSession = class {
2077
2108
  _complete(result) {
2078
2109
  this._authed = true;
2079
2110
  this._handshake = void 0;
2080
- if (result.securityLevel === "encrypted") this._pipe = this._buildPipe(createActionFrameCrypto({
2111
+ if (result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null) this._pipe = this._buildPipe(createActionFrameCrypto({
2081
2112
  link: this.config.link,
2082
- linkedClientId: result.linkedClientId
2113
+ keyMaterial: result.encryptionKeyMaterial
2083
2114
  }));
2084
2115
  this.config.onAuthenticated({
2085
2116
  client: new RuntimeCoordinate(result.remote),
@@ -2098,17 +2129,10 @@ var AcceptorSecureSession = class {
2098
2129
  this._authed = true;
2099
2130
  if (state.securityLevel !== "encrypted" || state.keyMaterial == null) return;
2100
2131
  const { link } = this.config;
2101
- const { linkedClientId, keyMaterial } = state;
2102
- const cryptoReady = link.initialize().then(() => link.linkClient({
2103
- linkedClientId,
2104
- verifyPublicKey: keyMaterial.verifyPublicKey,
2105
- exchangePublicKey: keyMaterial.exchangePublicKey,
2106
- saltString: keyMaterial.saltString,
2107
- infoString: keyMaterial.infoString,
2108
- bindVerifyKeysIntoDerivation: keyMaterial.bindVerifyKeysIntoDerivation
2109
- })).then(() => createActionFrameCrypto({
2132
+ const { keyMaterial } = state;
2133
+ const cryptoReady = link.initialize().then(() => createActionFrameCrypto({
2110
2134
  link,
2111
- linkedClientId
2135
+ keyMaterial
2112
2136
  }));
2113
2137
  cryptoReady.catch((err) => console.error("[ws-server] failed to restore encrypted session", err));
2114
2138
  this._pipe = this._buildPipe(cryptoReady);
@@ -2887,11 +2911,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
2887
2911
  dictionaryVersion: secure.dictionaryVersion,
2888
2912
  securityLevel: secure.securityLevel
2889
2913
  });
2890
- const hsid = nanoid();
2891
2914
  const hello = await handshake.createHello();
2892
2915
  const welcomeReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
2893
2916
  k: "hs",
2894
- hsid,
2895
2917
  m: encodeHandshakeMessage(hello)
2896
2918
  }))));
2897
2919
  if (welcomeReply?.k !== "hs") throw new Error("[exchange-handshake] expected a welcome reply");
@@ -2899,11 +2921,12 @@ async function runConnectorExchangeHandshake(carrier, secure) {
2899
2921
  if (welcome == null) throw new Error("[exchange-handshake] malformed welcome");
2900
2922
  if (welcome.t === "reject") throw new Error(`[exchange-handshake] rejected by peer: ${welcome.reason}`);
2901
2923
  if (welcome.t !== "welcome") throw new Error(`[exchange-handshake] expected welcome, got ${welcome.t}`);
2924
+ if (welcomeReply.hsc == null) throw new Error("[exchange-handshake] welcome missing handshake continuation token");
2902
2925
  const prove = await handshake.onWelcome(welcome);
2903
2926
  const acceptReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
2904
2927
  k: "hs",
2905
- hsid,
2906
- m: encodeHandshakeMessage(prove)
2928
+ m: encodeHandshakeMessage(prove),
2929
+ hsc: welcomeReply.hsc
2907
2930
  }))));
2908
2931
  if (acceptReply?.k !== "hs") throw new Error("[exchange-handshake] expected an accept reply");
2909
2932
  const accept = decodeHandshakeMessage(acceptReply.m);
@@ -2912,9 +2935,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
2912
2935
  if (accept.t !== "accept") throw new Error(`[exchange-handshake] expected accept, got ${accept.t}`);
2913
2936
  if (acceptReply.t == null) throw new Error("[exchange-handshake] accept missing session token");
2914
2937
  const result = await handshake.onAccept(accept);
2915
- const crypto = result.securityLevel === "encrypted" ? createActionFrameCrypto({
2938
+ const crypto = result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
2916
2939
  link: secure.link,
2917
- linkedClientId: result.linkedClientId
2940
+ keyMaterial: result.encryptionKeyMaterial
2918
2941
  }) : void 0;
2919
2942
  return {
2920
2943
  token: acceptReply.t,
@@ -3222,9 +3245,9 @@ async function runClientHandshake(channel, secure, nextHandshakeMessage) {
3222
3245
  if (accept.t === "reject") throw new Error(`[link-handshake] rejected by peer: ${accept.reason}`);
3223
3246
  if (accept.t !== "accept") throw new Error(`[link-handshake] expected accept, got ${accept.t}`);
3224
3247
  const result = await handshake.onAccept(accept);
3225
- return result.securityLevel === "encrypted" ? createActionFrameCrypto({
3248
+ return result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
3226
3249
  link: secure.link,
3227
- linkedClientId: result.linkedClientId
3250
+ keyMaterial: result.encryptionKeyMaterial
3228
3251
  }) : void 0;
3229
3252
  }
3230
3253
  function buildSendMethods(ctx, pipe, disconnectListeners, abortSet) {
@@ -3378,82 +3401,154 @@ var LinkTransport = class LinkTransport extends Transport {
3378
3401
  }
3379
3402
  };
3380
3403
  //#endregion
3404
+ //#region src/ActionRuntime/Transport/SecureSession/exchangeTicketSeal.ts
3405
+ const SEALED_ENVELOPE_LENGTH = 2;
3406
+ function createExchangeTicketSealer(link, options = {}) {
3407
+ let keyPromise;
3408
+ const getKey = () => {
3409
+ if (keyPromise == null) keyPromise = link.deriveLocalSealKey({ version: options.version }).catch((err) => {
3410
+ keyPromise = void 0;
3411
+ throw err;
3412
+ });
3413
+ return keyPromise;
3414
+ };
3415
+ return {
3416
+ async seal(value, ttlMs) {
3417
+ const { nonce, ciphertext } = await encryptBytesWithAesGcmKey({
3418
+ dataToEncrypt: pack({
3419
+ v: value,
3420
+ exp: Date.now() + ttlMs
3421
+ }),
3422
+ aesGcmKey: await getKey()
3423
+ });
3424
+ return bytesToBase64(pack([nonce, ciphertext]));
3425
+ },
3426
+ async open(blob) {
3427
+ try {
3428
+ const envelope = unpack(base64ToBytes(blob));
3429
+ if (!Array.isArray(envelope) || envelope.length !== SEALED_ENVELOPE_LENGTH) return void 0;
3430
+ const [nonce, ciphertext] = envelope;
3431
+ if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) return void 0;
3432
+ const payload = unpack(await decryptBytesWithAesGcmKey({
3433
+ dataToDecrypt: {
3434
+ nonce,
3435
+ ciphertext
3436
+ },
3437
+ aesGcmKey: await getKey()
3438
+ }));
3439
+ if (payload == null || typeof payload.exp !== "number" || payload.exp < Date.now()) return;
3440
+ return payload.v;
3441
+ } catch {
3442
+ return;
3443
+ }
3444
+ }
3445
+ };
3446
+ }
3447
+ //#endregion
3381
3448
  //#region src/ActionRuntime/Transport/SecureSession/exchangeAcceptor.ts
3449
+ /** The default session-ticket lifetime — see {@link IExchangeAcceptorConfig.sessionTtlMs}. */
3450
+ const DEFAULT_SESSION_TTL_MS = 720 * 60 * 1e3;
3451
+ /** The handshake continuation (`hsc`) only bridges the two handshake POSTs, so it expires quickly. */
3452
+ const HANDSHAKE_CONTINUATION_TTL_MS = 120 * 1e3;
3382
3453
  const textEncoder = new TextEncoder();
3383
3454
  const textDecoder = new TextDecoder();
3384
3455
  /**
3385
3456
  * Acceptor (accept-in) side of the secure exchange protocol — the HTTP counterpart to
3386
3457
  * {@link AcceptorSecureSession}. Each POST body is one {@link decodeExchangeRequest} envelope; the
3387
- * acceptor drives the server handshake over the two `hs` POSTs (correlated by `hsid`, since stateless
3388
- * requests can't rely on channel ordering), mints a session **token** on accept, and on every later `act`
3389
- * POST resolves the session by token, decrypts the body (at `encrypted`), routes it through the runtime,
3390
- * and returns the (encrypted) result inline as the reply.
3458
+ * acceptor drives the server handshake over the two `hs` POSTs, mints a session **token** on accept, and
3459
+ * on every later `act` POST resolves the session by token, decrypts the body (at `encrypted`), routes it
3460
+ * through the runtime, and returns the (encrypted) result inline as the reply.
3391
3461
  *
3392
- * Sessions and in-flight handshakes are held in memory fine for a single-instance server. (Surviving a
3393
- * Durable-Object eviction would persist each token's `keyMaterial` and re-derive the key on a miss, the
3394
- * same primitive `AcceptorSecureSession.rehydrate` uses; left as a follow-up.)
3462
+ * **Stateless.** It holds no in-memory handshakes or sessions: the in-flight handshake `pending` is
3463
+ * sealed into the `hsc` continuation token returned on `welcome` and echoed back on `prove`, and the live
3464
+ * session is sealed into the `t` token replayed on every `act`. Both are sealed under the acceptor's own
3465
+ * persisted identity ({@link createExchangeTicketSealer}), so any isolate that loaded the same identity
3466
+ * can serve any POST — no request needs to co-locate with another (no Durable Object required just to
3467
+ * pin a handshake to one instance). A tampered, wrong-key, or expired token opens to "no valid session".
3395
3468
  */
3396
3469
  var ExchangeAcceptor = class {
3397
3470
  _security;
3398
3471
  _runtime;
3399
3472
  _allowedLevels;
3400
3473
  _noneAllowed;
3401
- _pendingHandshakes = /* @__PURE__ */ new Map();
3402
- _sessions = /* @__PURE__ */ new Map();
3474
+ _sealer;
3475
+ _sessionTtlMs;
3403
3476
  constructor(config) {
3404
3477
  this._security = config.security;
3405
3478
  this._runtime = config.runtime;
3406
3479
  this._allowedLevels = Array.isArray(config.security.securityLevel) ? config.security.securityLevel : [config.security.securityLevel];
3407
3480
  this._noneAllowed = this._allowedLevels.includes("none");
3481
+ this._sealer = createExchangeTicketSealer(config.security.link);
3482
+ this._sessionTtlMs = config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;
3408
3483
  }
3409
3484
  /** Process one POST body (an exchange envelope), returning the reply body to send back. */
3410
3485
  async handlePost(body) {
3411
3486
  const request = decodeExchangeRequest(body);
3412
3487
  if (request == null) return this._err("malformed exchange request");
3488
+ await this._security.link.initialize();
3413
3489
  if (request.k === "hs") return encodeExchange(await this._handleHandshake(request));
3414
3490
  return encodeExchange(await this._handleAction(request));
3415
3491
  }
3492
+ _makeHandshake() {
3493
+ const security = this._security;
3494
+ return createServerHandshake({
3495
+ link: security.link,
3496
+ localCoordinate: security.localCoordinate,
3497
+ dictionaryVersion: security.dictionaryVersion,
3498
+ securityLevel: security.securityLevel,
3499
+ verifyKeyResolver: security.verifyKeyResolver
3500
+ });
3501
+ }
3416
3502
  async _handleHandshake(request) {
3417
3503
  const message = decodeHandshakeMessage(request.m);
3418
3504
  if (message == null) return {
3419
3505
  k: "err",
3420
3506
  message: "malformed handshake message"
3421
3507
  };
3422
- const security = this._security;
3423
- await security.link.initialize();
3424
- let handshake = this._pendingHandshakes.get(request.hsid);
3425
- if (handshake == null) {
3426
- handshake = createServerHandshake({
3427
- link: security.link,
3428
- localCoordinate: security.localCoordinate,
3429
- dictionaryVersion: security.dictionaryVersion,
3430
- securityLevel: security.securityLevel,
3431
- verifyKeyResolver: security.verifyKeyResolver
3432
- });
3433
- this._pendingHandshakes.set(request.hsid, handshake);
3508
+ if (message.t === "hello") {
3509
+ const handshake = this._makeHandshake();
3510
+ const reply = await handshake.onHello(message);
3511
+ if (reply.t === "reject") return {
3512
+ k: "hs",
3513
+ m: encodeHandshakeMessage(reply)
3514
+ };
3515
+ const pending = handshake.exportPending();
3516
+ if (pending == null) return {
3517
+ k: "err",
3518
+ message: "handshake produced no continuation state"
3519
+ };
3520
+ const hsc = await this._sealer.seal(pending, HANDSHAKE_CONTINUATION_TTL_MS);
3521
+ return {
3522
+ k: "hs",
3523
+ m: encodeHandshakeMessage(reply),
3524
+ hsc
3525
+ };
3434
3526
  }
3435
- if (message.t === "hello") return {
3436
- k: "hs",
3437
- m: encodeHandshakeMessage(await handshake.onHello(message))
3438
- };
3439
3527
  if (message.t === "prove") {
3528
+ if (request.hsc == null) return {
3529
+ k: "err",
3530
+ message: "prove missing continuation token"
3531
+ };
3532
+ const pending = await this._sealer.open(request.hsc);
3533
+ if (pending == null) return {
3534
+ k: "err",
3535
+ message: "invalid or expired continuation token"
3536
+ };
3537
+ const handshake = this._makeHandshake();
3538
+ await handshake.restorePending(pending);
3440
3539
  const reply = await handshake.onProve(message);
3441
- this._pendingHandshakes.delete(request.hsid);
3442
3540
  const result = handshake.getResult();
3443
3541
  if (reply.t === "accept" && result != null) {
3444
- const token = nanoid();
3445
- this._sessions.set(token, {
3446
- client: new RuntimeCoordinate(result.remote),
3542
+ const ticket = {
3543
+ client: result.remote,
3447
3544
  securityLevel: result.securityLevel,
3448
- crypto: result.securityLevel === "encrypted" ? createActionFrameCrypto({
3449
- link: security.link,
3450
- linkedClientId: result.linkedClientId
3451
- }) : void 0
3452
- });
3545
+ keyMaterial: result.encryptionKeyMaterial
3546
+ };
3547
+ const t = await this._sealer.seal(ticket, this._sessionTtlMs);
3453
3548
  return {
3454
3549
  k: "hs",
3455
3550
  m: encodeHandshakeMessage(reply),
3456
- t: token
3551
+ t
3457
3552
  };
3458
3553
  }
3459
3554
  return {
@@ -3467,20 +3562,26 @@ var ExchangeAcceptor = class {
3467
3562
  };
3468
3563
  }
3469
3564
  async _handleAction(request) {
3470
- let session;
3565
+ let client;
3566
+ let crypto;
3471
3567
  let candidate;
3472
3568
  if (request.t != null) {
3473
- session = this._sessions.get(request.t);
3474
- if (session == null) return {
3569
+ const ticket = await this._sealer.open(request.t);
3570
+ if (ticket == null) return {
3475
3571
  k: "err",
3476
3572
  message: "unknown or expired session token"
3477
3573
  };
3574
+ client = new RuntimeCoordinate(ticket.client);
3575
+ crypto = ticket.securityLevel === "encrypted" && ticket.keyMaterial != null ? createActionFrameCrypto({
3576
+ link: this._security.link,
3577
+ keyMaterial: ticket.keyMaterial
3578
+ }) : void 0;
3478
3579
  if ("c" in request) {
3479
- if (session.crypto == null) return {
3580
+ if (crypto == null) return {
3480
3581
  k: "err",
3481
3582
  message: "session is not encrypted"
3482
3583
  };
3483
- const plain = await session.crypto.decryptFrame(base64ToBytes(request.c));
3584
+ const plain = await crypto.decryptFrame(base64ToBytes(request.c));
3484
3585
  candidate = JSON.parse(textDecoder.decode(plain));
3485
3586
  } else candidate = request.w;
3486
3587
  } else {
@@ -3495,11 +3596,11 @@ var ExchangeAcceptor = class {
3495
3596
  message: "malformed action wire"
3496
3597
  };
3497
3598
  const wire = candidate;
3498
- if (session != null && wire.type === "request") wire.context.originClient = session.client.toJsonObject();
3599
+ if (client != null && wire.type === "request") wire.context.originClient = client.toJsonObject();
3499
3600
  const resultWire = (await (await this._runtime.handleActionPayloadWire(wire)).waitForResultPayload()).toJsonObject();
3500
- if (session?.crypto != null) return {
3601
+ if (crypto != null && "c" in request) return {
3501
3602
  k: "act",
3502
- c: bytesToBase64(await session.crypto.encryptFrame(textEncoder.encode(JSON.stringify(resultWire))))
3603
+ c: bytesToBase64(await crypto.encryptFrame(textEncoder.encode(JSON.stringify(resultWire))))
3503
3604
  };
3504
3605
  return {
3505
3606
  k: "act",
@@ -3717,4 +3818,4 @@ function createHibernatableWsServerAdapter(options) {
3717
3818
  //#endregion
3718
3819
  export { ActionPayload_Request as $, encodeHandshakeMessage as A, EErrId_NiceTransport as B, EHandshakeMessageType as C, createServerHandshake as D, createInMemoryTofuVerifyKeyResolver as E, ConnectorHandler as F, ActionSchema as G, err_nice_external_client as H, createConnectorHandler as I, isActionPayload_Result_JsonObject as J, EActionResponseMode as K, PeerLinkHandler as L, ActionLocalHandler as M, createLocalHandler as N, createStorageTofuVerifyKeyResolver as O, ActionRuntime as P, RunningAction as Q, ETransportShape as R, decodeActionFrame as S, createClientHandshake as T, isActionPayload_Any_JsonObject as U, err_nice_transport as V, isActionPayload_Request_JsonObject as W, EErrId_NiceAction as X, isAction_Base_JsonObject as Y, err_nice_action as Z, extractWirePayload as _, ExchangeAcceptor as a, RuntimeCoordinate as at, createAcceptorHandler as b, decodeExchangeReply as c, Transport as d, ActionPayload_Result as et, createBinaryWireSessionFactory as f, buildActionRouteDictionary as g, assembleWireJson as h, createActionFetchHandler as i, ActionBase as it, runtimeLinkId as j, decodeHandshakeMessage as k, decodeExchangeRequest as l, ReversePayloadType as m, ConnectionStateStore as n, EActionProgressType as nt, LinkTransport as o, runtimeCoordinateToStringIds as ot, PayloadTypeToInt as p, actionSchema as q, createConnectionStateStore as r, ActionPayload as rt, ExchangeTransport as s, createHibernatableWsServerAdapter as t, EActionPayloadType as tt, encodeExchange as u, createSecureAcceptorHandler as v, ESecurityLevel as w, createActionFrameCrypto as x, AcceptorHandler as y, ETransportStatus as z };
3719
3820
 
3720
- //# sourceMappingURL=createHibernatableWsServerAdapter-BkjESd01.mjs.map
3821
+ //# sourceMappingURL=createHibernatableWsServerAdapter-BD5n-Ev9.mjs.map