@h-ai/crypto 1.0.0-beta.21 → 1.0.0-beta.23

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/dist/index.js CHANGED
@@ -59,6 +59,7 @@ var en_US_default = {
59
59
  crypto_transportBodyLimitInvalid: "Transport plaintext limit must be an integer between 1024 and 67108864 bytes",
60
60
  crypto_transportBodyTooLarge: "Encrypted transport payload exceeds the configured byte limit",
61
61
  crypto_transportTimeInvalid: "Your device and server clocks may be out of sync, or the request has expired. Enable automatic time synchronization or set your device time manually, then refresh to retry. If this continues, ask an administrator to check the server clock.",
62
+ crypto_transportTimeResponseInvalid: "Transport time validation response is missing a valid server timestamp",
62
63
  crypto_transportTimeInvalidWithServerTime: "Your device and server clocks may be out of sync, or the request has expired. Server time is {serverTime}. Enable automatic time synchronization or set your device time manually, then refresh to retry. If this continues, ask an administrator to check the server clock.",
63
64
  crypto_transportTimeConfigInvalid: "Invalid transport time settings. Future clock skew must be an integer from 0 to 300 seconds and request lifetime from 1 to 600 seconds.",
64
65
  crypto_transportBodyLimitConflict: "Explicit transport limit {limit} bytes is below the enabled module requirement of {required} bytes; adjust the module budget or explicit limit",
@@ -121,6 +122,7 @@ var ja_JP_default = {
121
122
  crypto_transportBodyLimitInvalid: "\u901A\u4FE1\u5E73\u6587\u306E\u4E0A\u9650\u306F 1024 \u304B\u3089 67108864 \u30D0\u30A4\u30C8\u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
122
123
  crypto_transportBodyTooLarge: "\u6697\u53F7\u5316\u901A\u4FE1\u306E\u30C7\u30FC\u30BF\u304C\u8A2D\u5B9A\u3055\u308C\u305F\u30D0\u30A4\u30C8\u4E0A\u9650\u3092\u8D85\u3048\u3066\u3044\u307E\u3059",
123
124
  crypto_transportTimeInvalid: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u7AEF\u672B\u306E\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3059\u308B\u304B\u3001\u624B\u52D5\u3067\u6642\u523B\u3092\u8A2D\u5B9A\u3057\u3066\u304B\u3089\u3001\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
125
+ crypto_transportTimeResponseInvalid: "\u6697\u53F7\u5316\u901A\u4FE1\u306E\u6642\u523B\u691C\u8A3C\u5FDC\u7B54\u306B\u6709\u52B9\u306A\u30B5\u30FC\u30D0\u30FC\u6642\u523B\u304C\u3042\u308A\u307E\u305B\u3093",
124
126
  crypto_transportTimeInvalidWithServerTime: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u30B5\u30FC\u30D0\u30FC\u6642\u523B\u306F {serverTime} \u3067\u3059\u3002\u7AEF\u672B\u306E\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3059\u308B\u304B\u3001\u624B\u52D5\u3067\u6642\u523B\u3092\u8A2D\u5B9A\u3057\u3066\u304B\u3089\u3001\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
125
127
  crypto_transportTimeConfigInvalid: "\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u306E\u6642\u523B\u8A2D\u5B9A\u304C\u7121\u52B9\u3067\u3059\u3002\u672A\u6765\u65B9\u5411\u306E\u6642\u523B\u5DEE\u306F 0\uFF5E300 \u79D2\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9593\u306F 1\uFF5E600 \u79D2\u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
126
128
  crypto_transportBodyLimitConflict: "\u660E\u793A\u3055\u308C\u305F\u8EE2\u9001\u4E0A\u9650 {limit} \u30D0\u30A4\u30C8\u306F\u6709\u52B9\u306A\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u5FC5\u8981\u91CF {required} \u30D0\u30A4\u30C8\u672A\u6E80\u3067\u3059\u3002\u4E88\u7B97\u307E\u305F\u306F\u4E0A\u9650\u3092\u5909\u66F4\u3057\u3066\u304F\u3060\u3055\u3044",
@@ -183,6 +185,7 @@ var zh_CN_default = {
183
185
  crypto_transportBodyLimitInvalid: "\u4F20\u8F93\u660E\u6587\u5BB9\u91CF\u5FC5\u987B\u662F 1024 \u5230 67108864 \u5B57\u8282\u4E4B\u95F4\u7684\u6574\u6570",
184
186
  crypto_transportBodyTooLarge: "\u52A0\u5BC6\u4F20\u8F93\u62A5\u6587\u8D85\u8FC7\u914D\u7F6E\u7684\u5B57\u8282\u4E0A\u9650",
185
187
  crypto_transportTimeInvalid: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u8BF7\u5F00\u542F\u8BBE\u5907\u7684\u81EA\u52A8\u6821\u65F6\u6216\u624B\u5DE5\u4FEE\u6539\u65F6\u95F4\uFF0C\u7136\u540E\u5237\u65B0\u91CD\u8BD5\u3002\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002",
188
+ crypto_transportTimeResponseInvalid: "\u4F20\u8F93\u52A0\u5BC6\u65F6\u95F4\u6821\u9A8C\u54CD\u5E94\u7F3A\u5C11\u6709\u6548\u7684\u670D\u52A1\u5668\u65F6\u95F4",
186
189
  crypto_transportTimeInvalidWithServerTime: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u670D\u52A1\u5668\u65F6\u95F4\u4E3A {serverTime}\u3002\u8BF7\u5F00\u542F\u8BBE\u5907\u7684\u81EA\u52A8\u6821\u65F6\u6216\u624B\u5DE5\u4FEE\u6539\u65F6\u95F4\uFF0C\u7136\u540E\u5237\u65B0\u91CD\u8BD5\u3002\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002",
187
190
  crypto_transportTimeConfigInvalid: "\u4F20\u8F93\u65F6\u95F4\u914D\u7F6E\u65E0\u6548\u3002\u5141\u8BB8\u7684\u672A\u6765\u504F\u5DEE\u987B\u4E3A 0 \u81F3 300 \u79D2\u7684\u6574\u6570\uFF0C\u8BF7\u6C42\u6709\u6548\u671F\u987B\u4E3A 1 \u81F3 600 \u79D2\u7684\u6574\u6570\u3002",
188
191
  crypto_transportBodyLimitConflict: "\u663E\u5F0F\u4F20\u8F93\u4E0A\u9650 {limit} \u5B57\u8282\u5C0F\u4E8E\u5DF2\u542F\u7528\u6A21\u5757\u6240\u9700\u7684 {required} \u5B57\u8282\uFF1B\u8BF7\u8C03\u6574\u6A21\u5757\u9884\u7B97\u6216\u663E\u5F0F\u4E0A\u9650",
@@ -1058,6 +1061,9 @@ var TRANSPORT_TIME_LIMITS = Object.freeze({
1058
1061
  });
1059
1062
 
1060
1063
  // src/transport/crypto-transport-codec.ts
1064
+ function isValidTransportKeyTtlSeconds(value) {
1065
+ return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 2147483647;
1066
+ }
1061
1067
  function authenticatedText(payload) {
1062
1068
  const c = payload.context;
1063
1069
  return JSON.stringify([
@@ -1164,20 +1170,20 @@ function sameTransportContext(a, b) {
1164
1170
  // src/transport/crypto-transport-error.ts
1165
1171
  var TransportTimeError = class extends Error {
1166
1172
  constructor(context, serverTime) {
1167
- const validTime = typeof serverTime === "number" && Number.isSafeInteger(serverTime) && serverTime >= 0 && serverTime <= 864e13 ? serverTime : void 0;
1168
- const message = validTime === void 0 ? cryptoM("crypto_transportTimeInvalid") : cryptoM("crypto_transportTimeInvalidWithServerTime", {
1173
+ if (!Number.isSafeInteger(serverTime) || serverTime < 0 || serverTime > 864e13)
1174
+ throw new RangeError(cryptoM("crypto_transportTimeResponseInvalid"));
1175
+ super(cryptoM("crypto_transportTimeInvalidWithServerTime", {
1169
1176
  params: {
1170
- serverTime: new Date(validTime).toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC")
1177
+ serverTime: new Date(serverTime).toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC")
1171
1178
  }
1172
- });
1173
- super(message);
1179
+ }));
1174
1180
  this.context = context;
1181
+ this.serverTime = serverTime;
1175
1182
  this.name = "TransportTimeError";
1176
- this.serverTime = validTime;
1177
1183
  }
1178
1184
  context;
1179
- code = HaiCryptoError.TRANSPORT_TIME_INVALID.code;
1180
1185
  serverTime;
1186
+ code = HaiCryptoError.TRANSPORT_TIME_INVALID.code;
1181
1187
  };
1182
1188
  async function createTransportErrorResponse(error, authenticated) {
1183
1189
  const isTimeError = error instanceof TransportTimeError;
@@ -1194,7 +1200,7 @@ async function createTransportErrorResponse(error, authenticated) {
1194
1200
  }
1195
1201
  };
1196
1202
  if (authenticated) {
1197
- const encryptedError = isTimeError && error.serverTime !== void 0 ? {
1203
+ const encryptedError = isTimeError ? {
1198
1204
  ...publicError,
1199
1205
  message: error.message,
1200
1206
  details: { serverTime: error.serverTime }
@@ -1217,15 +1223,14 @@ async function createTransportErrorResponse(error, authenticated) {
1217
1223
  }
1218
1224
 
1219
1225
  // src/transport/crypto-transport-client.ts
1226
+ function isSessionUsable(session) {
1227
+ return Date.now() < session.renewAt && performance.now() < session.monotonicRenewAt;
1228
+ }
1220
1229
  function createTransportClient(options) {
1221
1230
  const baseFetch = options.fetch ?? fetch;
1222
1231
  const localLimits = options.maxPlaintextBytes === void 0 ? void 0 : resolveTransportBodyLimits(options.maxPlaintextBytes);
1223
- let bodyLimits = localLimits ?? resolveTransportBodyLimits();
1224
- let keyPair = null;
1225
- let serverPublicKey = null;
1226
- let serverKeyId = null;
1227
- let clientId = null;
1228
- let initPromise = null;
1232
+ let session;
1233
+ let initPromise;
1229
1234
  let generation = 0;
1230
1235
  let handshakeController;
1231
1236
  async function doInit(attemptGeneration) {
@@ -1237,6 +1242,8 @@ function createTransportClient(options) {
1237
1242
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientKeyGenerateFailed"));
1238
1243
  const controller = new AbortController();
1239
1244
  handshakeController = controller;
1245
+ const exchangeStartedAt = Date.now();
1246
+ const monotonicStart = performance.now();
1240
1247
  const timeout = setTimeout(() => controller.abort(new DOMException("Key exchange timed out", "TimeoutError")), 3e4);
1241
1248
  try {
1242
1249
  let response;
@@ -1269,6 +1276,7 @@ function createTransportClient(options) {
1269
1276
  if (!isKeyExchangeResponse(body)) {
1270
1277
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeMissingFields"));
1271
1278
  }
1279
+ let bodyLimits;
1272
1280
  try {
1273
1281
  const serverLimits = resolveTransportBodyLimits(body.maxPlaintextBytes);
1274
1282
  bodyLimits = resolveTransportBodyLimits(Math.min(localLimits?.plaintextBytes ?? serverLimits.plaintextBytes, serverLimits.plaintextBytes));
@@ -1277,11 +1285,21 @@ function createTransportClient(options) {
1277
1285
  }
1278
1286
  if (attemptGeneration !== generation || controller.signal.aborted)
1279
1287
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
1280
- keyPair = localKeyPair;
1281
- serverPublicKey = body.serverPublicKey;
1282
- serverKeyId = body.serverKeyId;
1283
- clientId = body.clientId;
1284
- return ok(void 0);
1288
+ const ttlMs = body.clientKeyTtlSeconds === void 0 ? Number.POSITIVE_INFINITY : body.clientKeyTtlSeconds * 1e3;
1289
+ const usableMs = ttlMs - Math.min(ttlMs / 10, 3e4);
1290
+ const nextSession = {
1291
+ keyPair: localKeyPair,
1292
+ serverPublicKey: body.serverPublicKey,
1293
+ serverKeyId: body.serverKeyId,
1294
+ clientId: body.clientId,
1295
+ bodyLimits,
1296
+ renewAt: exchangeStartedAt + usableMs,
1297
+ monotonicRenewAt: monotonicStart + usableMs
1298
+ };
1299
+ if (!isSessionUsable(nextSession))
1300
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeRequestFailed"));
1301
+ session = nextSession;
1302
+ return ok(nextSession);
1285
1303
  } finally {
1286
1304
  clearTimeout(timeout);
1287
1305
  if (handshakeController === controller)
@@ -1289,39 +1307,23 @@ function createTransportClient(options) {
1289
1307
  }
1290
1308
  }
1291
1309
  async function ensureReady() {
1292
- if (clientId && serverPublicKey)
1293
- return ok(void 0);
1310
+ if (session && isSessionUsable(session))
1311
+ return ok(session);
1294
1312
  if (!initPromise) {
1295
- const pending = doInit(generation).then((result) => {
1296
- if (!result.success && initPromise === pending)
1297
- initPromise = null;
1298
- return result;
1313
+ session = void 0;
1314
+ const pending = doInit(generation).finally(() => {
1315
+ if (initPromise === pending)
1316
+ initPromise = void 0;
1299
1317
  });
1300
1318
  initPromise = pending;
1301
1319
  }
1302
1320
  return initPromise;
1303
1321
  }
1304
- function encryptBody(plaintext, context) {
1305
- if (!serverPublicKey || !serverKeyId || !keyPair)
1306
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
1307
- return sealTransportPayload(options.crypto, plaintext, context, "request", 0, serverPublicKey, keyPair.privateKey, bodyLimits);
1308
- }
1309
- function decryptPayload(payload) {
1310
- if (!keyPair || !serverPublicKey)
1311
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
1312
- return openTransportPayload(options.crypto, payload, keyPair.privateKey, serverPublicKey, bodyLimits);
1313
- }
1314
- const encryptedFetch = async (input, init) => {
1315
- const requestInput = isRequestLike(input) ? input : void 0;
1316
- const signal = init?.signal ?? requestInput?.signal ?? void 0;
1322
+ async function waitForSession(signal) {
1317
1323
  signal?.throwIfAborted();
1318
- const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : requestInput?.url ?? String(input);
1319
- if (targetUrl === options.keyExchangeUrl)
1320
- return baseFetch(input, init);
1321
1324
  let onAbort;
1322
- let ready;
1323
1325
  try {
1324
- ready = await (signal ? Promise.race([
1326
+ const ready = await (signal ? Promise.race([
1325
1327
  ensureReady(),
1326
1328
  new Promise((_resolve, reject) => {
1327
1329
  onAbort = () => reject(signal.reason);
@@ -1330,24 +1332,36 @@ function createTransportClient(options) {
1330
1332
  onAbort();
1331
1333
  })
1332
1334
  ]) : ensureReady());
1335
+ if (!ready.success)
1336
+ throw new Error(ready.error.message);
1337
+ return ready.data;
1333
1338
  } finally {
1334
1339
  if (onAbort)
1335
1340
  signal?.removeEventListener("abort", onAbort);
1336
1341
  }
1337
- if (!ready.success)
1338
- throw new Error(ready.error.message);
1342
+ }
1343
+ const encryptedFetch = async (input, init) => {
1344
+ const requestInput = isRequestLike(input) ? input : void 0;
1345
+ const signal = init?.signal ?? requestInput?.signal ?? void 0;
1346
+ signal?.throwIfAborted();
1347
+ const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : requestInput?.url ?? String(input);
1348
+ if (targetUrl === options.keyExchangeUrl)
1349
+ return baseFetch(input, init);
1350
+ const requestGeneration = generation;
1351
+ let requestSession = await waitForSession(signal);
1352
+ if (requestGeneration !== generation)
1353
+ throw new Error(cryptoM("crypto_transportClientNotInitialized"));
1354
+ const method = (init?.method ?? requestInput?.method ?? "GET").toUpperCase();
1355
+ const requestBody = init?.body != null ? new Response(init.body).body : method === "GET" || method === "HEAD" ? null : requestInput?.body ?? null;
1356
+ const plaintext = await readTransportBody(requestBody, { maxBytes: requestSession.bodyLimits.plaintextBytes, signal });
1357
+ if (requestGeneration === generation && (requestSession !== session || !isSessionUsable(requestSession)))
1358
+ requestSession = await waitForSession(signal);
1359
+ signal?.throwIfAborted();
1360
+ if (requestGeneration !== generation || !isSessionUsable(requestSession))
1361
+ throw new Error(cryptoM("crypto_transportClientNotInitialized"));
1362
+ const { bodyLimits, clientId, keyPair, serverKeyId, serverPublicKey } = requestSession;
1339
1363
  const headers = new Headers(init?.headers ?? requestInput?.headers);
1340
1364
  headers.set(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER, clientId);
1341
- let rawBody = init?.body;
1342
- if (rawBody == null && requestInput) {
1343
- const method2 = (init?.method ?? requestInput.method).toUpperCase();
1344
- if (method2 !== "GET" && method2 !== "HEAD") {
1345
- const text = await readTransportBody(requestInput.body, { maxBytes: bodyLimits.plaintextBytes, signal });
1346
- if (text)
1347
- rawBody = text;
1348
- }
1349
- }
1350
- const method = (init?.method ?? requestInput?.method ?? "GET").toUpperCase();
1351
1365
  const base = new URL(options.keyExchangeUrl, globalThis.location?.href ?? "http://localhost");
1352
1366
  const url = new URL(targetUrl, base);
1353
1367
  const pathHash = hashTransportPath(options.crypto, url.pathname + url.search);
@@ -1362,10 +1376,12 @@ function createTransportClient(options) {
1362
1376
  method,
1363
1377
  pathHash: pathHash.data
1364
1378
  };
1365
- const plaintext = rawBody == null ? "" : await readTransportBody(new Response(rawBody).body, { maxBytes: bodyLimits.plaintextBytes, signal });
1366
- const encResult = encryptBody(plaintext, context);
1367
- if (!encResult.success)
1379
+ const encResult = sealTransportPayload(options.crypto, plaintext, context, "request", 0, serverPublicKey, keyPair.privateKey, bodyLimits);
1380
+ if (!encResult.success) {
1381
+ if (encResult.error.cause instanceof TransportBodyTooLargeError)
1382
+ throw encResult.error.cause;
1368
1383
  throw new Error(encResult.error.message);
1384
+ }
1369
1385
  const serialized = JSON.stringify(encResult.data);
1370
1386
  if (new TextEncoder().encode(serialized).byteLength > bodyLimits.encryptedPayloadBytes)
1371
1387
  throw new TransportBodyTooLargeError(bodyLimits.encryptedPayloadBytes, new TextEncoder().encode(serialized).byteLength, { stage: "encrypted" });
@@ -1432,7 +1448,9 @@ function createTransportClient(options) {
1432
1448
  if (!isValidEncryptedPayload(payload, bodyLimits) || payload.direction !== "response" || payload.status !== response.status || !sameTransportContext(payload.context, context)) {
1433
1449
  throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
1434
1450
  }
1435
- const decResult = decryptPayload(payload);
1451
+ if (requestGeneration !== generation)
1452
+ throw new Error(cryptoM("crypto_transportClientNotInitialized"));
1453
+ const decResult = openTransportPayload(options.crypto, payload, keyPair.privateKey, serverPublicKey, bodyLimits);
1436
1454
  if (!decResult.success) {
1437
1455
  if (decResult.error.cause instanceof TransportBodyTooLargeError)
1438
1456
  throw decResult.error.cause;
@@ -1448,7 +1466,9 @@ function createTransportClient(options) {
1448
1466
  }
1449
1467
  if (failure?.success === false && failure.error?.code === HaiCryptoError.TRANSPORT_TIME_INVALID.code) {
1450
1468
  const serverTime = failure.error.details?.serverTime;
1451
- throw new TransportTimeError(void 0, typeof serverTime === "number" ? serverTime : void 0);
1469
+ if (typeof serverTime !== "number")
1470
+ throw new TypeError(cryptoM("crypto_transportTimeResponseInvalid"));
1471
+ throw new TransportTimeError(void 0, serverTime);
1452
1472
  }
1453
1473
  }
1454
1474
  if (response.status === 500) {
@@ -1474,18 +1494,18 @@ function createTransportClient(options) {
1474
1494
  });
1475
1495
  };
1476
1496
  return {
1477
- init: ensureReady,
1497
+ async init() {
1498
+ const ready = await ensureReady();
1499
+ return ready.success ? ok(void 0) : ready;
1500
+ },
1478
1501
  encryptedFetch,
1479
- ready: () => clientId !== null && serverPublicKey !== null,
1502
+ ready: () => session !== void 0 && isSessionUsable(session),
1480
1503
  destroy() {
1481
1504
  generation++;
1482
1505
  handshakeController?.abort();
1483
1506
  handshakeController = void 0;
1484
- keyPair = null;
1485
- serverPublicKey = null;
1486
- serverKeyId = null;
1487
- clientId = null;
1488
- initPromise = null;
1507
+ session = void 0;
1508
+ initPromise = void 0;
1489
1509
  }
1490
1510
  };
1491
1511
  }
@@ -1500,7 +1520,7 @@ function isKeyExchangeResponse(body) {
1500
1520
  if (!body || typeof body !== "object")
1501
1521
  return false;
1502
1522
  const fields = body;
1503
- return typeof fields.serverPublicKey === "string" && fields.serverPublicKey.length > 0 && new TextEncoder().encode(fields.serverPublicKey).byteLength <= TRANSPORT_BODY_LIMITS.publicKeyBytes && typeof fields.clientId === "string" && fields.clientId.length > 0 && typeof fields.serverKeyId === "string" && /^[\w-]{1,64}$/.test(fields.serverKeyId);
1523
+ return typeof fields.serverPublicKey === "string" && fields.serverPublicKey.length > 0 && new TextEncoder().encode(fields.serverPublicKey).byteLength <= TRANSPORT_BODY_LIMITS.publicKeyBytes && typeof fields.clientId === "string" && fields.clientId.length > 0 && typeof fields.serverKeyId === "string" && /^[\w-]{1,64}$/.test(fields.serverKeyId) && typeof fields.maxPlaintextBytes === "number" && (fields.clientKeyTtlSeconds === void 0 || isValidTransportKeyTtlSeconds(fields.clientKeyTtlSeconds));
1504
1524
  }
1505
1525
  function parseTransportServerKeyRing(serialized) {
1506
1526
  try {
@@ -1554,6 +1574,9 @@ function createTransportEncryption(cryptoService, options) {
1554
1574
  }
1555
1575
  if (core.module.requiresSharedState(globalThis.process?.env ?? {}) && options.keyStore.stateScope !== "shared")
1556
1576
  return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportKeyStoreRequired"));
1577
+ const { clientKeyTtlSeconds } = options.keyStore;
1578
+ if (clientKeyTtlSeconds !== void 0 && !isValidTransportKeyTtlSeconds(clientKeyTtlSeconds))
1579
+ return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportKeyStoreTtlInvalid"));
1557
1580
  const serverKeys = /* @__PURE__ */ new Map();
1558
1581
  for (const [id, key] of Object.entries(ring.data.keys)) {
1559
1582
  const probe = cryptoService.asymmetric.encrypt("hai-transport-key-check", key.publicKey);
@@ -1569,6 +1592,7 @@ function createTransportEncryption(cryptoService, options) {
1569
1592
  const keyStore = options.keyStore;
1570
1593
  const manager = {
1571
1594
  bodyLimits,
1595
+ clientKeyTtlSeconds,
1572
1596
  getServerKeyId() {
1573
1597
  return activeKeyId;
1574
1598
  },
@@ -1886,13 +1910,6 @@ function createInMemoryKeyStore(maxClients = DEFAULT_MAX_CLIENTS) {
1886
1910
 
1887
1911
  // src/transport/store-provider/crypto-transport-store-redis.ts
1888
1912
  var TRANSPORT_KEY_CACHE_PREFIX = "hai:crypto:transport:client";
1889
- function normalizeTtlSeconds(ttlSeconds) {
1890
- if (ttlSeconds === void 0)
1891
- return void 0;
1892
- if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds <= 0 || ttlSeconds > 2147483647)
1893
- throw new TypeError(cryptoM("crypto_transportKeyStoreTtlInvalid"));
1894
- return ttlSeconds;
1895
- }
1896
1913
  function createClientId2() {
1897
1914
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
1898
1915
  return `c_${crypto.randomUUID()}`;
@@ -1909,11 +1926,14 @@ function unwrapResult(operation, result) {
1909
1926
  throw toOperationError(operation, result.error);
1910
1927
  }
1911
1928
  function createRedisTransportKeyStore(options) {
1912
- const ttlSeconds = normalizeTtlSeconds(options.ttlSeconds);
1929
+ const { ttlSeconds } = options;
1930
+ if (ttlSeconds !== void 0 && !isValidTransportKeyTtlSeconds(ttlSeconds))
1931
+ throw new TypeError(cryptoM("crypto_transportKeyStoreTtlInvalid"));
1913
1932
  function buildCacheKey(clientId) {
1914
1933
  return `${TRANSPORT_KEY_CACHE_PREFIX}:${clientId}`;
1915
1934
  }
1916
1935
  return {
1936
+ clientKeyTtlSeconds: ttlSeconds,
1917
1937
  get stateScope() {
1918
1938
  return options.cache.config?.type === "redis" ? "shared" : "local";
1919
1939
  },
@@ -1972,7 +1992,7 @@ function unwrapResult2(operation, result) {
1972
1992
  }
1973
1993
  function createReldbTransportKeyStore(options) {
1974
1994
  const { ttlSeconds } = options;
1975
- if (ttlSeconds !== void 0 && (!Number.isSafeInteger(ttlSeconds) || ttlSeconds <= 0 || ttlSeconds > 2147483647))
1995
+ if (ttlSeconds !== void 0 && !isValidTransportKeyTtlSeconds(ttlSeconds))
1976
1996
  throw new TypeError(cryptoM("crypto_transportKeyStoreTtlInvalid"));
1977
1997
  const maxClients = options.maxClients ?? 1e4;
1978
1998
  if (!Number.isSafeInteger(maxClients) || maxClients <= 0)
@@ -2072,6 +2092,7 @@ function createReldbTransportKeyStore(options) {
2072
2092
  return withCapacityLock(pruneRows);
2073
2093
  }
2074
2094
  return {
2095
+ clientKeyTtlSeconds: ttlSeconds,
2075
2096
  get stateScope() {
2076
2097
  return options.reldb.config?.type && options.reldb.config.type !== "sqlite" ? "shared" : "local";
2077
2098
  },