@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
@@ -1734,6 +1734,16 @@ function createInMemoryTofuVerifyKeyResolver() {
1734
1734
  * Storage-backed trust-on-first-use resolver: pins survive process restarts / Durable Object eviction
1735
1735
  * (e.g. back it with `createDurableObjectStorageAdapter`). Same policy as the in-memory variant — trust
1736
1736
  * + pin the first verify key per client identity, reject a different one thereafter.
1737
+ *
1738
+ * Fail-closed by construction: a thrown storage read (`getJson`) or first-pin write (`updateJsonWithDef`)
1739
+ * propagates out of `resolve`, which makes `onProve` reject the handshake — a storage error can never be
1740
+ * mistaken for "first use" and silently trusted. (A genuine `undefined`/absent read is the only path to
1741
+ * a fresh pin; the underlying adapters never coerce a thrown read to `undefined`.) Keep it that way: do
1742
+ * not wrap the storage calls in a `try/catch` that swallows the error.
1743
+ *
1744
+ * On an *eventually-consistent* store a stale "absent" read re-pins the **same** verify key the client
1745
+ * just presented (it is signature-verified before this runs), so the worst case is a harmless re-write,
1746
+ * never a weakened trust decision. Cross-isolate-strong pinning still wants a strongly-consistent store.
1737
1747
  */
1738
1748
  function createStorageTofuVerifyKeyResolver(storageAdapter) {
1739
1749
  const storage = (0, _nice_code_util.createTypedStorage)({ storageAdapter });
@@ -1787,6 +1797,13 @@ function createClientHandshake(config) {
1787
1797
  bindVerifyKeysIntoDerivation: true
1788
1798
  } : {}
1789
1799
  });
1800
+ const encryptionKeyMaterial = wantsEncryption && welcome.exchangePublicKey != null ? {
1801
+ verifyPublicKey: welcome.verifyPublicKey,
1802
+ exchangePublicKey: welcome.exchangePublicKey,
1803
+ saltString: sessionSalt(clientNonce, welcome.serverNonce),
1804
+ infoString: handshakeInfo(dictionaryVersion),
1805
+ bindVerifyKeysIntoDerivation: true
1806
+ } : void 0;
1790
1807
  const challenge = buildHandshakeChallenge({
1791
1808
  securityLevel,
1792
1809
  dictionaryVersion,
@@ -1802,7 +1819,8 @@ function createClientHandshake(config) {
1802
1819
  pending = {
1803
1820
  linkedServerId,
1804
1821
  server: welcome.server,
1805
- challenge
1822
+ challenge,
1823
+ encryptionKeyMaterial
1806
1824
  };
1807
1825
  return {
1808
1826
  t: "prove",
@@ -1821,7 +1839,8 @@ function createClientHandshake(config) {
1821
1839
  return {
1822
1840
  linkedClientId: pending.linkedServerId,
1823
1841
  remote: pending.server,
1824
- securityLevel
1842
+ securityLevel,
1843
+ encryptionKeyMaterial: pending.encryptionKeyMaterial
1825
1844
  };
1826
1845
  }
1827
1846
  };
@@ -1916,6 +1935,16 @@ function createServerHandshake(config) {
1916
1935
  /** The completed handshake result once `onProve` has accepted, else `undefined`. */
1917
1936
  getResult() {
1918
1937
  return result;
1938
+ },
1939
+ exportPending() {
1940
+ return pending;
1941
+ },
1942
+ async restorePending(restored) {
1943
+ pending = restored;
1944
+ await link.linkClient({
1945
+ linkedClientId: restored.linkedClientId,
1946
+ verifyPublicKey: restored.clientVerifyKey
1947
+ });
1919
1948
  }
1920
1949
  };
1921
1950
  }
@@ -1946,14 +1975,16 @@ function parseJsonActionFrame(message) {
1946
1975
  const ENCRYPTED_ENVELOPE_LENGTH = 2;
1947
1976
  /**
1948
1977
  * Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
1949
- * level. Keyed by the link + `linkedClientId`, so it reuses the cached shared AES-GCM key.
1978
+ * level. The shared key is derived once from {@link IActionFrameCryptoConfig.keyMaterial} and captured
1979
+ * for this connection alone, decoupling it from the link's shared key cache.
1950
1980
  */
1951
- function createActionFrameCrypto({ link, linkedClientId }) {
1981
+ function createActionFrameCrypto({ link, keyMaterial }) {
1982
+ const keyPromise = link.deriveSharedAesGcmKey(keyMaterial);
1952
1983
  return {
1953
1984
  async encryptFrame(frame) {
1954
- const { nonce, ciphertext } = await link.encryptBytesForLinkedClient({
1955
- linkedClientId,
1956
- dataToEncrypt: frame
1985
+ const { nonce, ciphertext } = await (0, _nice_code_util.encryptBytesWithAesGcmKey)({
1986
+ dataToEncrypt: frame,
1987
+ aesGcmKey: await keyPromise
1957
1988
  });
1958
1989
  return (0, msgpackr.pack)([nonce, ciphertext]);
1959
1990
  },
@@ -1963,12 +1994,12 @@ function createActionFrameCrypto({ link, linkedClientId }) {
1963
1994
  if (!Array.isArray(envelope) || envelope.length !== ENCRYPTED_ENVELOPE_LENGTH) throw new Error("[ws-crypto] malformed encrypted frame envelope");
1964
1995
  const [nonce, ciphertext] = envelope;
1965
1996
  if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) throw new Error("[ws-crypto] malformed encrypted frame fields");
1966
- return await link.decryptBytesFromLinkedClient({
1967
- linkedClientId,
1997
+ return await (0, _nice_code_util.decryptBytesWithAesGcmKey)({
1968
1998
  dataToDecrypt: {
1969
1999
  nonce,
1970
2000
  ciphertext
1971
- }
2001
+ },
2002
+ aesGcmKey: await keyPromise
1972
2003
  });
1973
2004
  }
1974
2005
  };
@@ -2079,9 +2110,9 @@ var AcceptorSecureSession = class {
2079
2110
  _complete(result) {
2080
2111
  this._authed = true;
2081
2112
  this._handshake = void 0;
2082
- if (result.securityLevel === "encrypted") this._pipe = this._buildPipe(createActionFrameCrypto({
2113
+ if (result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null) this._pipe = this._buildPipe(createActionFrameCrypto({
2083
2114
  link: this.config.link,
2084
- linkedClientId: result.linkedClientId
2115
+ keyMaterial: result.encryptionKeyMaterial
2085
2116
  }));
2086
2117
  this.config.onAuthenticated({
2087
2118
  client: new RuntimeCoordinate(result.remote),
@@ -2100,17 +2131,10 @@ var AcceptorSecureSession = class {
2100
2131
  this._authed = true;
2101
2132
  if (state.securityLevel !== "encrypted" || state.keyMaterial == null) return;
2102
2133
  const { link } = this.config;
2103
- const { linkedClientId, keyMaterial } = state;
2104
- const cryptoReady = link.initialize().then(() => link.linkClient({
2105
- linkedClientId,
2106
- verifyPublicKey: keyMaterial.verifyPublicKey,
2107
- exchangePublicKey: keyMaterial.exchangePublicKey,
2108
- saltString: keyMaterial.saltString,
2109
- infoString: keyMaterial.infoString,
2110
- bindVerifyKeysIntoDerivation: keyMaterial.bindVerifyKeysIntoDerivation
2111
- })).then(() => createActionFrameCrypto({
2134
+ const { keyMaterial } = state;
2135
+ const cryptoReady = link.initialize().then(() => createActionFrameCrypto({
2112
2136
  link,
2113
- linkedClientId
2137
+ keyMaterial
2114
2138
  }));
2115
2139
  cryptoReady.catch((err) => console.error("[ws-server] failed to restore encrypted session", err));
2116
2140
  this._pipe = this._buildPipe(cryptoReady);
@@ -2889,11 +2913,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
2889
2913
  dictionaryVersion: secure.dictionaryVersion,
2890
2914
  securityLevel: secure.securityLevel
2891
2915
  });
2892
- const hsid = (0, nanoid.nanoid)();
2893
2916
  const hello = await handshake.createHello();
2894
2917
  const welcomeReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
2895
2918
  k: "hs",
2896
- hsid,
2897
2919
  m: encodeHandshakeMessage(hello)
2898
2920
  }))));
2899
2921
  if (welcomeReply?.k !== "hs") throw new Error("[exchange-handshake] expected a welcome reply");
@@ -2901,11 +2923,12 @@ async function runConnectorExchangeHandshake(carrier, secure) {
2901
2923
  if (welcome == null) throw new Error("[exchange-handshake] malformed welcome");
2902
2924
  if (welcome.t === "reject") throw new Error(`[exchange-handshake] rejected by peer: ${welcome.reason}`);
2903
2925
  if (welcome.t !== "welcome") throw new Error(`[exchange-handshake] expected welcome, got ${welcome.t}`);
2926
+ if (welcomeReply.hsc == null) throw new Error("[exchange-handshake] welcome missing handshake continuation token");
2904
2927
  const prove = await handshake.onWelcome(welcome);
2905
2928
  const acceptReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
2906
2929
  k: "hs",
2907
- hsid,
2908
- m: encodeHandshakeMessage(prove)
2930
+ m: encodeHandshakeMessage(prove),
2931
+ hsc: welcomeReply.hsc
2909
2932
  }))));
2910
2933
  if (acceptReply?.k !== "hs") throw new Error("[exchange-handshake] expected an accept reply");
2911
2934
  const accept = decodeHandshakeMessage(acceptReply.m);
@@ -2914,9 +2937,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
2914
2937
  if (accept.t !== "accept") throw new Error(`[exchange-handshake] expected accept, got ${accept.t}`);
2915
2938
  if (acceptReply.t == null) throw new Error("[exchange-handshake] accept missing session token");
2916
2939
  const result = await handshake.onAccept(accept);
2917
- const crypto = result.securityLevel === "encrypted" ? createActionFrameCrypto({
2940
+ const crypto = result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
2918
2941
  link: secure.link,
2919
- linkedClientId: result.linkedClientId
2942
+ keyMaterial: result.encryptionKeyMaterial
2920
2943
  }) : void 0;
2921
2944
  return {
2922
2945
  token: acceptReply.t,
@@ -3224,9 +3247,9 @@ async function runClientHandshake(channel, secure, nextHandshakeMessage) {
3224
3247
  if (accept.t === "reject") throw new Error(`[link-handshake] rejected by peer: ${accept.reason}`);
3225
3248
  if (accept.t !== "accept") throw new Error(`[link-handshake] expected accept, got ${accept.t}`);
3226
3249
  const result = await handshake.onAccept(accept);
3227
- return result.securityLevel === "encrypted" ? createActionFrameCrypto({
3250
+ return result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
3228
3251
  link: secure.link,
3229
- linkedClientId: result.linkedClientId
3252
+ keyMaterial: result.encryptionKeyMaterial
3230
3253
  }) : void 0;
3231
3254
  }
3232
3255
  function buildSendMethods(ctx, pipe, disconnectListeners, abortSet) {
@@ -3380,82 +3403,154 @@ var LinkTransport = class LinkTransport extends Transport {
3380
3403
  }
3381
3404
  };
3382
3405
  //#endregion
3406
+ //#region src/ActionRuntime/Transport/SecureSession/exchangeTicketSeal.ts
3407
+ const SEALED_ENVELOPE_LENGTH = 2;
3408
+ function createExchangeTicketSealer(link, options = {}) {
3409
+ let keyPromise;
3410
+ const getKey = () => {
3411
+ if (keyPromise == null) keyPromise = link.deriveLocalSealKey({ version: options.version }).catch((err) => {
3412
+ keyPromise = void 0;
3413
+ throw err;
3414
+ });
3415
+ return keyPromise;
3416
+ };
3417
+ return {
3418
+ async seal(value, ttlMs) {
3419
+ const { nonce, ciphertext } = await (0, _nice_code_util.encryptBytesWithAesGcmKey)({
3420
+ dataToEncrypt: (0, msgpackr.pack)({
3421
+ v: value,
3422
+ exp: Date.now() + ttlMs
3423
+ }),
3424
+ aesGcmKey: await getKey()
3425
+ });
3426
+ return bytesToBase64((0, msgpackr.pack)([nonce, ciphertext]));
3427
+ },
3428
+ async open(blob) {
3429
+ try {
3430
+ const envelope = (0, msgpackr.unpack)(base64ToBytes(blob));
3431
+ if (!Array.isArray(envelope) || envelope.length !== SEALED_ENVELOPE_LENGTH) return void 0;
3432
+ const [nonce, ciphertext] = envelope;
3433
+ if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) return void 0;
3434
+ const payload = (0, msgpackr.unpack)(await (0, _nice_code_util.decryptBytesWithAesGcmKey)({
3435
+ dataToDecrypt: {
3436
+ nonce,
3437
+ ciphertext
3438
+ },
3439
+ aesGcmKey: await getKey()
3440
+ }));
3441
+ if (payload == null || typeof payload.exp !== "number" || payload.exp < Date.now()) return;
3442
+ return payload.v;
3443
+ } catch {
3444
+ return;
3445
+ }
3446
+ }
3447
+ };
3448
+ }
3449
+ //#endregion
3383
3450
  //#region src/ActionRuntime/Transport/SecureSession/exchangeAcceptor.ts
3451
+ /** The default session-ticket lifetime — see {@link IExchangeAcceptorConfig.sessionTtlMs}. */
3452
+ const DEFAULT_SESSION_TTL_MS = 720 * 60 * 1e3;
3453
+ /** The handshake continuation (`hsc`) only bridges the two handshake POSTs, so it expires quickly. */
3454
+ const HANDSHAKE_CONTINUATION_TTL_MS = 120 * 1e3;
3384
3455
  const textEncoder = new TextEncoder();
3385
3456
  const textDecoder = new TextDecoder();
3386
3457
  /**
3387
3458
  * Acceptor (accept-in) side of the secure exchange protocol — the HTTP counterpart to
3388
3459
  * {@link AcceptorSecureSession}. Each POST body is one {@link decodeExchangeRequest} envelope; the
3389
- * acceptor drives the server handshake over the two `hs` POSTs (correlated by `hsid`, since stateless
3390
- * requests can't rely on channel ordering), mints a session **token** on accept, and on every later `act`
3391
- * POST resolves the session by token, decrypts the body (at `encrypted`), routes it through the runtime,
3392
- * and returns the (encrypted) result inline as the reply.
3460
+ * acceptor drives the server handshake over the two `hs` POSTs, mints a session **token** on accept, and
3461
+ * on every later `act` POST resolves the session by token, decrypts the body (at `encrypted`), routes it
3462
+ * through the runtime, and returns the (encrypted) result inline as the reply.
3393
3463
  *
3394
- * Sessions and in-flight handshakes are held in memory fine for a single-instance server. (Surviving a
3395
- * Durable-Object eviction would persist each token's `keyMaterial` and re-derive the key on a miss, the
3396
- * same primitive `AcceptorSecureSession.rehydrate` uses; left as a follow-up.)
3464
+ * **Stateless.** It holds no in-memory handshakes or sessions: the in-flight handshake `pending` is
3465
+ * sealed into the `hsc` continuation token returned on `welcome` and echoed back on `prove`, and the live
3466
+ * session is sealed into the `t` token replayed on every `act`. Both are sealed under the acceptor's own
3467
+ * persisted identity ({@link createExchangeTicketSealer}), so any isolate that loaded the same identity
3468
+ * can serve any POST — no request needs to co-locate with another (no Durable Object required just to
3469
+ * pin a handshake to one instance). A tampered, wrong-key, or expired token opens to "no valid session".
3397
3470
  */
3398
3471
  var ExchangeAcceptor = class {
3399
3472
  _security;
3400
3473
  _runtime;
3401
3474
  _allowedLevels;
3402
3475
  _noneAllowed;
3403
- _pendingHandshakes = /* @__PURE__ */ new Map();
3404
- _sessions = /* @__PURE__ */ new Map();
3476
+ _sealer;
3477
+ _sessionTtlMs;
3405
3478
  constructor(config) {
3406
3479
  this._security = config.security;
3407
3480
  this._runtime = config.runtime;
3408
3481
  this._allowedLevels = Array.isArray(config.security.securityLevel) ? config.security.securityLevel : [config.security.securityLevel];
3409
3482
  this._noneAllowed = this._allowedLevels.includes("none");
3483
+ this._sealer = createExchangeTicketSealer(config.security.link);
3484
+ this._sessionTtlMs = config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;
3410
3485
  }
3411
3486
  /** Process one POST body (an exchange envelope), returning the reply body to send back. */
3412
3487
  async handlePost(body) {
3413
3488
  const request = decodeExchangeRequest(body);
3414
3489
  if (request == null) return this._err("malformed exchange request");
3490
+ await this._security.link.initialize();
3415
3491
  if (request.k === "hs") return encodeExchange(await this._handleHandshake(request));
3416
3492
  return encodeExchange(await this._handleAction(request));
3417
3493
  }
3494
+ _makeHandshake() {
3495
+ const security = this._security;
3496
+ return createServerHandshake({
3497
+ link: security.link,
3498
+ localCoordinate: security.localCoordinate,
3499
+ dictionaryVersion: security.dictionaryVersion,
3500
+ securityLevel: security.securityLevel,
3501
+ verifyKeyResolver: security.verifyKeyResolver
3502
+ });
3503
+ }
3418
3504
  async _handleHandshake(request) {
3419
3505
  const message = decodeHandshakeMessage(request.m);
3420
3506
  if (message == null) return {
3421
3507
  k: "err",
3422
3508
  message: "malformed handshake message"
3423
3509
  };
3424
- const security = this._security;
3425
- await security.link.initialize();
3426
- let handshake = this._pendingHandshakes.get(request.hsid);
3427
- if (handshake == null) {
3428
- handshake = createServerHandshake({
3429
- link: security.link,
3430
- localCoordinate: security.localCoordinate,
3431
- dictionaryVersion: security.dictionaryVersion,
3432
- securityLevel: security.securityLevel,
3433
- verifyKeyResolver: security.verifyKeyResolver
3434
- });
3435
- this._pendingHandshakes.set(request.hsid, handshake);
3510
+ if (message.t === "hello") {
3511
+ const handshake = this._makeHandshake();
3512
+ const reply = await handshake.onHello(message);
3513
+ if (reply.t === "reject") return {
3514
+ k: "hs",
3515
+ m: encodeHandshakeMessage(reply)
3516
+ };
3517
+ const pending = handshake.exportPending();
3518
+ if (pending == null) return {
3519
+ k: "err",
3520
+ message: "handshake produced no continuation state"
3521
+ };
3522
+ const hsc = await this._sealer.seal(pending, HANDSHAKE_CONTINUATION_TTL_MS);
3523
+ return {
3524
+ k: "hs",
3525
+ m: encodeHandshakeMessage(reply),
3526
+ hsc
3527
+ };
3436
3528
  }
3437
- if (message.t === "hello") return {
3438
- k: "hs",
3439
- m: encodeHandshakeMessage(await handshake.onHello(message))
3440
- };
3441
3529
  if (message.t === "prove") {
3530
+ if (request.hsc == null) return {
3531
+ k: "err",
3532
+ message: "prove missing continuation token"
3533
+ };
3534
+ const pending = await this._sealer.open(request.hsc);
3535
+ if (pending == null) return {
3536
+ k: "err",
3537
+ message: "invalid or expired continuation token"
3538
+ };
3539
+ const handshake = this._makeHandshake();
3540
+ await handshake.restorePending(pending);
3442
3541
  const reply = await handshake.onProve(message);
3443
- this._pendingHandshakes.delete(request.hsid);
3444
3542
  const result = handshake.getResult();
3445
3543
  if (reply.t === "accept" && result != null) {
3446
- const token = (0, nanoid.nanoid)();
3447
- this._sessions.set(token, {
3448
- client: new RuntimeCoordinate(result.remote),
3544
+ const ticket = {
3545
+ client: result.remote,
3449
3546
  securityLevel: result.securityLevel,
3450
- crypto: result.securityLevel === "encrypted" ? createActionFrameCrypto({
3451
- link: security.link,
3452
- linkedClientId: result.linkedClientId
3453
- }) : void 0
3454
- });
3547
+ keyMaterial: result.encryptionKeyMaterial
3548
+ };
3549
+ const t = await this._sealer.seal(ticket, this._sessionTtlMs);
3455
3550
  return {
3456
3551
  k: "hs",
3457
3552
  m: encodeHandshakeMessage(reply),
3458
- t: token
3553
+ t
3459
3554
  };
3460
3555
  }
3461
3556
  return {
@@ -3469,20 +3564,26 @@ var ExchangeAcceptor = class {
3469
3564
  };
3470
3565
  }
3471
3566
  async _handleAction(request) {
3472
- let session;
3567
+ let client;
3568
+ let crypto;
3473
3569
  let candidate;
3474
3570
  if (request.t != null) {
3475
- session = this._sessions.get(request.t);
3476
- if (session == null) return {
3571
+ const ticket = await this._sealer.open(request.t);
3572
+ if (ticket == null) return {
3477
3573
  k: "err",
3478
3574
  message: "unknown or expired session token"
3479
3575
  };
3576
+ client = new RuntimeCoordinate(ticket.client);
3577
+ crypto = ticket.securityLevel === "encrypted" && ticket.keyMaterial != null ? createActionFrameCrypto({
3578
+ link: this._security.link,
3579
+ keyMaterial: ticket.keyMaterial
3580
+ }) : void 0;
3480
3581
  if ("c" in request) {
3481
- if (session.crypto == null) return {
3582
+ if (crypto == null) return {
3482
3583
  k: "err",
3483
3584
  message: "session is not encrypted"
3484
3585
  };
3485
- const plain = await session.crypto.decryptFrame(base64ToBytes(request.c));
3586
+ const plain = await crypto.decryptFrame(base64ToBytes(request.c));
3486
3587
  candidate = JSON.parse(textDecoder.decode(plain));
3487
3588
  } else candidate = request.w;
3488
3589
  } else {
@@ -3497,11 +3598,11 @@ var ExchangeAcceptor = class {
3497
3598
  message: "malformed action wire"
3498
3599
  };
3499
3600
  const wire = candidate;
3500
- if (session != null && wire.type === "request") wire.context.originClient = session.client.toJsonObject();
3601
+ if (client != null && wire.type === "request") wire.context.originClient = client.toJsonObject();
3501
3602
  const resultWire = (await (await this._runtime.handleActionPayloadWire(wire)).waitForResultPayload()).toJsonObject();
3502
- if (session?.crypto != null) return {
3603
+ if (crypto != null && "c" in request) return {
3503
3604
  k: "act",
3504
- c: bytesToBase64(await session.crypto.encryptFrame(textEncoder.encode(JSON.stringify(resultWire))))
3605
+ c: bytesToBase64(await crypto.encryptFrame(textEncoder.encode(JSON.stringify(resultWire))))
3505
3606
  };
3506
3607
  return {
3507
3608
  k: "act",
@@ -4078,4 +4179,4 @@ Object.defineProperty(exports, "runtimeLinkId", {
4078
4179
  }
4079
4180
  });
4080
4181
 
4081
- //# sourceMappingURL=createHibernatableWsServerAdapter-FSDWrxoF.cjs.map
4182
+ //# sourceMappingURL=createHibernatableWsServerAdapter-j96U9vgo.cjs.map