@tinycloud/node-sdk 3.8.0-beta.2 → 3.8.0-beta.20

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 (44) hide show
  1. package/README.md +6 -0
  2. package/dist/{TinyCloudNode-B6VOBrCb.d.ts → TinyCloudNode-BRyeZas2.d.ts} +47 -6
  3. package/dist/{TinyCloudNode-CVO41O12.d.cts → TinyCloudNode-NrmztjDd.d.cts} +47 -6
  4. package/dist/{WasmKeyProvider-5Ee7fcAw.d.cts → WasmKeyProvider-BKCPRC-c.d.cts} +21 -1
  5. package/dist/{WasmKeyProvider-5Ee7fcAw.d.ts → WasmKeyProvider-BKCPRC-c.d.ts} +21 -1
  6. package/dist/{authority-DGi1hIMK.d.ts → authority-D6JMJwor.d.ts} +1 -1
  7. package/dist/{authority-DgVV80bI.d.cts → authority-Z-reu6zL.d.cts} +1 -1
  8. package/dist/core.cjs +336 -29
  9. package/dist/core.cjs.map +1 -1
  10. package/dist/core.d.cts +5 -4
  11. package/dist/core.d.ts +5 -4
  12. package/dist/core.js +330 -24
  13. package/dist/core.js.map +1 -1
  14. package/dist/credentials-core.d.cts +1 -1
  15. package/dist/credentials-core.d.ts +1 -1
  16. package/dist/credentials.d.cts +1 -1
  17. package/dist/credentials.d.ts +1 -1
  18. package/dist/{delegation-0LKZkmP2.d.cts → delegation-Ckc3UETl.d.cts} +1 -1
  19. package/dist/{delegation-0LKZkmP2.d.ts → delegation-Ckc3UETl.d.ts} +1 -1
  20. package/dist/encryption-keys.d.cts +2 -2
  21. package/dist/encryption-keys.d.ts +2 -2
  22. package/dist/index.cjs +304 -40
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +4 -3
  25. package/dist/index.d.ts +4 -3
  26. package/dist/index.js +293 -27
  27. package/dist/index.js.map +1 -1
  28. package/dist/replication/authority.cjs +3 -0
  29. package/dist/replication/authority.cjs.map +1 -1
  30. package/dist/replication/authority.d.cts +4 -3
  31. package/dist/replication/authority.d.ts +4 -3
  32. package/dist/replication/authority.js +3 -0
  33. package/dist/replication/authority.js.map +1 -1
  34. package/dist/replication/runtime.cjs +1 -0
  35. package/dist/replication/runtime.cjs.map +1 -1
  36. package/dist/replication/runtime.js +1 -0
  37. package/dist/replication/runtime.js.map +1 -1
  38. package/dist/share-receive.cjs +775 -0
  39. package/dist/share-receive.cjs.map +1 -0
  40. package/dist/share-receive.d.cts +32 -0
  41. package/dist/share-receive.d.ts +32 -0
  42. package/dist/share-receive.js +777 -0
  43. package/dist/share-receive.js.map +1 -0
  44. package/package.json +11 -4
package/dist/core.js CHANGED
@@ -242,10 +242,10 @@ import {
242
242
  activateSessionWithHost,
243
243
  checkNodeInfo,
244
244
  AutoApproveSpaceCreationHandler,
245
+ ManifestValidationError,
245
246
  DEFAULT_MANIFEST_SPACE,
246
247
  ENCRYPTION_PERMISSION_SERVICE,
247
248
  composeManifestRequest,
248
- parseNetworkId,
249
249
  principalDidEquals,
250
250
  resourceCapabilitiesToAbilitiesMap,
251
251
  resourceCapabilitiesToSpaceAbilitiesMap,
@@ -273,6 +273,44 @@ import {
273
273
  HOOKS,
274
274
  ENCRYPTION
275
275
  } from "@tinycloud/sdk-core";
276
+ import { parseEncryptionResource } from "@tinycloud/sdk-services/encryption/resource";
277
+ import { parseNetworkId } from "@tinycloud/sdk-services";
278
+
279
+ // src/authorization/appKeyHostBinding.ts
280
+ var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
281
+ var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
282
+ var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
283
+ function appKeyHostBinding(domain, appId) {
284
+ if (!APP_ID.test(appId)) return false;
285
+ if (domain.endsWith(":")) return false;
286
+ let url;
287
+ try {
288
+ url = new URL(`https://${domain}`);
289
+ } catch {
290
+ return false;
291
+ }
292
+ if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
293
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
294
+ const host = url.hostname;
295
+ if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
296
+ const labels = host.split(".");
297
+ if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
298
+ if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
299
+ const reversed = labels.reverse().join(".");
300
+ return appId === reversed || appId.startsWith(`${reversed}.`);
301
+ }
302
+ function isAppKeyHostBound(keysAppId, origin) {
303
+ if (typeof origin === "string" && !origin.includes("://")) {
304
+ return appKeyHostBinding(origin, keysAppId);
305
+ }
306
+ let url;
307
+ try {
308
+ url = typeof origin === "string" ? new URL(origin) : origin;
309
+ } catch {
310
+ return false;
311
+ }
312
+ return appKeyHostBinding(url.host, keysAppId);
313
+ }
276
314
 
277
315
  // src/replication/authority-sign-in.ts
278
316
  import {
@@ -410,6 +448,7 @@ function cloneAbilitiesMap(actions) {
410
448
  var NodeUserAuthorization = class {
411
449
  constructor(config) {
412
450
  this.activeSessionKeyId = "default";
451
+ this.preparedSessionCounter = 0;
413
452
  /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
414
453
  this.sessionGeneration = 0;
415
454
  /**
@@ -766,6 +805,23 @@ var NodeUserAuthorization = class {
766
805
  );
767
806
  return this._capabilityRequest;
768
807
  }
808
+ async refreshNamedKeyFeatureForSignIn(address, chainId) {
809
+ const request = this.getCapabilityRequest();
810
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
811
+ const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
812
+ if (hasNamedKeys) {
813
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
814
+ await this.refreshNodeInfo();
815
+ }
816
+ }
817
+ /** Manifest key paths that this sign-in cannot grant. */
818
+ get unavailableKeyPaths() {
819
+ const request = this.getCapabilityRequest();
820
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
821
+ return (request?.manifests ?? []).flatMap(
822
+ (manifest) => this._nodeFeatures.includes("encryption-named-keys-v1") && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id) ? [] : (manifest.keys ?? []).map((entry) => "path" in entry ? entry.path : entry.prefix)
823
+ );
824
+ }
769
825
  resolveSpaceName(space, address, chainId) {
770
826
  if (space.startsWith("tinycloud:")) {
771
827
  return space;
@@ -957,15 +1013,32 @@ var NodeUserAuthorization = class {
957
1013
  }
958
1014
  const rawAbilities = {};
959
1015
  const currentDid = pkhDid(address, chainId);
1016
+ if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
1017
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
1018
+ for (const manifest of request.manifests) {
1019
+ if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
1020
+ for (const declaration of manifest.keys ?? []) {
1021
+ const path = "path" in declaration ? declaration.path : declaration.prefix;
1022
+ const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
1023
+ for (const ability of declaration.abilities) {
1024
+ addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
1025
+ }
1026
+ }
1027
+ }
1028
+ }
960
1029
  const spaceResources = request.resources.filter((entry) => {
961
1030
  if (entry.service !== ENCRYPTION_PERMISSION_SERVICE) {
962
1031
  return true;
963
1032
  }
1033
+ const parsed = entry.path.includes("/") ? parseEncryptionResource(entry.path) : { ...parseNetworkId(entry.path), kind: "network" };
1034
+ const sharePrefix = parsed.kind === "prefix" && parsed.network === "default" && parsed.prefix === "share" && entry.actions.length === 1 && entry.actions[0] === DECRYPT_ACTION && didPrincipalMatches(parsed.ownerDid, currentDid);
1035
+ if (parsed.kind !== "network" && !sharePrefix) {
1036
+ throw new ManifestValidationError("named keys go in manifest.keys");
1037
+ }
964
1038
  for (const action of entry.actions) {
965
1039
  addRawAbility(rawAbilities, entry.path, action);
966
1040
  }
967
- if (entry.actions.includes(DECRYPT_ACTION)) {
968
- const parsed = parseNetworkId(entry.path);
1041
+ if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
969
1042
  if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
970
1043
  addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
971
1044
  }
@@ -1002,8 +1075,7 @@ var NodeUserAuthorization = class {
1002
1075
  /**
1003
1076
  * Build SIWE overrides from the top-level nonce and siweConfig.
1004
1077
  * - Top-level `nonce` is seeded first so `siweConfig.nonce` wins if both are set.
1005
- * - statement is prepended to the default statement
1006
- * - resources are appended to the default resources
1078
+ * - resources are inserted after WASM preparation, before the final ReCap
1007
1079
  * - uri triggers a warning (overwriting delegation target)
1008
1080
  * - all other fields override directly
1009
1081
  * - per-call nonce overrides siweConfig.nonce when provided
@@ -1021,9 +1093,6 @@ var NodeUserAuthorization = class {
1021
1093
  if (statement) {
1022
1094
  overrides.statement = this.statement ? `${statement} ${this.statement}` : statement;
1023
1095
  }
1024
- if (resources && resources.length > 0) {
1025
- overrides.resources = resources;
1026
- }
1027
1096
  if (uri) {
1028
1097
  console.warn(
1029
1098
  "[tinycloud] siweConfig.uri is overwriting the delegation target URI. This may break delegation chain validation if the URI does not match the session key DID."
@@ -1035,6 +1104,29 @@ var NodeUserAuthorization = class {
1035
1104
  }
1036
1105
  return overrides;
1037
1106
  }
1107
+ prepareSessionWithResources(params) {
1108
+ const resources = this.siweConfig?.resources;
1109
+ if (resources?.some((resource) => typeof resource !== "string" || !/^urn:tinycloud:recipients:[A-Za-z0-9_-]+$/.test(resource))) {
1110
+ throw new Error("SIWE metadata resources must be base64url recipients URIs");
1111
+ }
1112
+ const prepared = this.wasm.prepareSession(params);
1113
+ if (!resources?.length) return prepared;
1114
+ const recapLines = /^- urn:recap:[^\r\n]+$/gim;
1115
+ const recaps = prepared.siwe.match(recapLines);
1116
+ const recap = recaps?.[0];
1117
+ if (recaps?.length !== 1 || !prepared.siwe.endsWith(recap)) {
1118
+ throw new Error("Prepared SIWE must end with exactly one ReCap");
1119
+ }
1120
+ const insertedLines = resources.map((resource) => `- ${resource}`);
1121
+ const siwe = prepared.siwe.slice(0, -recap.length) + insertedLines.join("\n") + "\n" + recap;
1122
+ const expectedFields = extractImmutableSiweFields(prepared.siwe);
1123
+ expectedFields.nonRecapResources = [expectedFields.nonRecapResources, ...insertedLines].filter(Boolean).join("\n");
1124
+ const actualRecaps = siwe.match(recapLines);
1125
+ if (actualRecaps?.length !== 1 || actualRecaps[0] !== recap || diffImmutableSiweFields(expectedFields, extractImmutableSiweFields(siwe)).length) {
1126
+ throw new Error("SIWE metadata insertion changed the prepared message");
1127
+ }
1128
+ return { ...prepared, siwe };
1129
+ }
1038
1130
  /**
1039
1131
  * Add an extension to the authorization flow.
1040
1132
  */
@@ -1282,7 +1374,7 @@ var NodeUserAuthorization = class {
1282
1374
  spaceAbilities[spaceId] = resourceCapabilitiesToAbilitiesMap(resources);
1283
1375
  }
1284
1376
  const abilities = spaceAbilities[options.spaceId] ?? resourceCapabilitiesToAbilitiesMap([]);
1285
- const prepared = this.wasm.prepareSession({
1377
+ const prepared = this.prepareSessionWithResources({
1286
1378
  abilities,
1287
1379
  spaceAbilities,
1288
1380
  ...options.rawAbilities !== void 0 ? { rawAbilities: options.rawAbilities } : {},
@@ -1361,6 +1453,8 @@ var NodeUserAuthorization = class {
1361
1453
  this._chainId = chainId;
1362
1454
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
1363
1455
  this.assertSignInCurrent(generation);
1456
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
1457
+ this.assertSignInCurrent(generation);
1364
1458
  const keyId = `session-${Date.now()}`;
1365
1459
  this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
1366
1460
  this.activeSessionKeyId = keyId;
@@ -1373,7 +1467,7 @@ var NodeUserAuthorization = class {
1373
1467
  const spaceId = capabilityPlan.spaceId;
1374
1468
  const now = /* @__PURE__ */ new Date();
1375
1469
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
1376
- const prepared = this.wasm.prepareSession({
1470
+ const prepared = this.prepareSessionWithResources({
1377
1471
  abilities: capabilityPlan.abilities,
1378
1472
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
1379
1473
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -1507,9 +1601,9 @@ var NodeUserAuthorization = class {
1507
1601
  refuseCaveated(this.getCapabilityRequest()?.manifests);
1508
1602
  const address = canonicalizeAddress(await this.signer.getAddress());
1509
1603
  const chainId = await this.signer.getChainId();
1510
- const keyId = `session-${Date.now()}`;
1511
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
1512
- this.activeSessionKeyId = keyId;
1604
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
1605
+ const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
1606
+ this.sessionManager.createSessionKey(keyId);
1513
1607
  const jwkString = this.sessionManager.jwk(keyId);
1514
1608
  if (!jwkString) {
1515
1609
  throw new Error("Failed to create session key");
@@ -1519,7 +1613,7 @@ var NodeUserAuthorization = class {
1519
1613
  const spaceId = capabilityPlan.spaceId;
1520
1614
  const now = /* @__PURE__ */ new Date();
1521
1615
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
1522
- const prepared = this.wasm.prepareSession({
1616
+ const prepared = this.prepareSessionWithResources({
1523
1617
  abilities: capabilityPlan.abilities,
1524
1618
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
1525
1619
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -1590,6 +1684,7 @@ var NodeUserAuthorization = class {
1590
1684
  await this.sessionStorage.save(address, persistedData);
1591
1685
  this._session = clientSession;
1592
1686
  this._tinyCloudSession = tinyCloudSession;
1687
+ this.activeSessionKeyId = keyId;
1593
1688
  this._address = address;
1594
1689
  this._chainId = chainId;
1595
1690
  await this.refreshNodeInfo();
@@ -1636,6 +1731,11 @@ var NodeUserAuthorization = class {
1636
1731
  * @param options Optional overrides for the OpenKey request.
1637
1732
  */
1638
1733
  async signInWithOpenKey(authorizeFn, options) {
1734
+ const pending = await this.prepareOpenKeySignIn(options);
1735
+ return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
1736
+ }
1737
+ /** Finish address, host and SIWE preparation before an approval click. */
1738
+ async prepareOpenKeySignIn(options) {
1639
1739
  refuseCaveated(this.getCapabilityRequest()?.manifests);
1640
1740
  if (options?.host !== void 0) {
1641
1741
  const overrideHost = options.host.trim();
@@ -1647,7 +1747,7 @@ var NodeUserAuthorization = class {
1647
1747
  const signingAddress = canonicalizeAddress(await this.signer.getAddress());
1648
1748
  const signingChainId = await this.signer.getChainId();
1649
1749
  await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
1650
- const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
1750
+ const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
1651
1751
  const presentation = this.buildPresentationEnvelope(options?.reason);
1652
1752
  const hostHint = this.primaryTinyCloudHost;
1653
1753
  const originalRequest = {
@@ -1659,12 +1759,32 @@ var NodeUserAuthorization = class {
1659
1759
  keyId: options?.openkeyKeyId,
1660
1760
  presentation
1661
1761
  };
1762
+ return {
1763
+ request: originalRequest,
1764
+ prepared,
1765
+ keyId,
1766
+ jwk,
1767
+ address,
1768
+ chainId,
1769
+ generation: this.sessionGeneration,
1770
+ createdAt: Date.now()
1771
+ };
1772
+ }
1773
+ /** Complete a prepared approval. The caller starts authorization before awaiting this method. */
1774
+ async completeOpenKeySignIn(pending, authorization, options) {
1775
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
1776
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
1777
+ }
1778
+ if (pending.generation !== this.sessionGeneration) {
1779
+ throw new Error("Prepared OpenKey sign-in expired after sign-out");
1780
+ }
1781
+ const { request: originalRequest, prepared, keyId, jwk } = pending;
1662
1782
  let result;
1663
1783
  let signed;
1664
1784
  let signature;
1665
1785
  for (let attempt = 0; ; ) {
1666
1786
  try {
1667
- result ?? (result = await authorizeFn(originalRequest));
1787
+ result ?? (result = await authorization);
1668
1788
  signed = await this.verifyOpenKeyResult(result, prepared);
1669
1789
  signature = result.signature;
1670
1790
  break;
@@ -2085,6 +2205,50 @@ var NodeUserAuthorization = class {
2085
2205
  }
2086
2206
  };
2087
2207
 
2208
+ // src/authorization/openKeyBridge.ts
2209
+ function wireOpenKeyAuthorize(openkey, opts) {
2210
+ return async (input) => {
2211
+ const result = await openkey.authorizeTinyCloud({
2212
+ protocolVersion: 1,
2213
+ siwe: input.siwe,
2214
+ keyId: input.keyId,
2215
+ jwk: input.jwk,
2216
+ host: input.host,
2217
+ // Forward the presentation envelope verbatim — the OpenKey SDK
2218
+ // validates and size-bounds it before use.
2219
+ presentation: input.presentation
2220
+ }, opts);
2221
+ if (result.protocolVersion !== 1) {
2222
+ throw new Error(
2223
+ `wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
2224
+ );
2225
+ }
2226
+ if (typeof result.signedMessage !== "string" || !result.signedMessage) {
2227
+ throw new Error(
2228
+ "wireOpenKeyAuthorize: OpenKey returned no signedMessage \u2014 cannot complete a TinyCloud session"
2229
+ );
2230
+ }
2231
+ if (!Array.isArray(result.selectedActionKeys)) {
2232
+ throw new Error(
2233
+ "wireOpenKeyAuthorize: OpenKey returned no selectedActionKeys"
2234
+ );
2235
+ }
2236
+ if (!Array.isArray(result.permissions)) {
2237
+ throw new Error(
2238
+ "wireOpenKeyAuthorize: OpenKey returned no permissions"
2239
+ );
2240
+ }
2241
+ return {
2242
+ protocolVersion: 1,
2243
+ address: result.address,
2244
+ signature: result.signature,
2245
+ signedMessage: result.signedMessage,
2246
+ selectedActionKeys: result.selectedActionKeys,
2247
+ permissions: result.permissions
2248
+ };
2249
+ };
2250
+ }
2251
+
2088
2252
  // src/recordingServiceContext.ts
2089
2253
  import {
2090
2254
  ServiceContext
@@ -2770,10 +2934,12 @@ import {
2770
2934
  ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
2771
2935
  CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError2,
2772
2936
  PermissionNotInManifestError,
2937
+ ManifestValidationError as ManifestValidationError2,
2773
2938
  SessionExpiredError,
2774
2939
  canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
2775
2940
  recapCaveatsEqual as recapCaveatsEqual2,
2776
2941
  expandPermissionEntries as expandPermissionEntriesCore,
2942
+ expandDelegationPermissionEntries,
2777
2943
  isCapabilitySubset as isCapabilitySubset2,
2778
2944
  isExplicitOnlyAction,
2779
2945
  parseRecapCapabilities,
@@ -2804,6 +2970,7 @@ import {
2804
2970
  withoutShareLink,
2805
2971
  verifyEip191MessageSignature,
2806
2972
  signCompactUcanRootAuthorization,
2973
+ signCompactUcanAuthorization,
2807
2974
  markDelegationTerminal,
2808
2975
  isStorageFullError,
2809
2976
  STORAGE_FULL_MESSAGE,
@@ -2819,6 +2986,7 @@ import {
2819
2986
  tinyCloudDebugLogger
2820
2987
  } from "@tinycloud/sdk-services";
2821
2988
  import { RequestLog, observeReplicaReads, readOnlyRequestLog } from "@tinycloud/sdk-services/telemetry/requestLog";
2989
+ import { parseEncryptionResource as parseEncryptionResource2 } from "@tinycloud/sdk-services/encryption/resource";
2822
2990
 
2823
2991
  // src/account/AccountService.ts
2824
2992
  import { AccountService } from "@tinycloud/sdk-core";
@@ -3310,6 +3478,7 @@ var ROOT_DELEGATION_ACTIONS = [
3310
3478
  DUCKDB2.ALL
3311
3479
  ];
3312
3480
  var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
3481
+ var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
3313
3482
  function unifiedRootAttenuation(capabilities) {
3314
3483
  const attenuation = {};
3315
3484
  for (const capability of capabilities) {
@@ -3807,6 +3976,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3807
3976
  constructor(config = {}) {
3808
3977
  this.signer = null;
3809
3978
  this.auth = null;
3979
+ this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
3810
3980
  this.tc = null;
3811
3981
  this._chainId = 1;
3812
3982
  this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
@@ -3841,6 +4011,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3841
4011
  return void 0;
3842
4012
  }
3843
4013
  };
4014
+ this._keysUnavailable = [];
3844
4015
  this._baseVaults = /* @__PURE__ */ new Map();
3845
4016
  this._baseSecrets = /* @__PURE__ */ new Map();
3846
4017
  this._secrets = /* @__PURE__ */ new Map();
@@ -4668,6 +4839,8 @@ var _TinyCloudNode = class _TinyCloudNode {
4668
4839
  this._hooks = void 0;
4669
4840
  this._vault = void 0;
4670
4841
  this._encryption = void 0;
4842
+ this._namedKeyClient = void 0;
4843
+ this._keysUnavailable = [];
4671
4844
  this._baseVaults.clear();
4672
4845
  this._baseSecrets.clear();
4673
4846
  this._secrets.clear();
@@ -4744,6 +4917,37 @@ var _TinyCloudNode = class _TinyCloudNode {
4744
4917
  }
4745
4918
  await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
4746
4919
  }
4920
+ /** Prepare the OpenKey request before the approval click. */
4921
+ async prepareOpenKeySignIn() {
4922
+ if (!this.signer || !this.tc || !this.auth) {
4923
+ throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
4924
+ }
4925
+ await this.signOutInFlight;
4926
+ await this.accountRegistryTail;
4927
+ await this.wasmBindings.ensureInitialized?.();
4928
+ return this.auth.prepareOpenKeySignIn();
4929
+ }
4930
+ /** Start OpenKey authorization synchronously, then complete normal session setup. */
4931
+ signInWithPreparedOpenKey(pending, authorizeFn) {
4932
+ if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
4933
+ throw new Error("Prepared OpenKey sign-in is unavailable");
4934
+ }
4935
+ if (this.consumedOpenKeyPendings.has(pending)) {
4936
+ throw new Error("Prepared OpenKey sign-in was already used");
4937
+ }
4938
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
4939
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
4940
+ }
4941
+ this.consumedOpenKeyPendings.add(pending);
4942
+ const authorization = authorizeFn(pending.request);
4943
+ void authorization.catch(() => void 0);
4944
+ return this.signInWith(() => {
4945
+ if (this._address !== pending.address || this._chainId !== pending.chainId) {
4946
+ throw new Error("OpenKey account changed after sign-in preparation");
4947
+ }
4948
+ return this.auth.completeOpenKeySignIn(pending, authorization);
4949
+ });
4950
+ }
4747
4951
  async signInWith(performSignIn) {
4748
4952
  refuseCaveated(this.auth?.capabilityRequest?.manifests);
4749
4953
  const epoch = this.sessionEpoch;
@@ -4790,6 +4994,8 @@ var _TinyCloudNode = class _TinyCloudNode {
4790
4994
  this._hooks = void 0;
4791
4995
  this._vault = void 0;
4792
4996
  this._encryption = void 0;
4997
+ this._namedKeyClient = void 0;
4998
+ this._keysUnavailable = [];
4793
4999
  this._baseVaults.clear();
4794
5000
  this._baseSecrets.clear();
4795
5001
  this._secrets.clear();
@@ -4842,6 +5048,26 @@ var _TinyCloudNode = class _TinyCloudNode {
4842
5048
  this.assertSessionEpoch(epoch, "Sign-in");
4843
5049
  await this.ensureRequestedEncryptionNetworks();
4844
5050
  this.assertSessionEpoch(epoch, "Sign-in");
5051
+ this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
5052
+ if (this._keysUnavailable.length > 0) {
5053
+ this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
5054
+ }
5055
+ if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
5056
+ for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
5057
+ for (const declaration of manifest.keys ?? []) {
5058
+ if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
5059
+ try {
5060
+ await this.keys.ensureKey(declaration.path);
5061
+ } catch (error) {
5062
+ this._keysUnavailable.push(declaration.path);
5063
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
5064
+ this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
5065
+ }
5066
+ this.assertSessionEpoch(epoch, "Sign-in");
5067
+ }
5068
+ }
5069
+ }
5070
+ }
4845
5071
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
4846
5072
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(SECRETS_SPACE2));
4847
5073
  }
@@ -5629,7 +5855,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5629
5855
  const networkIds = /* @__PURE__ */ new Set();
5630
5856
  for (const resource of request.resources) {
5631
5857
  if (resource.service === ENCRYPTION_PERMISSION_SERVICE2 && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
5632
- networkIds.add(resource.path);
5858
+ const parsed = resource.path.includes("/") ? parseEncryptionResource2(resource.path) : { ...parseNetworkId2(resource.path), kind: "network" };
5859
+ if (parsed.kind === "network") networkIds.add(resource.path);
5633
5860
  }
5634
5861
  }
5635
5862
  return [...networkIds];
@@ -6179,6 +6406,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6179
6406
  this._hooks = stagedGraph.hooks;
6180
6407
  this._vault = stagedGraph.vault;
6181
6408
  this._encryption = stagedGraph.encryption;
6409
+ this._namedKeyClient = void 0;
6410
+ this._keysUnavailable = [];
6182
6411
  this._capabilityRegistry = stagedGraph.capabilityRegistry;
6183
6412
  this._keyProvider = stagedGraph.keyProvider;
6184
6413
  this._sharingService = stagedGraph.sharingService;
@@ -6360,6 +6589,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6360
6589
  invoke: graph.invoke,
6361
6590
  invokeAny: graph.invokeAny,
6362
6591
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
6592
+ sessionInvocationSigner: this.sessionInvocationSignerFor(input.serviceSession),
6363
6593
  fetch: graph.fetch
6364
6594
  });
6365
6595
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -6575,6 +6805,31 @@ var _TinyCloudNode = class _TinyCloudNode {
6575
6805
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
6576
6806
  return signBytesWithJwk(bytes, session.jwk);
6577
6807
  }
6808
+ /**
6809
+ * Signs proofless invocations with `session`'s own did:key, for the node's
6810
+ * issued-delegation list and revocation by the delegator. The audience is
6811
+ * this Node's DID; each invocation lives 60 seconds.
6812
+ */
6813
+ sessionInvocationSignerFor(session) {
6814
+ return {
6815
+ did: () => session.verificationMethod.split("#", 1)[0],
6816
+ sign: async (attenuation) => {
6817
+ const now = Math.floor(Date.now() / 1e3);
6818
+ const invocation = await signCompactUcanAuthorization({
6819
+ issuerDid: session.verificationMethod.split("#", 1)[0],
6820
+ audienceDid: await this.fetchNodeId(),
6821
+ attenuation,
6822
+ facts: [{}],
6823
+ proofs: [],
6824
+ notBefore: now,
6825
+ expiresAt: now + 60,
6826
+ nonce: `urn:uuid:${crypto.randomUUID()}`,
6827
+ sign: (bytes) => signBytesWithJwk(bytes, session.jwk)
6828
+ });
6829
+ return invocation.authorization;
6830
+ }
6831
+ };
6832
+ }
6578
6833
  /** Author a proofless Policy/v3 owner root with the authenticated holder key. */
6579
6834
  async createUnifiedOwnerRoot(input) {
6580
6835
  const ownerDid = this.credentialHolderDid;
@@ -7109,7 +7364,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7109
7364
  graph.assertActive();
7110
7365
  const signed = await this.signRawNetworkAuthorization({
7111
7366
  targetNode: input.targetNode,
7112
- networkId: input.networkId,
7367
+ networkId: input.body.keyPath === void 0 ? input.networkId : `${input.networkId}/${input.body.keyPath}`,
7113
7368
  action: DECRYPT_ACTION2,
7114
7369
  facts: input.facts
7115
7370
  }, { graph, session });
@@ -7282,6 +7537,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7282
7537
  invoke: graph.invoke,
7283
7538
  invokeAny: graph.invokeAny,
7284
7539
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
7540
+ sessionInvocationSigner: this.sessionInvocationSignerFor(serviceSession),
7285
7541
  fetch: graph.fetch
7286
7542
  });
7287
7543
  this._spaceService = new SpaceService({
@@ -7532,7 +7788,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7532
7788
  * Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
7533
7789
  * `POST /revoke`, and return its Authorization header value. The node
7534
7790
  * accepts it when the owner delegated `cid` or owns a space its abilities
7535
- * target, whichever key issued it. Valid for 60 seconds. Requires the
7791
+ * target, whichever key issued it. Valid for 10 minutes. Requires the
7536
7792
  * owner wallet signer.
7537
7793
  */
7538
7794
  async signOwnerRevocation(cid) {
@@ -7548,7 +7804,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7548
7804
  chainId: session.chainId,
7549
7805
  nonce: generateNonce(),
7550
7806
  issuedAt: issuedAt.toISOString(),
7551
- expirationTime: new Date(issuedAt.getTime() + 6e4).toISOString()
7807
+ expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
7552
7808
  }).prepareMessage();
7553
7809
  const signature = await this.signer.signMessage(siwe);
7554
7810
  return this.wasmBindings.siweToDelegationHeaders({ siwe, signature }).Authorization;
@@ -7775,6 +8031,40 @@ var _TinyCloudNode = class _TinyCloudNode {
7775
8031
  get encryption() {
7776
8032
  return this.getEncryptionService();
7777
8033
  }
8034
+ /** Named keys bound to the current session. */
8035
+ get keys() {
8036
+ return new Proxy({}, {
8037
+ get: (_target, property) => {
8038
+ if (property === "refreshNodeInfo") {
8039
+ return () => {
8040
+ void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
8041
+ };
8042
+ }
8043
+ if (typeof property !== "string" || ![
8044
+ "getPublicKey",
8045
+ "describeKey",
8046
+ "createKey",
8047
+ "blockKey",
8048
+ "unblockKey",
8049
+ "ensureKey",
8050
+ "encrypt",
8051
+ "decrypt",
8052
+ "decryptMany"
8053
+ ].includes(property)) return void 0;
8054
+ return (...args) => {
8055
+ this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.js").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
8056
+ return this._namedKeyClient.then((client) => {
8057
+ const method = client[property];
8058
+ return method.apply(client, args);
8059
+ });
8060
+ };
8061
+ }
8062
+ });
8063
+ }
8064
+ /** Manifest key paths unavailable during the latest `signIn()` attempt. */
8065
+ get keysUnavailable() {
8066
+ return this._keysUnavailable;
8067
+ }
7778
8068
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
7779
8069
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
7780
8070
  return this.fetchEncryptionNetworkAt(
@@ -8664,7 +8954,19 @@ var _TinyCloudNode = class _TinyCloudNode {
8664
8954
  "delegateTo requires a non-empty permissions array"
8665
8955
  );
8666
8956
  }
8667
- const expandedEntries = this.expandPermissionEntries(permissions);
8957
+ const expandedEntries = expandDelegationPermissionEntries(permissions);
8958
+ const namedKeyEntries = expandedEntries.filter((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.path.includes("/"));
8959
+ if (namedKeyEntries.length > 0) {
8960
+ if (expandedEntries.some((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && !entry.path.includes("/"))) {
8961
+ throw new ManifestValidationError2("named-key delegation cannot include bare-network permissions");
8962
+ }
8963
+ if (options?.forceWalletSign) {
8964
+ throw new ManifestValidationError2("named-key delegation requires a signed session grant");
8965
+ }
8966
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
8967
+ const { subset, missing } = this.signedCapabilitySubset(namedKeyEntries, granted);
8968
+ if (!subset) throw new PermissionNotInManifestError(missing, granted);
8969
+ }
8668
8970
  const now = /* @__PURE__ */ new Date();
8669
8971
  const expiryMs = resolveExpiryMs(options?.expiry);
8670
8972
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -10049,7 +10351,7 @@ import {
10049
10351
  CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError3,
10050
10352
  PermissionNotInManifestError as PermissionNotInManifestError2,
10051
10353
  SessionExpiredError as SessionExpiredError2,
10052
- ManifestValidationError,
10354
+ ManifestValidationError as ManifestValidationError3,
10053
10355
  composeManifestRequest as composeManifestRequest2,
10054
10356
  resolveManifest as resolveManifest2,
10055
10357
  validateManifest,
@@ -10134,12 +10436,13 @@ export {
10134
10436
  IdentityParseError,
10135
10437
  InvalidRestoredSessionError,
10136
10438
  KVService3 as KVService,
10137
- ManifestValidationError,
10439
+ ManifestValidationError3 as ManifestValidationError,
10138
10440
  MemorySessionStorage,
10139
10441
  NodeUserAuthorization,
10140
10442
  PermissionNotInManifestError2 as PermissionNotInManifestError,
10141
10443
  PrefixedKVService,
10142
10444
  ProtocolMismatchError2 as ProtocolMismatchError,
10445
+ RuntimeDelegationRejectedError,
10143
10446
  SECRET_NAME_RE,
10144
10447
  SQLAction,
10145
10448
  SQLService3 as SQLService,
@@ -10162,6 +10465,7 @@ export {
10162
10465
  VersionCheckError,
10163
10466
  WasmKeyProvider,
10164
10467
  activateCompactRuntimeDelegation,
10468
+ activateValidatedRuntimeDelegation,
10165
10469
  addressStorageKey,
10166
10470
  buildSpaceUri,
10167
10471
  canonicalizeAddress3 as canonicalizeAddress,
@@ -10191,6 +10495,7 @@ export {
10191
10495
  getNodeReplicationLoaders,
10192
10496
  getTinyCloudDebugLogs,
10193
10497
  installTinyCloudDebugGlobals,
10498
+ isAppKeyHostBound,
10194
10499
  isCapabilitySubset3 as isCapabilitySubset,
10195
10500
  isEvmAddress,
10196
10501
  loadManifest,
@@ -10212,6 +10517,7 @@ export {
10212
10517
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
10213
10518
  serializeDelegation,
10214
10519
  tinyCloudDebugLogger2 as tinyCloudDebugLogger,
10215
- validateManifest
10520
+ validateManifest,
10521
+ wireOpenKeyAuthorize
10216
10522
  };
10217
10523
  //# sourceMappingURL=core.js.map