@tinycloud/node-sdk 3.0.0-beta.2 → 3.0.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.
package/dist/index.cjs CHANGED
@@ -17060,6 +17060,7 @@ __export(index_exports, {
17060
17060
  FileSessionStorage: () => FileSessionStorage,
17061
17061
  HooksService: () => import_sdk_core18.HooksService,
17062
17062
  IdentityParseError: () => import_sdk_core11.IdentityParseError,
17063
+ InvalidRestoredSessionError: () => InvalidRestoredSessionError,
17063
17064
  KVService: () => import_sdk_core13.KVService,
17064
17065
  ManifestValidationError: () => import_sdk_core12.ManifestValidationError,
17065
17066
  MemorySessionStorage: () => MemorySessionStorage,
@@ -17139,6 +17140,7 @@ __export(index_exports, {
17139
17140
  expandActionShortNames: () => import_sdk_core12.expandActionShortNames,
17140
17141
  expandPermissionEntries: () => import_sdk_core12.expandPermissionEntries,
17141
17142
  expandPermissionEntry: () => import_sdk_core12.expandPermissionEntry,
17143
+ extractSiweExpiration: () => extractSiweExpiration,
17142
17144
  generateRandomReceiverKey: () => import_sdk_core17.generateRandomReceiverKey,
17143
17145
  getTinyCloudDebugLogs: () => import_sdk_core11.getTinyCloudDebugLogs,
17144
17146
  grantAuthRequest: () => grantAuthRequest,
@@ -17157,6 +17159,7 @@ __export(index_exports, {
17157
17159
  parseExpiry: () => import_sdk_core12.parseExpiry,
17158
17160
  parseNetworkId: () => import_sdk_core17.parseNetworkId,
17159
17161
  parsePkhDid: () => import_sdk_core11.parsePkhDid,
17162
+ parseSecretCatalogKey: () => import_sdk_core16.parseSecretCatalogKey,
17160
17163
  parseSpaceUri: () => import_sdk_core21.parseSpaceUri,
17161
17164
  pkhDid: () => import_sdk_core11.pkhDid,
17162
17165
  principalDid: () => import_sdk_core11.principalDid,
@@ -19108,6 +19111,11 @@ var NodeSecretsService = class {
19108
19111
  if (!permission.ok) return permission;
19109
19112
  return options === void 0 ? this.service.list() : this.service.list(options);
19110
19113
  }
19114
+ async listAll() {
19115
+ const permission = await this.ensurePermission("", void 0, "list");
19116
+ if (!permission.ok) return permission;
19117
+ return this.service.listAll();
19118
+ }
19111
19119
  get service() {
19112
19120
  return this.config.getService();
19113
19121
  }
@@ -19169,7 +19177,11 @@ var NodeSecretsService = class {
19169
19177
  (entry) => manifests.some((candidate) => {
19170
19178
  const resolved = (0, import_sdk_core6.resolveManifest)(candidate);
19171
19179
  return resolved.resources.some(
19172
- (resource) => resource.service === entry.service && spaceMatches(resource.space, entry.space, this.config.resolveSpace) && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
19180
+ (resource) => resource.service === entry.service && spaceMatches(
19181
+ resource.space,
19182
+ entry.space,
19183
+ this.config.resolveSpace
19184
+ ) && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
19173
19185
  );
19174
19186
  })
19175
19187
  );
@@ -19302,6 +19314,22 @@ function cloneRecapCaveats(caveats) {
19302
19314
  throw new Error("Verified persisted session ReCap contains invalid caveats.");
19303
19315
  }
19304
19316
  }
19317
+ function jsonFromVerifier(value) {
19318
+ if (value === void 0) return null;
19319
+ if (Array.isArray(value)) return value.map(jsonFromVerifier);
19320
+ if (value instanceof Map) {
19321
+ const entries = [...value.entries()];
19322
+ if (!entries.every(([key2]) => typeof key2 === "string")) return value;
19323
+ return Object.fromEntries(entries.map(([key2, nested]) => [key2, jsonFromVerifier(nested)]));
19324
+ }
19325
+ if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
19326
+ return Object.fromEntries(Object.entries(value).map(([key2, nested]) => [key2, jsonFromVerifier(nested)]));
19327
+ }
19328
+ return value;
19329
+ }
19330
+ function verifierRecapEntries(entries) {
19331
+ return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
19332
+ }
19305
19333
  var ServiceGraphLifetime = class {
19306
19334
  constructor(invokeFn, invokeAnyFn, fetchFn) {
19307
19335
  this.invokeFn = invokeFn;
@@ -19374,18 +19402,23 @@ var UnsupportedSessionRestoreError = class extends Error {
19374
19402
  this.name = "UnsupportedSessionRestoreError";
19375
19403
  }
19376
19404
  };
19405
+ var InvalidRestoredSessionError = class extends Error {
19406
+ constructor(message = "Persisted session authority is invalid or expired.") {
19407
+ super(message);
19408
+ this.code = "AUTH_EXPIRED";
19409
+ this.name = "InvalidRestoredSessionError";
19410
+ }
19411
+ };
19377
19412
  function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
19378
19413
  if (typeof persistedVerificationMethod !== "string") return void 0;
19379
19414
  const principal = canonicalVerificationMethod.split("#", 1)[0];
19380
19415
  return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
19381
19416
  }
19382
19417
  function persistedExpiry(value) {
19383
- if (typeof value !== "string") {
19384
- throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
19385
- }
19418
+ if (typeof value !== "string") throw new InvalidRestoredSessionError("Persisted session must include expiresAt.");
19386
19419
  const expiry = new Date(value);
19387
19420
  if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
19388
- throw new Error("Persisted session expiry is invalid or expired.");
19421
+ throw new InvalidRestoredSessionError();
19389
19422
  }
19390
19423
  return expiry;
19391
19424
  }
@@ -19864,6 +19897,29 @@ var _TinyCloudNode = class _TinyCloudNode {
19864
19897
  const authHosts = this.auth?.hosts ?? [];
19865
19898
  return authHosts.length > 0 ? authHosts : [this.config.host];
19866
19899
  }
19900
+ /** The registry-resolved Node endpoint and the identity returned by that exact Node. */
19901
+ async activeNodeIdentity() {
19902
+ const host = this.hosts[0];
19903
+ if (host === void 0) throw new Error("TinyCloud node host has not been resolved");
19904
+ const parsed = new URL(host);
19905
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost")) || parsed.origin !== host) {
19906
+ throw new Error("TinyCloud node host is not a canonical origin");
19907
+ }
19908
+ const nodeDid = await this.fetchNodeId();
19909
+ if (!nodeDid.startsWith("did:key:z")) throw new Error("TinyCloud node identity is not a did:key");
19910
+ return Object.freeze({ origin: parsed.origin, nodeDid });
19911
+ }
19912
+ /** Publish the active owner Node as a session-signed registry record. */
19913
+ async publishActiveNodeLocation(registryUrl, fetchFn = globalThis.fetch.bind(globalThis)) {
19914
+ const node = await this.activeNodeIdentity();
19915
+ return (0, import_sdk_core7.publishLocationRecord)({
19916
+ registryUrl,
19917
+ subject: this.credentialHolderDid,
19918
+ nodeOrigin: node.origin,
19919
+ signer: { type: "did:key", signBytes: (bytes) => this.signSessionBytes(bytes) },
19920
+ fetch: fetchFn
19921
+ });
19922
+ }
19867
19923
  /**
19868
19924
  * Get the primary identity DID for this user.
19869
19925
  * - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
@@ -19905,16 +19961,26 @@ var _TinyCloudNode = class _TinyCloudNode {
19905
19961
  */
19906
19962
  getVerifiedSessionCapabilities() {
19907
19963
  const session = this.currentTinyCloudSession();
19908
- if (!session || !session.siwe) return [];
19909
- return (0, import_sdk_core7.parseRecapCapabilities)(
19910
- (siwe) => this.parseRecapWithCaveats(siwe),
19911
- session.siwe
19912
- ).map((entry) => entry.service === "tinycloud.encryption" ? {
19913
- service: entry.service,
19914
- path: entry.path,
19915
- actions: [...entry.actions],
19916
- ...entry.caveats === void 0 ? {} : { caveats: entry.caveats }
19917
- } : entry);
19964
+ return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
19965
+ }
19966
+ /** Normalize signed capabilities without collapsing nested encryption into raw authority. */
19967
+ projectSignedRecapCapabilities(siwe) {
19968
+ const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
19969
+ const normalized = (0, import_sdk_core7.parseRecapCapabilities)(() => [entry], siwe)[0];
19970
+ if (normalized.service !== "tinycloud.encryption") return normalized;
19971
+ if (!this.isEncryptionNetworkOperation("encryption", entry.path, entry.space)) {
19972
+ return { ...normalized, space: entry.space };
19973
+ }
19974
+ return {
19975
+ service: normalized.service,
19976
+ path: normalized.path,
19977
+ actions: normalized.actions,
19978
+ ...normalized.caveats === void 0 ? {} : { caveats: normalized.caveats }
19979
+ };
19980
+ });
19981
+ return projected.sort(
19982
+ (left, right) => (left.space ?? "").localeCompare(right.space ?? "") || left.service.localeCompare(right.service) || left.path.localeCompare(right.path)
19983
+ );
19918
19984
  }
19919
19985
  /**
19920
19986
  * Get the Ethereum address for this user.
@@ -20369,9 +20435,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20369
20435
  * We must keep the full owner-scoped URI so a synthetic primary grant can
20370
20436
  * never cover an operation on a different owner's space.
20371
20437
  *
20372
- * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
20373
- * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
20374
- * other entry becomes a `spaceId` op carrying the raw recap `space` URI.
20438
+ * Only top-level encryption network entries (`space: "encryption"` or
20439
+ * absent) become `resource` ops. A network-shaped path nested under a
20440
+ * data space remains a `spaceId` op, not raw network authority.
20375
20441
  * One op per action.
20376
20442
  *
20377
20443
  * Returns `[]` for session-only / restored-without-siwe modes and for any
@@ -20392,7 +20458,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20392
20458
  operations = entries.flatMap((entry) => {
20393
20459
  const service = this.invocationServiceName(entry.service);
20394
20460
  return entry.actions.map((action) => ({
20395
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
20461
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
20396
20462
  service,
20397
20463
  path: entry.path,
20398
20464
  action,
@@ -20554,7 +20620,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20554
20620
  return;
20555
20621
  } catch (error) {
20556
20622
  const message = error instanceof Error ? error.message : String(error);
20557
- if (/Unauthorized Action|\b401\b/.test(message)) {
20623
+ if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
20558
20624
  console.warn(
20559
20625
  "TinyCloud account registry sync stopped: authorization verdict is not retryable",
20560
20626
  error
@@ -20804,9 +20870,14 @@ var _TinyCloudNode = class _TinyCloudNode {
20804
20870
  */
20805
20871
  async restoreSession(sessionData) {
20806
20872
  await this.wasmBindings.ensureInitialized?.();
20807
- const restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
20873
+ let restoredJwk;
20874
+ try {
20875
+ restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
20876
+ } catch {
20877
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
20878
+ }
20808
20879
  if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
20809
- throw new Error("Persisted session chain ID must be a positive safe integer.");
20880
+ throw new InvalidRestoredSessionError("Persisted session chain ID must be a positive safe integer.");
20810
20881
  }
20811
20882
  const stagedManager = this.wasmBindings.createSessionManager();
20812
20883
  const stagedReplace = stagedManager.replaceSessionKey;
@@ -20818,32 +20889,31 @@ var _TinyCloudNode = class _TinyCloudNode {
20818
20889
  let stagedKeyId;
20819
20890
  let stagedJwk;
20820
20891
  let canonicalVerificationMethod;
20892
+ const keyIds = liveKeys.call(this.sessionManager);
20893
+ if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
20894
+ throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
20895
+ }
20896
+ for (const keyId of keyIds) {
20897
+ if (keyId === this.sessionKeyId) continue;
20898
+ const jwk = this.sessionManager.jwk(keyId);
20899
+ if (!jwk) throw new Error("missing live session key");
20900
+ stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
20901
+ }
20821
20902
  try {
20822
- const keyIds = liveKeys.call(this.sessionManager);
20823
- if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
20824
- throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
20825
- }
20826
- for (const keyId of keyIds) {
20827
- if (keyId === this.sessionKeyId) continue;
20828
- const jwk = this.sessionManager.jwk(keyId);
20829
- if (!jwk) throw new Error("missing live session key");
20830
- stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
20831
- }
20832
20903
  stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
20833
- const stagedJwkJson = stagedManager.jwk(stagedKeyId);
20834
- if (!stagedJwkJson) throw new Error("missing restored session key");
20835
- stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
20836
- canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
20837
- } catch (error) {
20838
- if (error instanceof UnsupportedSessionRestoreError) throw error;
20839
- throw new Error("Persisted session has an invalid private Ed25519 session key.");
20904
+ } catch {
20905
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
20840
20906
  }
20907
+ const stagedJwkJson = stagedManager.jwk(stagedKeyId);
20908
+ if (!stagedJwkJson) throw new Error("missing restored session key");
20909
+ stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
20910
+ canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
20841
20911
  const restoredVerificationMethod = canonicalRestoredVerificationMethod(
20842
20912
  canonicalVerificationMethod,
20843
20913
  sessionData.verificationMethod
20844
20914
  );
20845
20915
  if (!restoredVerificationMethod) {
20846
- throw new Error(
20916
+ throw new InvalidRestoredSessionError(
20847
20917
  "Persisted session verification method does not match its private Ed25519 session key."
20848
20918
  );
20849
20919
  }
@@ -20855,26 +20925,37 @@ var _TinyCloudNode = class _TinyCloudNode {
20855
20925
  ];
20856
20926
  const hasPersistedProof = proofValues.every((value) => value !== void 0);
20857
20927
  if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
20858
- throw new Error("Persisted session authority metadata is incomplete.");
20928
+ throw new InvalidRestoredSessionError("Persisted session authority metadata is incomplete.");
20929
+ }
20930
+ let restoredAddress;
20931
+ try {
20932
+ restoredAddress = hasPersistedProof ? (0, import_sdk_core7.canonicalizeAddress)(sessionData.address) : void 0;
20933
+ } catch {
20934
+ throw new InvalidRestoredSessionError("Persisted session has an invalid wallet address.");
20859
20935
  }
20860
- const restoredAddress = hasPersistedProof ? (0, import_sdk_core7.canonicalizeAddress)(sessionData.address) : void 0;
20861
20936
  let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
20862
20937
  let stagedRecap = [];
20863
20938
  if (hasPersistedProof) {
20864
20939
  if (typeof this.wasmBindings.validatePersistedSession !== "function") {
20865
20940
  throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
20866
20941
  }
20867
- const verified = this.wasmBindings.validatePersistedSession({
20868
- delegationHeader: sessionData.delegationHeader,
20869
- delegationCid: sessionData.delegationCid,
20870
- spaceId: sessionData.spaceId,
20871
- jwk: stagedJwk,
20872
- address: restoredAddress,
20873
- chainId: sessionData.chainId,
20874
- siwe: sessionData.siwe,
20875
- signature: sessionData.signature
20876
- });
20877
- const exactRecap = verified.verifiedRecap;
20942
+ const verified = (() => {
20943
+ try {
20944
+ return this.wasmBindings.validatePersistedSession({
20945
+ delegationHeader: sessionData.delegationHeader,
20946
+ delegationCid: sessionData.delegationCid,
20947
+ spaceId: sessionData.spaceId,
20948
+ jwk: stagedJwk,
20949
+ address: restoredAddress,
20950
+ chainId: sessionData.chainId,
20951
+ siwe: sessionData.siwe,
20952
+ signature: sessionData.signature
20953
+ });
20954
+ } catch (error) {
20955
+ throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
20956
+ }
20957
+ })();
20958
+ const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
20878
20959
  if (!Array.isArray(exactRecap) || !exactRecap.every(
20879
20960
  (entry) => entry !== null && typeof entry === "object" && typeof entry.service === "string" && typeof entry.space === "string" && typeof entry.path === "string" && Array.isArray(entry.actions) && entry.actions.every((action) => typeof action === "string") && Array.isArray(entry.caveats) && entry.caveats.every(
20880
20961
  (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
@@ -20894,7 +20975,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20894
20975
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
20895
20976
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
20896
20977
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
20897
- throw new Error("Persisted session expiry does not match its signed SIWE authority.");
20978
+ throw new InvalidRestoredSessionError("Persisted session expiry does not match its signed SIWE authority.");
20898
20979
  }
20899
20980
  stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
20900
20981
  }
@@ -20991,7 +21072,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20991
21072
  operations = recap.flatMap((entry) => {
20992
21073
  const service = this.invocationServiceName(entry.service);
20993
21074
  return entry.actions.map((action) => ({
20994
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
21075
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
20995
21076
  service,
20996
21077
  path: entry.path,
20997
21078
  action,
@@ -21087,7 +21168,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21087
21168
  const delegations = input.recap.map((entry) => ({
21088
21169
  cid: session.delegationCid,
21089
21170
  delegateDID: session.verificationMethod,
21090
- spaceId: entry.space,
21171
+ spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
21091
21172
  path: entry.path,
21092
21173
  actions: [...entry.actions],
21093
21174
  caveats: cloneRecapCaveats(entry.caveats),
@@ -21777,16 +21858,11 @@ var _TinyCloudNode = class _TinyCloudNode {
21777
21858
  );
21778
21859
  graph.assertActive();
21779
21860
  if (!response.ok) {
21780
- let permissionHint;
21781
- if (response.status === 401 || response.status === 403) {
21782
- try {
21783
- const body = await response.json();
21784
- const record = typeof body === "object" && body !== null ? body : void 0;
21785
- const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
21786
- permissionHint = (0, import_sdk_services.parsePermissionHint)(record?.permissionHint) ?? (0, import_sdk_services.parsePermissionHint)(nested?.permissionHint);
21787
- } catch {
21788
- }
21789
- }
21861
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services.parsePermissionHint)({
21862
+ service: "tinycloud.encryption",
21863
+ path: networkId,
21864
+ actions: ["tinycloud.encryption/decrypt"]
21865
+ }) : void 0;
21790
21866
  throw new import_sdk_core7.DecryptTransportResponseError(response.status, permissionHint);
21791
21867
  }
21792
21868
  return await response.json();
@@ -22211,63 +22287,62 @@ var _TinyCloudNode = class _TinyCloudNode {
22211
22287
  }
22212
22288
  };
22213
22289
  }
22214
- /** Register a policy that is already bound to an activated owner delegation. */
22215
- async registerOwnerSharePolicy(params) {
22290
+ /** Register a signed policy and sibling roots with this Node's Policy/v3 runtime. */
22291
+ async registerPolicy(params) {
22216
22292
  this._serviceGraph.assertActive();
22217
- if (params.policy.bytes.byteLength > 2 * 1024 * 1024) throw new Error("Owner share policy is too large");
22218
- const response = await fetch(`${this.config.host}/share/v2/policies`, {
22219
- method: "POST",
22220
- headers: { "content-type": "application/json", accept: "application/json" },
22221
- body: JSON.stringify({
22222
- policy: { bytes: base64UrlEncode(params.policy.bytes), cid: params.policy.cid, proof: params.policy.proof },
22223
- ownerDelegation: { cid: params.ownerDelegation.delegationCid, dagCbor: base64UrlEncode(params.ownerDelegation.signedDagCbor) },
22224
- enforcementDelegation: params.enforcementDelegation,
22225
- contentSourceDigest: params.contentSourceDigest
22226
- })
22293
+ return (0, import_sdk_core7.registerPolicyV3)({
22294
+ ...params,
22295
+ nodeOrigin: this.config.host,
22296
+ fetch: globalThis.fetch.bind(globalThis)
22227
22297
  });
22228
- if (!response.ok) {
22229
- let code = "";
22230
- try {
22231
- const body = await response.clone().json();
22232
- if (typeof body.error?.code === "string") code = ` ${body.error.code}`;
22233
- } catch {
22234
- }
22235
- throw new Error(`Owner share policy registration failed: ${response.status}${code}`);
22236
- }
22237
- const responseBytes = new Uint8Array(await response.arrayBuffer());
22238
- return (0, import_sdk_core7.validateOwnerSharePolicyRegistrationBytes)(responseBytes, params);
22239
22298
  }
22240
- /** Authorize a short-lived, one-use v2 delivery using the authenticated invocation chain. */
22241
- async authorizeShareDelivery(input) {
22299
+ /** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
22300
+ async authorizeShareDeliveryV3(input) {
22242
22301
  const session = this.currentTinyCloudSession();
22243
22302
  const serviceSession = this._serviceContext?.session;
22244
22303
  if (!session || !serviceSession) throw new Error("Share delivery requires an authenticated session");
22304
+ const idempotencyBytes = new TextEncoder().encode(input.idempotencyKey);
22305
+ if (idempotencyBytes.byteLength === 0 || idempotencyBytes.byteLength > 512) {
22306
+ throw new Error("Share delivery idempotency key is invalid");
22307
+ }
22308
+ const jtiDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", idempotencyBytes));
22245
22309
  const body = {
22246
- envelopeCid: input.envelopeCid,
22310
+ envelope: input.envelope,
22311
+ sealedEnvelope: input.sealedEnvelope,
22312
+ envelopeKey: input.envelopeKey,
22247
22313
  shareCid: input.shareCid,
22248
- shareId: input.shareId,
22249
- registrationCid: input.registrationCid,
22250
- policyCid: input.policyCid,
22251
- delegationCid: input.delegationCid,
22252
- enforcementDelegationCid: input.enforcementDelegationCid,
22253
22314
  recipientEmail: input.recipientEmail,
22254
22315
  shareUrl: input.shareUrl,
22255
22316
  documentName: input.documentName,
22256
- jti: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
22257
- idempotencyKey: input.idempotencyKey,
22258
- expiresAt: input.expiresAt
22317
+ jti: base64UrlEncode(jtiDigest.slice(0, 16)),
22318
+ expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
22259
22319
  };
22260
22320
  const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core7.canonicalizeEncryptionJson)(body)))));
22261
22321
  const request = { ...body, requestBodyDigest };
22262
22322
  const authorization = authorizationHeader(this.invokeAnyWithRuntimePermissions(serviceSession, [{ spaceId: session.spaceId, service: "kv", path: input.resourcePath, action: "tinycloud.kv/get" }]));
22263
- const response = await fetch(`${this.config.host}/share/v2/deliveries/authorize`, {
22323
+ const response = await fetch(`${this.config.host}/policy/v3/deliveries/authorize`, {
22264
22324
  method: "POST",
22265
22325
  headers: { accept: "application/json", "content-type": "application/json", authorization },
22266
22326
  body: (0, import_sdk_core7.canonicalizeEncryptionJson)(request)
22267
22327
  });
22268
- if (!response.ok) throw new Error(`Share delivery authorization failed: ${response.status}`);
22269
- const responseBytes = new Uint8Array(await response.arrayBuffer());
22270
- return (0, import_sdk_core7.validateShareDeliveryAuthorizationBytes)(responseBytes, { request, nodeProof: input.nodeProof, credentialsAudience: input.credentialsAudience });
22328
+ if (!response.ok) throw new Error(`V3 share delivery authorization failed: ${response.status}`);
22329
+ const verified = (0, import_sdk_core7.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
22330
+ request,
22331
+ senderKeyDid: this.credentialHolderDid,
22332
+ deliveryAudience: input.deliveryAudience
22333
+ });
22334
+ const requestDigest = new Uint8Array(await crypto.subtle.digest(
22335
+ "SHA-256",
22336
+ new TextEncoder().encode(`${import_sdk_core7.CREDENTIAL_INVITATION_REQUEST_DOMAIN}${(0, import_sdk_core7.canonicalizeEncryptionJson)(verified.request)}`)
22337
+ ));
22338
+ return {
22339
+ ...verified,
22340
+ proof: {
22341
+ alg: "EdDSA",
22342
+ kid: this.credentialHolderDid,
22343
+ signature: base64UrlEncode(await this.signSessionBytes(requestDigest))
22344
+ }
22345
+ };
22271
22346
  }
22272
22347
  async createRootDelegationForSharing(params, assertActive) {
22273
22348
  try {
@@ -22722,7 +22797,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22722
22797
  continue;
22723
22798
  }
22724
22799
  let space;
22725
- if (service !== "tinycloud.encryption") {
22800
+ if (service !== "tinycloud.encryption" || operation.resource === void 0) {
22726
22801
  if (typeof operation.spaceId !== "string") continue;
22727
22802
  try {
22728
22803
  space = this.resolvePermissionSpace(operation.spaceId, session);
@@ -23267,11 +23342,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23267
23342
  );
23268
23343
  return { delegation: delegation2, prompted: true };
23269
23344
  }
23270
- const granted = (0, import_sdk_core7.parseRecapCapabilities)(
23271
- (siwe) => this.parseRecapWithCaveats(siwe),
23272
- session.siwe
23273
- );
23274
- const { subset, missing } = (0, import_sdk_core7.isCapabilitySubset)(expandedEntries, granted);
23345
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
23346
+ const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
23275
23347
  if (!subset) {
23276
23348
  const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
23277
23349
  const runtimeGrant = this.findGrantForOperations(
@@ -23591,7 +23663,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23591
23663
  return abilities;
23592
23664
  }
23593
23665
  isEncryptionPermissionEntry(entry) {
23594
- return entry.service === import_sdk_core7.ENCRYPTION_PERMISSION_SERVICE && entry.path.startsWith("urn:tinycloud:encryption:");
23666
+ return entry.service === import_sdk_core7.ENCRYPTION_PERMISSION_SERVICE && this.isEncryptionNetworkOperation("encryption", entry.path, entry.space);
23595
23667
  }
23596
23668
  permissionsToRawAbilities(entries) {
23597
23669
  const rawAbilities = {};
@@ -23615,7 +23687,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23615
23687
  return entries.flatMap((entry) => {
23616
23688
  const service = this.shortServiceName(entry.service);
23617
23689
  return entry.actions.map((action) => ({
23618
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
23690
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
23619
23691
  service,
23620
23692
  path: entry.path,
23621
23693
  action,
@@ -23623,13 +23695,21 @@ var _TinyCloudNode = class _TinyCloudNode {
23623
23695
  }));
23624
23696
  });
23625
23697
  }
23698
+ /** Raw network authority cannot be covered by a nested encryption space. */
23699
+ signedCapabilitySubset(requested, granted) {
23700
+ const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry)).map((entry) => ({ ...entry, space: "encryption" }));
23701
+ const spaceGranted = granted.filter((entry) => !this.isEncryptionPermissionEntry(entry));
23702
+ const missing = requested.filter((entry) => {
23703
+ const raw = this.isEncryptionPermissionEntry(entry);
23704
+ const comparable = raw ? { ...entry, space: "encryption" } : entry;
23705
+ return !(0, import_sdk_core7.isCapabilitySubset)([comparable], raw ? rawGranted : spaceGranted).subset;
23706
+ });
23707
+ return { subset: missing.length === 0, missing };
23708
+ }
23626
23709
  sessionCoversPermissionEntries(session, entries) {
23627
23710
  try {
23628
- const granted = (0, import_sdk_core7.parseRecapCapabilities)(
23629
- (siwe) => this.parseRecapWithCaveats(siwe),
23630
- session.siwe
23631
- );
23632
- return (0, import_sdk_core7.isCapabilitySubset)(entries, granted).subset;
23711
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
23712
+ return this.signedCapabilitySubset(entries, granted).subset;
23633
23713
  } catch {
23634
23714
  return false;
23635
23715
  }
@@ -23639,7 +23719,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23639
23719
  const spaceId = this.resolvePermissionSpace(entry.space, session);
23640
23720
  const service = this.shortServiceName(entry.service);
23641
23721
  return entry.actions.map((action) => ({
23642
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
23722
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
23643
23723
  service,
23644
23724
  path: entry.path,
23645
23725
  action,
@@ -23728,7 +23808,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23728
23808
  return resources.flatMap((resource) => {
23729
23809
  const service = this.invocationServiceName(resource.service);
23730
23810
  return resource.actions.map((action) => ({
23731
- ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
23811
+ ...this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
23732
23812
  service,
23733
23813
  path: resource.path,
23734
23814
  action,
@@ -23789,7 +23869,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23789
23869
  };
23790
23870
  for (const resource of resources) {
23791
23871
  const service = this.invocationServiceName(resource.service);
23792
- if (this.isEncryptionNetworkOperation(service, resource.path)) {
23872
+ if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
23793
23873
  rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
23794
23874
  addActions(rawAbilities[resource.path], resource.actions);
23795
23875
  continue;
@@ -23858,21 +23938,15 @@ var _TinyCloudNode = class _TinyCloudNode {
23858
23938
  }
23859
23939
  return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
23860
23940
  }
23861
- // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. The embedded
23862
- // EIP-155 address is case-insensitive, but the CLI canonicalizes it to
23863
- // lowercase when building a space URI while stored runtime delegations keep
23864
- // the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
23865
- // an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
23866
- // segment and leave everything else (crucially the case-sensitive space NAME)
23867
- // byte-exact. Mirrors the CLI's `normalizeSpaceForCompare` (OPENKEY_SCOPE_MISMATCH fix).
23941
+ // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. Compare
23942
+ // only the embedded EIP-155 address without case sensitivity; chain, name,
23943
+ // and all non-PKH space identifiers remain byte-exact.
23868
23944
  spaceIdsEqual(a, b) {
23869
23945
  return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
23870
23946
  }
23871
23947
  normalizeSpaceAddress(space) {
23872
- return space.replace(
23873
- /(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
23874
- (_match, prefix, addr) => prefix + addr.toLowerCase()
23875
- );
23948
+ const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
23949
+ return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
23876
23950
  }
23877
23951
  actionContains(grantedAction, requestedAction) {
23878
23952
  if (grantedAction === requestedAction) {
@@ -23889,10 +23963,11 @@ var _TinyCloudNode = class _TinyCloudNode {
23889
23963
  }
23890
23964
  /** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
23891
23965
  parseRecapWithCaveats(siwe) {
23892
- return this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe) ?? this.wasmBindings.parseRecapFromSiwe(siwe);
23966
+ const verified = this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe);
23967
+ return verified == null ? this.wasmBindings.parseRecapFromSiwe(siwe) : verifierRecapEntries(verified);
23893
23968
  }
23894
- isEncryptionNetworkOperation(service, path) {
23895
- return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
23969
+ isEncryptionNetworkOperation(service, path, space) {
23970
+ return service === "encryption" && (space === void 0 || space === "encryption") && path.startsWith("urn:tinycloud:encryption:");
23896
23971
  }
23897
23972
  operationFromInvokeAnyEntry(entry) {
23898
23973
  const service = this.invocationServiceName(entry.service);
@@ -23905,7 +23980,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23905
23980
  ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
23906
23981
  };
23907
23982
  }
23908
- if (this.isEncryptionNetworkOperation(service, entry.path)) {
23983
+ if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
23909
23984
  return {
23910
23985
  resource: entry.path,
23911
23986
  service,
@@ -24190,8 +24265,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24190
24265
  const jwk = mySession.jwk;
24191
24266
  const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
24192
24267
  const now = /* @__PURE__ */ new Date();
24193
- const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
24194
- const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
24268
+ const expirationTime = delegation.expiry;
24195
24269
  const prepared = this.wasmBindings.prepareSession({
24196
24270
  abilities,
24197
24271
  address: this.wasmBindings.ensureEip55(mySession.address),
@@ -24295,8 +24369,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24295
24369
  );
24296
24370
  }
24297
24371
  const now = /* @__PURE__ */ new Date();
24298
- const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
24299
- const requestedExpiry = new Date(now.getTime() + expiryMs);
24372
+ const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
24300
24373
  const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
24301
24374
  const abilities = {};
24302
24375
  const rawAbilities = {};
@@ -24969,6 +25042,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
24969
25042
  FileSessionStorage,
24970
25043
  HooksService,
24971
25044
  IdentityParseError,
25045
+ InvalidRestoredSessionError,
24972
25046
  KVService,
24973
25047
  ManifestValidationError,
24974
25048
  MemorySessionStorage,
@@ -25048,6 +25122,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
25048
25122
  expandActionShortNames,
25049
25123
  expandPermissionEntries,
25050
25124
  expandPermissionEntry,
25125
+ extractSiweExpiration,
25051
25126
  generateRandomReceiverKey,
25052
25127
  getTinyCloudDebugLogs,
25053
25128
  grantAuthRequest,
@@ -25066,6 +25141,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
25066
25141
  parseExpiry,
25067
25142
  parseNetworkId,
25068
25143
  parsePkhDid,
25144
+ parseSecretCatalogKey,
25069
25145
  parseSpaceUri,
25070
25146
  pkhDid,
25071
25147
  principalDid,