@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/index.js CHANGED
@@ -17099,6 +17099,9 @@ function assertValidReplicationConfig(replication) {
17099
17099
  if (replication?.syncScope !== void 0 && replication.syncScope !== "prefixes") {
17100
17100
  throw new TypeError('replication.syncScope must be "prefixes"');
17101
17101
  }
17102
+ if (replication?.contentFetch !== void 0 && replication.contentFetch !== "eager" && replication.contentFetch !== "lazy") {
17103
+ throw new TypeError('replication.contentFetch must be "eager" or "lazy"');
17104
+ }
17102
17105
  if (replication === void 0 || !replication.enabled) return;
17103
17106
  if (replication.storage === void 0 || replication.storage === null) {
17104
17107
  throw new TypeError("replication.enabled requires replication.storage");
@@ -17151,6 +17154,7 @@ function resolvedOptions(options) {
17151
17154
  maxStalenessMs: options.maxStalenessMs ?? 12e4,
17152
17155
  staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5e3,
17153
17156
  allowSecrets: options.allowSecrets === true,
17157
+ contentFetch: options.contentFetch ?? "eager",
17154
17158
  verify: options.verify === true
17155
17159
  };
17156
17160
  }
@@ -18734,10 +18738,12 @@ import {
18734
18738
  ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
18735
18739
  CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError2,
18736
18740
  PermissionNotInManifestError,
18741
+ ManifestValidationError as ManifestValidationError2,
18737
18742
  SessionExpiredError,
18738
18743
  canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
18739
18744
  recapCaveatsEqual as recapCaveatsEqual2,
18740
18745
  expandPermissionEntries as expandPermissionEntriesCore,
18746
+ expandDelegationPermissionEntries,
18741
18747
  isCapabilitySubset as isCapabilitySubset2,
18742
18748
  isExplicitOnlyAction,
18743
18749
  parseRecapCapabilities,
@@ -18768,6 +18774,7 @@ import {
18768
18774
  withoutShareLink,
18769
18775
  verifyEip191MessageSignature,
18770
18776
  signCompactUcanRootAuthorization,
18777
+ signCompactUcanAuthorization,
18771
18778
  markDelegationTerminal,
18772
18779
  isStorageFullError,
18773
18780
  STORAGE_FULL_MESSAGE,
@@ -18783,9 +18790,9 @@ import {
18783
18790
  tinyCloudDebugLogger
18784
18791
  } from "@tinycloud/sdk-services";
18785
18792
  import { RequestLog, observeReplicaReads, readOnlyRequestLog } from "@tinycloud/sdk-services/telemetry/requestLog";
18793
+ import { parseEncryptionResource as parseEncryptionResource2 } from "@tinycloud/sdk-services/encryption/resource";
18786
18794
 
18787
18795
  // src/authorization/NodeUserAuthorization.ts
18788
- init_authority_sign_in();
18789
18796
  import { createHash } from "crypto";
18790
18797
  import { SiweMessage } from "siwe";
18791
18798
  import {
@@ -18794,10 +18801,10 @@ import {
18794
18801
  activateSessionWithHost,
18795
18802
  checkNodeInfo,
18796
18803
  AutoApproveSpaceCreationHandler,
18804
+ ManifestValidationError,
18797
18805
  DEFAULT_MANIFEST_SPACE,
18798
18806
  ENCRYPTION_PERMISSION_SERVICE,
18799
18807
  composeManifestRequest,
18800
- parseNetworkId,
18801
18808
  principalDidEquals as principalDidEquals2,
18802
18809
  resourceCapabilitiesToAbilitiesMap,
18803
18810
  resourceCapabilitiesToSpaceAbilitiesMap,
@@ -18825,6 +18832,47 @@ import {
18825
18832
  HOOKS,
18826
18833
  ENCRYPTION
18827
18834
  } from "@tinycloud/sdk-core";
18835
+ import { parseEncryptionResource } from "@tinycloud/sdk-services/encryption/resource";
18836
+ import { parseNetworkId } from "@tinycloud/sdk-services";
18837
+
18838
+ // src/authorization/appKeyHostBinding.ts
18839
+ var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
18840
+ var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
18841
+ var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
18842
+ function appKeyHostBinding(domain, appId) {
18843
+ if (!APP_ID.test(appId)) return false;
18844
+ if (domain.endsWith(":")) return false;
18845
+ let url;
18846
+ try {
18847
+ url = new URL(`https://${domain}`);
18848
+ } catch {
18849
+ return false;
18850
+ }
18851
+ if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
18852
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
18853
+ const host = url.hostname;
18854
+ if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
18855
+ const labels = host.split(".");
18856
+ if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
18857
+ if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
18858
+ const reversed = labels.reverse().join(".");
18859
+ return appId === reversed || appId.startsWith(`${reversed}.`);
18860
+ }
18861
+ function isAppKeyHostBound(keysAppId, origin) {
18862
+ if (typeof origin === "string" && !origin.includes("://")) {
18863
+ return appKeyHostBinding(origin, keysAppId);
18864
+ }
18865
+ let url;
18866
+ try {
18867
+ url = typeof origin === "string" ? new URL(origin) : origin;
18868
+ } catch {
18869
+ return false;
18870
+ }
18871
+ return appKeyHostBinding(url.host, keysAppId);
18872
+ }
18873
+
18874
+ // src/authorization/NodeUserAuthorization.ts
18875
+ init_authority_sign_in();
18828
18876
 
18829
18877
  // src/authorization/caveatRefusal.ts
18830
18878
  import {
@@ -18982,6 +19030,7 @@ function cloneAbilitiesMap(actions) {
18982
19030
  var NodeUserAuthorization = class {
18983
19031
  constructor(config) {
18984
19032
  this.activeSessionKeyId = "default";
19033
+ this.preparedSessionCounter = 0;
18985
19034
  /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
18986
19035
  this.sessionGeneration = 0;
18987
19036
  /**
@@ -19338,6 +19387,23 @@ var NodeUserAuthorization = class {
19338
19387
  );
19339
19388
  return this._capabilityRequest;
19340
19389
  }
19390
+ async refreshNamedKeyFeatureForSignIn(address, chainId) {
19391
+ const request = this.getCapabilityRequest();
19392
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19393
+ const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
19394
+ if (hasNamedKeys) {
19395
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
19396
+ await this.refreshNodeInfo();
19397
+ }
19398
+ }
19399
+ /** Manifest key paths that this sign-in cannot grant. */
19400
+ get unavailableKeyPaths() {
19401
+ const request = this.getCapabilityRequest();
19402
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19403
+ return (request?.manifests ?? []).flatMap(
19404
+ (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)
19405
+ );
19406
+ }
19341
19407
  resolveSpaceName(space, address, chainId) {
19342
19408
  if (space.startsWith("tinycloud:")) {
19343
19409
  return space;
@@ -19529,15 +19595,32 @@ var NodeUserAuthorization = class {
19529
19595
  }
19530
19596
  const rawAbilities = {};
19531
19597
  const currentDid = pkhDid(address, chainId);
19598
+ if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
19599
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19600
+ for (const manifest of request.manifests) {
19601
+ if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
19602
+ for (const declaration of manifest.keys ?? []) {
19603
+ const path = "path" in declaration ? declaration.path : declaration.prefix;
19604
+ const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
19605
+ for (const ability of declaration.abilities) {
19606
+ addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
19607
+ }
19608
+ }
19609
+ }
19610
+ }
19532
19611
  const spaceResources = request.resources.filter((entry) => {
19533
19612
  if (entry.service !== ENCRYPTION_PERMISSION_SERVICE) {
19534
19613
  return true;
19535
19614
  }
19615
+ const parsed = entry.path.includes("/") ? parseEncryptionResource(entry.path) : { ...parseNetworkId(entry.path), kind: "network" };
19616
+ const sharePrefix = parsed.kind === "prefix" && parsed.network === "default" && parsed.prefix === "share" && entry.actions.length === 1 && entry.actions[0] === DECRYPT_ACTION && didPrincipalMatches(parsed.ownerDid, currentDid);
19617
+ if (parsed.kind !== "network" && !sharePrefix) {
19618
+ throw new ManifestValidationError("named keys go in manifest.keys");
19619
+ }
19536
19620
  for (const action of entry.actions) {
19537
19621
  addRawAbility(rawAbilities, entry.path, action);
19538
19622
  }
19539
- if (entry.actions.includes(DECRYPT_ACTION)) {
19540
- const parsed = parseNetworkId(entry.path);
19623
+ if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
19541
19624
  if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
19542
19625
  addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
19543
19626
  }
@@ -19574,8 +19657,7 @@ var NodeUserAuthorization = class {
19574
19657
  /**
19575
19658
  * Build SIWE overrides from the top-level nonce and siweConfig.
19576
19659
  * - Top-level `nonce` is seeded first so `siweConfig.nonce` wins if both are set.
19577
- * - statement is prepended to the default statement
19578
- * - resources are appended to the default resources
19660
+ * - resources are inserted after WASM preparation, before the final ReCap
19579
19661
  * - uri triggers a warning (overwriting delegation target)
19580
19662
  * - all other fields override directly
19581
19663
  * - per-call nonce overrides siweConfig.nonce when provided
@@ -19593,9 +19675,6 @@ var NodeUserAuthorization = class {
19593
19675
  if (statement) {
19594
19676
  overrides.statement = this.statement ? `${statement} ${this.statement}` : statement;
19595
19677
  }
19596
- if (resources && resources.length > 0) {
19597
- overrides.resources = resources;
19598
- }
19599
19678
  if (uri) {
19600
19679
  console.warn(
19601
19680
  "[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."
@@ -19607,6 +19686,29 @@ var NodeUserAuthorization = class {
19607
19686
  }
19608
19687
  return overrides;
19609
19688
  }
19689
+ prepareSessionWithResources(params) {
19690
+ const resources = this.siweConfig?.resources;
19691
+ if (resources?.some((resource) => typeof resource !== "string" || !/^urn:tinycloud:recipients:[A-Za-z0-9_-]+$/.test(resource))) {
19692
+ throw new Error("SIWE metadata resources must be base64url recipients URIs");
19693
+ }
19694
+ const prepared = this.wasm.prepareSession(params);
19695
+ if (!resources?.length) return prepared;
19696
+ const recapLines = /^- urn:recap:[^\r\n]+$/gim;
19697
+ const recaps = prepared.siwe.match(recapLines);
19698
+ const recap = recaps?.[0];
19699
+ if (recaps?.length !== 1 || !prepared.siwe.endsWith(recap)) {
19700
+ throw new Error("Prepared SIWE must end with exactly one ReCap");
19701
+ }
19702
+ const insertedLines = resources.map((resource) => `- ${resource}`);
19703
+ const siwe = prepared.siwe.slice(0, -recap.length) + insertedLines.join("\n") + "\n" + recap;
19704
+ const expectedFields = extractImmutableSiweFields(prepared.siwe);
19705
+ expectedFields.nonRecapResources = [expectedFields.nonRecapResources, ...insertedLines].filter(Boolean).join("\n");
19706
+ const actualRecaps = siwe.match(recapLines);
19707
+ if (actualRecaps?.length !== 1 || actualRecaps[0] !== recap || diffImmutableSiweFields(expectedFields, extractImmutableSiweFields(siwe)).length) {
19708
+ throw new Error("SIWE metadata insertion changed the prepared message");
19709
+ }
19710
+ return { ...prepared, siwe };
19711
+ }
19610
19712
  /**
19611
19713
  * Add an extension to the authorization flow.
19612
19714
  */
@@ -19854,7 +19956,7 @@ var NodeUserAuthorization = class {
19854
19956
  spaceAbilities[spaceId] = resourceCapabilitiesToAbilitiesMap(resources);
19855
19957
  }
19856
19958
  const abilities = spaceAbilities[options.spaceId] ?? resourceCapabilitiesToAbilitiesMap([]);
19857
- const prepared = this.wasm.prepareSession({
19959
+ const prepared = this.prepareSessionWithResources({
19858
19960
  abilities,
19859
19961
  spaceAbilities,
19860
19962
  ...options.rawAbilities !== void 0 ? { rawAbilities: options.rawAbilities } : {},
@@ -19933,6 +20035,8 @@ var NodeUserAuthorization = class {
19933
20035
  this._chainId = chainId;
19934
20036
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
19935
20037
  this.assertSignInCurrent(generation);
20038
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
20039
+ this.assertSignInCurrent(generation);
19936
20040
  const keyId = `session-${Date.now()}`;
19937
20041
  this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
19938
20042
  this.activeSessionKeyId = keyId;
@@ -19945,7 +20049,7 @@ var NodeUserAuthorization = class {
19945
20049
  const spaceId = capabilityPlan.spaceId;
19946
20050
  const now = /* @__PURE__ */ new Date();
19947
20051
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
19948
- const prepared = this.wasm.prepareSession({
20052
+ const prepared = this.prepareSessionWithResources({
19949
20053
  abilities: capabilityPlan.abilities,
19950
20054
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
19951
20055
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -20079,9 +20183,9 @@ var NodeUserAuthorization = class {
20079
20183
  refuseCaveated(this.getCapabilityRequest()?.manifests);
20080
20184
  const address = canonicalizeAddress(await this.signer.getAddress());
20081
20185
  const chainId = await this.signer.getChainId();
20082
- const keyId = `session-${Date.now()}`;
20083
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
20084
- this.activeSessionKeyId = keyId;
20186
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
20187
+ const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
20188
+ this.sessionManager.createSessionKey(keyId);
20085
20189
  const jwkString = this.sessionManager.jwk(keyId);
20086
20190
  if (!jwkString) {
20087
20191
  throw new Error("Failed to create session key");
@@ -20091,7 +20195,7 @@ var NodeUserAuthorization = class {
20091
20195
  const spaceId = capabilityPlan.spaceId;
20092
20196
  const now = /* @__PURE__ */ new Date();
20093
20197
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
20094
- const prepared = this.wasm.prepareSession({
20198
+ const prepared = this.prepareSessionWithResources({
20095
20199
  abilities: capabilityPlan.abilities,
20096
20200
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
20097
20201
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -20162,6 +20266,7 @@ var NodeUserAuthorization = class {
20162
20266
  await this.sessionStorage.save(address, persistedData);
20163
20267
  this._session = clientSession;
20164
20268
  this._tinyCloudSession = tinyCloudSession;
20269
+ this.activeSessionKeyId = keyId;
20165
20270
  this._address = address;
20166
20271
  this._chainId = chainId;
20167
20272
  await this.refreshNodeInfo();
@@ -20208,6 +20313,11 @@ var NodeUserAuthorization = class {
20208
20313
  * @param options Optional overrides for the OpenKey request.
20209
20314
  */
20210
20315
  async signInWithOpenKey(authorizeFn, options) {
20316
+ const pending = await this.prepareOpenKeySignIn(options);
20317
+ return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
20318
+ }
20319
+ /** Finish address, host and SIWE preparation before an approval click. */
20320
+ async prepareOpenKeySignIn(options) {
20211
20321
  refuseCaveated(this.getCapabilityRequest()?.manifests);
20212
20322
  if (options?.host !== void 0) {
20213
20323
  const overrideHost = options.host.trim();
@@ -20219,7 +20329,7 @@ var NodeUserAuthorization = class {
20219
20329
  const signingAddress = canonicalizeAddress(await this.signer.getAddress());
20220
20330
  const signingChainId = await this.signer.getChainId();
20221
20331
  await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
20222
- const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
20332
+ const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
20223
20333
  const presentation = this.buildPresentationEnvelope(options?.reason);
20224
20334
  const hostHint = this.primaryTinyCloudHost;
20225
20335
  const originalRequest = {
@@ -20231,12 +20341,32 @@ var NodeUserAuthorization = class {
20231
20341
  keyId: options?.openkeyKeyId,
20232
20342
  presentation
20233
20343
  };
20344
+ return {
20345
+ request: originalRequest,
20346
+ prepared,
20347
+ keyId,
20348
+ jwk,
20349
+ address,
20350
+ chainId,
20351
+ generation: this.sessionGeneration,
20352
+ createdAt: Date.now()
20353
+ };
20354
+ }
20355
+ /** Complete a prepared approval. The caller starts authorization before awaiting this method. */
20356
+ async completeOpenKeySignIn(pending, authorization, options) {
20357
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
20358
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
20359
+ }
20360
+ if (pending.generation !== this.sessionGeneration) {
20361
+ throw new Error("Prepared OpenKey sign-in expired after sign-out");
20362
+ }
20363
+ const { request: originalRequest, prepared, keyId, jwk } = pending;
20234
20364
  let result;
20235
20365
  let signed2;
20236
20366
  let signature2;
20237
20367
  for (let attempt = 0; ; ) {
20238
20368
  try {
20239
- result ?? (result = await authorizeFn(originalRequest));
20369
+ result ?? (result = await authorization);
20240
20370
  signed2 = await this.verifyOpenKeyResult(result, prepared);
20241
20371
  signature2 = result.signature;
20242
20372
  break;
@@ -21147,6 +21277,7 @@ var ROOT_DELEGATION_ACTIONS = [
21147
21277
  DUCKDB2.ALL
21148
21278
  ];
21149
21279
  var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
21280
+ var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
21150
21281
  function unifiedRootAttenuation(capabilities) {
21151
21282
  const attenuation = {};
21152
21283
  for (const capability of capabilities) {
@@ -21644,6 +21775,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21644
21775
  constructor(config = {}) {
21645
21776
  this.signer = null;
21646
21777
  this.auth = null;
21778
+ this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
21647
21779
  this.tc = null;
21648
21780
  this._chainId = 1;
21649
21781
  this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
@@ -21678,6 +21810,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21678
21810
  return void 0;
21679
21811
  }
21680
21812
  };
21813
+ this._keysUnavailable = [];
21681
21814
  this._baseVaults = /* @__PURE__ */ new Map();
21682
21815
  this._baseSecrets = /* @__PURE__ */ new Map();
21683
21816
  this._secrets = /* @__PURE__ */ new Map();
@@ -22505,6 +22638,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22505
22638
  this._hooks = void 0;
22506
22639
  this._vault = void 0;
22507
22640
  this._encryption = void 0;
22641
+ this._namedKeyClient = void 0;
22642
+ this._keysUnavailable = [];
22508
22643
  this._baseVaults.clear();
22509
22644
  this._baseSecrets.clear();
22510
22645
  this._secrets.clear();
@@ -22581,6 +22716,37 @@ var _TinyCloudNode = class _TinyCloudNode {
22581
22716
  }
22582
22717
  await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
22583
22718
  }
22719
+ /** Prepare the OpenKey request before the approval click. */
22720
+ async prepareOpenKeySignIn() {
22721
+ if (!this.signer || !this.tc || !this.auth) {
22722
+ throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
22723
+ }
22724
+ await this.signOutInFlight;
22725
+ await this.accountRegistryTail;
22726
+ await this.wasmBindings.ensureInitialized?.();
22727
+ return this.auth.prepareOpenKeySignIn();
22728
+ }
22729
+ /** Start OpenKey authorization synchronously, then complete normal session setup. */
22730
+ signInWithPreparedOpenKey(pending, authorizeFn) {
22731
+ if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
22732
+ throw new Error("Prepared OpenKey sign-in is unavailable");
22733
+ }
22734
+ if (this.consumedOpenKeyPendings.has(pending)) {
22735
+ throw new Error("Prepared OpenKey sign-in was already used");
22736
+ }
22737
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
22738
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
22739
+ }
22740
+ this.consumedOpenKeyPendings.add(pending);
22741
+ const authorization = authorizeFn(pending.request);
22742
+ void authorization.catch(() => void 0);
22743
+ return this.signInWith(() => {
22744
+ if (this._address !== pending.address || this._chainId !== pending.chainId) {
22745
+ throw new Error("OpenKey account changed after sign-in preparation");
22746
+ }
22747
+ return this.auth.completeOpenKeySignIn(pending, authorization);
22748
+ });
22749
+ }
22584
22750
  async signInWith(performSignIn) {
22585
22751
  refuseCaveated(this.auth?.capabilityRequest?.manifests);
22586
22752
  const epoch = this.sessionEpoch;
@@ -22627,6 +22793,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22627
22793
  this._hooks = void 0;
22628
22794
  this._vault = void 0;
22629
22795
  this._encryption = void 0;
22796
+ this._namedKeyClient = void 0;
22797
+ this._keysUnavailable = [];
22630
22798
  this._baseVaults.clear();
22631
22799
  this._baseSecrets.clear();
22632
22800
  this._secrets.clear();
@@ -22679,6 +22847,26 @@ var _TinyCloudNode = class _TinyCloudNode {
22679
22847
  this.assertSessionEpoch(epoch, "Sign-in");
22680
22848
  await this.ensureRequestedEncryptionNetworks();
22681
22849
  this.assertSessionEpoch(epoch, "Sign-in");
22850
+ this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
22851
+ if (this._keysUnavailable.length > 0) {
22852
+ this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
22853
+ }
22854
+ if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
22855
+ for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
22856
+ for (const declaration of manifest.keys ?? []) {
22857
+ if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
22858
+ try {
22859
+ await this.keys.ensureKey(declaration.path);
22860
+ } catch (error) {
22861
+ this._keysUnavailable.push(declaration.path);
22862
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
22863
+ this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
22864
+ }
22865
+ this.assertSessionEpoch(epoch, "Sign-in");
22866
+ }
22867
+ }
22868
+ }
22869
+ }
22682
22870
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
22683
22871
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(SECRETS_SPACE2));
22684
22872
  }
@@ -23466,7 +23654,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23466
23654
  const networkIds = /* @__PURE__ */ new Set();
23467
23655
  for (const resource of request.resources) {
23468
23656
  if (resource.service === ENCRYPTION_PERMISSION_SERVICE2 && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
23469
- networkIds.add(resource.path);
23657
+ const parsed = resource.path.includes("/") ? parseEncryptionResource2(resource.path) : { ...parseNetworkId2(resource.path), kind: "network" };
23658
+ if (parsed.kind === "network") networkIds.add(resource.path);
23470
23659
  }
23471
23660
  }
23472
23661
  return [...networkIds];
@@ -24016,6 +24205,8 @@ var _TinyCloudNode = class _TinyCloudNode {
24016
24205
  this._hooks = stagedGraph.hooks;
24017
24206
  this._vault = stagedGraph.vault;
24018
24207
  this._encryption = stagedGraph.encryption;
24208
+ this._namedKeyClient = void 0;
24209
+ this._keysUnavailable = [];
24019
24210
  this._capabilityRegistry = stagedGraph.capabilityRegistry;
24020
24211
  this._keyProvider = stagedGraph.keyProvider;
24021
24212
  this._sharingService = stagedGraph.sharingService;
@@ -24197,6 +24388,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24197
24388
  invoke: graph.invoke,
24198
24389
  invokeAny: graph.invokeAny,
24199
24390
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
24391
+ sessionInvocationSigner: this.sessionInvocationSignerFor(input.serviceSession),
24200
24392
  fetch: graph.fetch
24201
24393
  });
24202
24394
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -24412,6 +24604,31 @@ var _TinyCloudNode = class _TinyCloudNode {
24412
24604
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
24413
24605
  return signBytesWithJwk(bytes, session.jwk);
24414
24606
  }
24607
+ /**
24608
+ * Signs proofless invocations with `session`'s own did:key, for the node's
24609
+ * issued-delegation list and revocation by the delegator. The audience is
24610
+ * this Node's DID; each invocation lives 60 seconds.
24611
+ */
24612
+ sessionInvocationSignerFor(session) {
24613
+ return {
24614
+ did: () => session.verificationMethod.split("#", 1)[0],
24615
+ sign: async (attenuation) => {
24616
+ const now = Math.floor(Date.now() / 1e3);
24617
+ const invocation = await signCompactUcanAuthorization({
24618
+ issuerDid: session.verificationMethod.split("#", 1)[0],
24619
+ audienceDid: await this.fetchNodeId(),
24620
+ attenuation,
24621
+ facts: [{}],
24622
+ proofs: [],
24623
+ notBefore: now,
24624
+ expiresAt: now + 60,
24625
+ nonce: `urn:uuid:${crypto.randomUUID()}`,
24626
+ sign: (bytes) => signBytesWithJwk(bytes, session.jwk)
24627
+ });
24628
+ return invocation.authorization;
24629
+ }
24630
+ };
24631
+ }
24415
24632
  /** Author a proofless Policy/v3 owner root with the authenticated holder key. */
24416
24633
  async createUnifiedOwnerRoot(input) {
24417
24634
  const ownerDid = this.credentialHolderDid;
@@ -24946,7 +25163,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24946
25163
  graph.assertActive();
24947
25164
  const signed2 = await this.signRawNetworkAuthorization({
24948
25165
  targetNode: input.targetNode,
24949
- networkId: input.networkId,
25166
+ networkId: input.body.keyPath === void 0 ? input.networkId : `${input.networkId}/${input.body.keyPath}`,
24950
25167
  action: DECRYPT_ACTION2,
24951
25168
  facts: input.facts
24952
25169
  }, { graph, session });
@@ -25119,6 +25336,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25119
25336
  invoke: graph.invoke,
25120
25337
  invokeAny: graph.invokeAny,
25121
25338
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
25339
+ sessionInvocationSigner: this.sessionInvocationSignerFor(serviceSession),
25122
25340
  fetch: graph.fetch
25123
25341
  });
25124
25342
  this._spaceService = new SpaceService({
@@ -25369,7 +25587,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25369
25587
  * Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
25370
25588
  * `POST /revoke`, and return its Authorization header value. The node
25371
25589
  * accepts it when the owner delegated `cid` or owns a space its abilities
25372
- * target, whichever key issued it. Valid for 60 seconds. Requires the
25590
+ * target, whichever key issued it. Valid for 10 minutes. Requires the
25373
25591
  * owner wallet signer.
25374
25592
  */
25375
25593
  async signOwnerRevocation(cid) {
@@ -25385,7 +25603,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25385
25603
  chainId: session.chainId,
25386
25604
  nonce: generateNonce(),
25387
25605
  issuedAt: issuedAt.toISOString(),
25388
- expirationTime: new Date(issuedAt.getTime() + 6e4).toISOString()
25606
+ expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
25389
25607
  }).prepareMessage();
25390
25608
  const signature2 = await this.signer.signMessage(siwe);
25391
25609
  return this.wasmBindings.siweToDelegationHeaders({ siwe, signature: signature2 }).Authorization;
@@ -25612,6 +25830,40 @@ var _TinyCloudNode = class _TinyCloudNode {
25612
25830
  get encryption() {
25613
25831
  return this.getEncryptionService();
25614
25832
  }
25833
+ /** Named keys bound to the current session. */
25834
+ get keys() {
25835
+ return new Proxy({}, {
25836
+ get: (_target, property) => {
25837
+ if (property === "refreshNodeInfo") {
25838
+ return () => {
25839
+ void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
25840
+ };
25841
+ }
25842
+ if (typeof property !== "string" || ![
25843
+ "getPublicKey",
25844
+ "describeKey",
25845
+ "createKey",
25846
+ "blockKey",
25847
+ "unblockKey",
25848
+ "ensureKey",
25849
+ "encrypt",
25850
+ "decrypt",
25851
+ "decryptMany"
25852
+ ].includes(property)) return void 0;
25853
+ return (...args) => {
25854
+ this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.js").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
25855
+ return this._namedKeyClient.then((client) => {
25856
+ const method = client[property];
25857
+ return method.apply(client, args);
25858
+ });
25859
+ };
25860
+ }
25861
+ });
25862
+ }
25863
+ /** Manifest key paths unavailable during the latest `signIn()` attempt. */
25864
+ get keysUnavailable() {
25865
+ return this._keysUnavailable;
25866
+ }
25615
25867
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
25616
25868
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
25617
25869
  return this.fetchEncryptionNetworkAt(
@@ -26501,7 +26753,19 @@ var _TinyCloudNode = class _TinyCloudNode {
26501
26753
  "delegateTo requires a non-empty permissions array"
26502
26754
  );
26503
26755
  }
26504
- const expandedEntries = this.expandPermissionEntries(permissions);
26756
+ const expandedEntries = expandDelegationPermissionEntries(permissions);
26757
+ const namedKeyEntries = expandedEntries.filter((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.path.includes("/"));
26758
+ if (namedKeyEntries.length > 0) {
26759
+ if (expandedEntries.some((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && !entry.path.includes("/"))) {
26760
+ throw new ManifestValidationError2("named-key delegation cannot include bare-network permissions");
26761
+ }
26762
+ if (options?.forceWalletSign) {
26763
+ throw new ManifestValidationError2("named-key delegation requires a signed session grant");
26764
+ }
26765
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
26766
+ const { subset, missing } = this.signedCapabilitySubset(namedKeyEntries, granted);
26767
+ if (!subset) throw new PermissionNotInManifestError(missing, granted);
26768
+ }
26505
26769
  const now = /* @__PURE__ */ new Date();
26506
26770
  const expiryMs = resolveExpiryMs(options?.expiry);
26507
26771
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -28051,7 +28315,7 @@ var FileSessionStorage = class {
28051
28315
  };
28052
28316
 
28053
28317
  // src/authorization/openKeyBridge.ts
28054
- function wireOpenKeyAuthorize(openkey) {
28318
+ function wireOpenKeyAuthorize(openkey, opts) {
28055
28319
  return async (input) => {
28056
28320
  const result = await openkey.authorizeTinyCloud({
28057
28321
  protocolVersion: 1,
@@ -28062,7 +28326,7 @@ function wireOpenKeyAuthorize(openkey) {
28062
28326
  // Forward the presentation envelope verbatim — the OpenKey SDK
28063
28327
  // validates and size-bounds it before use.
28064
28328
  presentation: input.presentation
28065
- });
28329
+ }, opts);
28066
28330
  if (result.protocolVersion !== 1) {
28067
28331
  throw new Error(
28068
28332
  `wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
@@ -28104,7 +28368,7 @@ import {
28104
28368
  CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError3,
28105
28369
  PermissionNotInManifestError as PermissionNotInManifestError2,
28106
28370
  SessionExpiredError as SessionExpiredError2,
28107
- ManifestValidationError,
28371
+ ManifestValidationError as ManifestValidationError3,
28108
28372
  composeManifestRequest as composeManifestRequest2,
28109
28373
  resolveManifest as resolveManifest2,
28110
28374
  validateManifest,
@@ -29249,7 +29513,8 @@ var SqliteReplicaHandle = class {
29249
29513
  options.signal.addEventListener("abort", onAbort, { once: true });
29250
29514
  const engine = new (__privateGet(this, _replica)).Replica({
29251
29515
  store: __privateGet(this, _store),
29252
- transportFor
29516
+ transportFor,
29517
+ ...this.spec.contentFetch === void 0 ? {} : { contentFetch: this.spec.contentFetch }
29253
29518
  });
29254
29519
  const running = engine.sync({ signal: abort.signal });
29255
29520
  const registration = { promise: running, abort: () => abort.abort() };
@@ -30768,7 +31033,7 @@ export {
30768
31033
  InvalidRestoredSessionError,
30769
31034
  KVService4 as KVService,
30770
31035
  MAX_SECRET_SCOPE_LENGTH,
30771
- ManifestValidationError,
31036
+ ManifestValidationError3 as ManifestValidationError,
30772
31037
  MemorySessionStorage,
30773
31038
  NETWORK_NAME_PATTERN,
30774
31039
  NetworkIdError,
@@ -30865,6 +31130,7 @@ export {
30865
31130
  hexDecode,
30866
31131
  hexEncode,
30867
31132
  installTinyCloudDebugGlobals,
31133
+ isAppKeyHostBound,
30868
31134
  isCapabilitySubset4 as isCapabilitySubset,
30869
31135
  isEvmAddress,
30870
31136
  isNetworkId,