@tinycloud/share-sdk 1.0.0-beta.3 → 1.0.0-beta.4

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
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ CredentialInvitationError: () => CredentialInvitationError,
23
24
  DEFAULT_MAX_CONTENT_BLOB_BYTES: () => DEFAULT_MAX_CONTENT_BLOB_BYTES,
24
25
  DEFAULT_MAX_SEALED_BLOB_BYTES: () => DEFAULT_MAX_SEALED_BLOB_BYTES,
25
26
  DEFAULT_SHARE_LIFETIME_MS: () => DEFAULT_SHARE_LIFETIME_MS,
@@ -52,6 +53,7 @@ __export(index_exports, {
52
53
  createNativeShare: () => createNativeShare,
53
54
  createPolicyEnforcementDelegation: () => createPolicyEnforcementDelegation,
54
55
  createShareV2HolderBindingArtifact: () => createShareV2HolderBindingArtifact,
56
+ deliverCredentialInvitation: () => deliverCredentialInvitation,
55
57
  fromBase64Url: () => fromBase64Url,
56
58
  historyRecordForPublishedShare: () => historyRecordForPublishedShare,
57
59
  inspectShare: () => inspectShare,
@@ -229,10 +231,10 @@ var util;
229
231
  return obj[e];
230
232
  });
231
233
  };
232
- util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object3) => {
234
+ util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object4) => {
233
235
  const keys = [];
234
- for (const key in object3) {
235
- if (Object.prototype.hasOwnProperty.call(object3, key)) {
236
+ for (const key in object4) {
237
+ if (Object.prototype.hasOwnProperty.call(object4, key)) {
236
238
  keys.push(key);
237
239
  }
238
240
  }
@@ -5632,7 +5634,74 @@ function checkBearerDelegation(envelope, options = {}) {
5632
5634
  return { ok: true, delegateeDid: audience };
5633
5635
  }
5634
5636
  var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
5637
+ var SEALED_BLOB_VERSION = 1;
5635
5638
  var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
5639
+ var KEY_LENGTH = 32;
5640
+ var NONCE_LENGTH = 12;
5641
+ var TAG_LENGTH = 16;
5642
+ var HEADER_LENGTH = 1;
5643
+ function assertKey(key32) {
5644
+ if (key32.length !== KEY_LENGTH) {
5645
+ throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
5646
+ }
5647
+ }
5648
+ async function importAesKey(key32, usage) {
5649
+ assertKey(key32);
5650
+ return globalThis.crypto.subtle.importKey(
5651
+ "raw",
5652
+ key32,
5653
+ "AES-GCM",
5654
+ false,
5655
+ [usage]
5656
+ );
5657
+ }
5658
+ function generateKey() {
5659
+ return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
5660
+ }
5661
+ async function seal(plaintextBytes, key32) {
5662
+ const { nonce, ciphertext } = await encryptEnvelope(plaintextBytes, key32);
5663
+ const blob = new Uint8Array(HEADER_LENGTH + nonce.length + ciphertext.length);
5664
+ blob[0] = SEALED_BLOB_VERSION;
5665
+ blob.set(nonce, HEADER_LENGTH);
5666
+ blob.set(ciphertext, HEADER_LENGTH + nonce.length);
5667
+ return { blob, cid: await computeCid(blob) };
5668
+ }
5669
+ async function open(blob, key32) {
5670
+ if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
5671
+ throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
5672
+ }
5673
+ if (blob[0] !== SEALED_BLOB_VERSION) {
5674
+ throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
5675
+ }
5676
+ const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
5677
+ const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
5678
+ return decryptEnvelope(nonce, ciphertext, key32);
5679
+ }
5680
+ async function encryptEnvelope(plaintextBytes, key32) {
5681
+ const key = await importAesKey(key32, "encrypt");
5682
+ const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
5683
+ const ciphertext = new Uint8Array(
5684
+ await globalThis.crypto.subtle.encrypt(
5685
+ { name: "AES-GCM", iv: nonce, additionalData: AAD },
5686
+ key,
5687
+ plaintextBytes
5688
+ )
5689
+ );
5690
+ return { nonce, ciphertext };
5691
+ }
5692
+ async function decryptEnvelope(nonce, ciphertext, key32) {
5693
+ if (nonce.length !== NONCE_LENGTH) {
5694
+ throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
5695
+ }
5696
+ const key = await importAesKey(key32, "decrypt");
5697
+ return new Uint8Array(
5698
+ await globalThis.crypto.subtle.decrypt(
5699
+ { name: "AES-GCM", iv: nonce, additionalData: AAD },
5700
+ key,
5701
+ ciphertext
5702
+ )
5703
+ );
5704
+ }
5636
5705
  function isPlainRecord(value) {
5637
5706
  const proto = Object.getPrototypeOf(value);
5638
5707
  return proto === Object.prototype || proto === null;
@@ -5865,8 +5934,6 @@ function base32Lower(bytes3) {
5865
5934
  return output;
5866
5935
  }
5867
5936
  var KEY_LENGTH2 = 32;
5868
- var INLINE_PREFIX = "#tc2=";
5869
- var PUBLIC_INLINE_PARAMETER = "tc2";
5870
5937
  var MAX_INLINE_BYTES = 256 * 1024;
5871
5938
  function assertCanonicalCid(cidString) {
5872
5939
  const cid2 = CID.parse(cidString);
@@ -5875,62 +5942,61 @@ function assertCanonicalCid(cidString) {
5875
5942
  throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
5876
5943
  }
5877
5944
  }
5878
- async function encodePublicInlineShareUrl(parts) {
5945
+ async function encodeSealedInlineShareUrl(parts) {
5879
5946
  if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
5880
- if (parts.plaintext.byteLength === 0 || parts.plaintext.byteLength > MAX_INLINE_BYTES) throw new RangeError("public inline envelope is outside the allowed size");
5881
- const ciphertextCid = await computeCid(parts.plaintext);
5882
- const payloadBytes = new TextEncoder().encode(canonicalize({
5947
+ if (parts.ciphertext.byteLength === 0 || parts.ciphertext.byteLength > MAX_INLINE_BYTES) throw new RangeError("inline ciphertext is outside the allowed size");
5948
+ if (parts.key32.byteLength !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
5949
+ const ciphertextCid = await computeCid(parts.ciphertext);
5950
+ const payload = canonicalize({
5883
5951
  v: 2,
5884
- c: toBase64Url(parts.plaintext),
5885
- cid: ciphertextCid
5886
- }));
5952
+ c: toBase64Url(parts.ciphertext),
5953
+ cid: ciphertextCid,
5954
+ k: toBase64Url(parts.key32)
5955
+ });
5956
+ const payloadBytes = new TextEncoder().encode(payload);
5887
5957
  if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
5888
- return `${parts.origin}/viewer?${PUBLIC_INLINE_PARAMETER}=${toBase64Url(payloadBytes)}`;
5958
+ return `${parts.origin}/s/inline#v=2&p=${toBase64Url(payloadBytes)}`;
5889
5959
  }
5890
- function parseInlineShareUrl(url, options = {}) {
5960
+ async function parseSealedInlineShareUrl(url, options = {}) {
5891
5961
  const parsed = new URL(url);
5892
- if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("inline share URL must be canonical HTTPS without userinfo");
5893
- if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
5894
- if (parsed.pathname !== "/viewer") throw new TypeError("not a TinyCloud policy share URL");
5895
- const secretPayload = parsed.search === "" && parsed.hash.startsWith(INLINE_PREFIX) ? parsed.hash.slice(INLINE_PREFIX.length) : void 0;
5896
- const publicPayload = parsed.hash === "" && parsed.searchParams.size === 1 ? parsed.searchParams.get(PUBLIC_INLINE_PARAMETER) ?? void 0 : void 0;
5897
- if (secretPayload === void 0 && publicPayload === void 0) throw new TypeError("not a canonical TinyCloud policy share URL");
5898
- if (publicPayload !== void 0 && parsed.search !== `?${PUBLIC_INLINE_PARAMETER}=${publicPayload}`) throw new TypeError("public inline URL is not canonical");
5962
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("sealed inline share URL must be canonical HTTPS without userinfo");
5963
+ if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("sealed inline share URL origin is not trusted");
5964
+ if (parsed.pathname !== "/s/inline" || parsed.search !== "") throw new TypeError("not a Node sealed-inline share URL");
5965
+ if (url !== `${parsed.origin}/s/inline${parsed.hash}`) throw new TypeError("sealed inline share URL is not lexically canonical");
5966
+ const prefix = "#v=2&p=";
5967
+ if (!parsed.hash.startsWith(prefix)) throw new TypeError("sealed inline URL is missing its canonical fragment");
5968
+ const encoded = parsed.hash.slice(prefix.length);
5969
+ if (encoded.length === 0 || parsed.hash !== `${prefix}${encoded}`) throw new TypeError("sealed inline URL fragment is not canonical");
5899
5970
  let payloadBytes;
5900
5971
  try {
5901
- payloadBytes = fromBase64Url(secretPayload ?? publicPayload);
5972
+ payloadBytes = fromBase64Url(encoded);
5902
5973
  } catch {
5903
- throw new TypeError("inline payload is not canonical base64url");
5974
+ throw new TypeError("sealed inline payload is not canonical base64url");
5904
5975
  }
5905
- if (payloadBytes.byteLength === 0 || payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new TypeError("inline payload is too large");
5976
+ if (payloadBytes.byteLength === 0 || payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new TypeError("sealed inline payload is too large");
5977
+ const payloadText = new TextDecoder("utf-8", { fatal: true }).decode(payloadBytes);
5906
5978
  let value;
5907
5979
  try {
5908
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payloadBytes));
5980
+ value = JSON.parse(payloadText);
5909
5981
  } catch {
5910
- throw new TypeError("inline payload is not valid JSON");
5982
+ throw new TypeError("sealed inline payload is not valid JSON");
5911
5983
  }
5912
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload must be an object");
5913
- if (canonicalize(value) !== new TextDecoder("utf-8").decode(payloadBytes)) throw new TypeError("inline payload is not canonical JSON");
5984
+ if (typeof value !== "object" || value === null || Array.isArray(value) || canonicalize(value) !== payloadText) throw new TypeError("sealed inline payload is not canonical JSON");
5914
5985
  const record = value;
5915
- const keys = Object.keys(record);
5916
- if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") || record.v !== 2 || typeof record.c !== "string" || typeof record.cid !== "string" || publicPayload !== void 0 && (keys.length !== 3 || record.k !== void 0)) throw new TypeError("inline payload has invalid fields");
5986
+ if (Object.keys(record).length !== 4 || record.v !== 2 || typeof record.c !== "string" || typeof record.cid !== "string" || typeof record.k !== "string") throw new TypeError("sealed inline payload has invalid fields");
5917
5987
  let ciphertext;
5988
+ let key32;
5918
5989
  try {
5919
5990
  ciphertext = fromBase64Url(record.c);
5991
+ key32 = fromBase64Url(record.k);
5920
5992
  } catch {
5921
- throw new TypeError("inline ciphertext is not canonical base64url");
5993
+ throw new TypeError("sealed inline payload has invalid base64url material");
5922
5994
  }
5923
- if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
5995
+ if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("sealed inline ciphertext is outside the allowed size");
5996
+ if (key32.byteLength !== KEY_LENGTH2) throw new TypeError("sealed inline key must be 32 bytes");
5924
5997
  assertCanonicalCid(record.cid);
5925
- if (record.k !== void 0 && typeof record.k !== "string") throw new TypeError("inline key must be base64url");
5926
- let key32;
5927
- try {
5928
- key32 = record.k === void 0 ? void 0 : fromBase64Url(record.k);
5929
- } catch {
5930
- throw new TypeError("inline key is not canonical base64url");
5931
- }
5932
- if (key32 !== void 0 && key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
5933
- return { kind: "inline", ciphertextCid: record.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
5998
+ if (await computeCid(ciphertext) !== record.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
5999
+ return { kind: "inline", ciphertextCid: record.cid, ciphertext, key32 };
5934
6000
  }
5935
6001
 
5936
6002
  // src/receive.ts
@@ -5978,19 +6044,17 @@ async function resolvePolicyShare(link, options) {
5978
6044
  let parsed;
5979
6045
  try {
5980
6046
  url = new URL(link);
5981
- if (url.pathname !== "/viewer" || url.hash !== "" || url.searchParams.size !== 1 || !url.searchParams.has("tc2")) throw new Error("not public Policy/v3");
5982
- parsed = parseInlineShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
5983
- if (parsed.key32 !== void 0) throw new Error("secret inline transport is retired");
6047
+ parsed = await parseSealedInlineShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
5984
6048
  } catch {
5985
6049
  throw new ShareReceiveError("invalid-link", "share link format is invalid");
5986
6050
  }
5987
6051
  const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
5988
- if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "public policy envelope exceeds the configured byte limit");
5989
- if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "public envelope bytes do not match the link CID");
6052
+ if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed policy envelope exceeds the configured byte limit");
6053
+ if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "sealed envelope bytes do not match the link CID");
5990
6054
  options.signal?.throwIfAborted();
5991
6055
  let envelope;
5992
6056
  try {
5993
- const encoded = new TextDecoder("utf-8", { fatal: true }).decode(parsed.ciphertext);
6057
+ const encoded = new TextDecoder("utf-8", { fatal: true }).decode(await open(parsed.ciphertext, parsed.key32));
5994
6058
  const value = JSON.parse(encoded);
5995
6059
  if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
5996
6060
  envelope = shareEnvelopeV3Schema.parse(value);
@@ -6741,14 +6805,25 @@ async function publishAddressedShare(options) {
6741
6805
  const envelope = { ...unsigned, signature: { signerDid: options.authority.ownerDid, algorithm: "Ed25519", value: toBase64Url(envelopeSignature) } };
6742
6806
  shareEnvelopeV3Schema.parse(envelope);
6743
6807
  const envelopeBytes = textEncoder.encode(canonicalize(envelope));
6744
- const envelopeCid = await computeCid(envelopeBytes);
6745
- const url = await encodePublicInlineShareUrl({ origin: options.shareOrigin, plaintext: envelopeBytes });
6746
- const deliveryMaterial = { envelope, shareCid: envelopeCid };
6808
+ const envelopeKeyBytes = generateKey();
6809
+ const sealed = await seal(envelopeBytes, envelopeKeyBytes);
6810
+ const envelopeKey = toBase64Url(envelopeKeyBytes);
6811
+ const url = await encodeSealedInlineShareUrl({
6812
+ origin: options.shareOrigin,
6813
+ ciphertext: sealed.blob,
6814
+ key32: envelopeKeyBytes
6815
+ });
6816
+ const deliveryMaterial = {
6817
+ envelope,
6818
+ shareCid: sealed.cid,
6819
+ sealedEnvelope: toBase64Url(sealed.blob),
6820
+ envelopeKey
6821
+ };
6747
6822
  options.onDeliveryMaterial?.(deliveryMaterial);
6748
6823
  return publicationResult({
6749
6824
  options,
6750
6825
  url,
6751
- envelopeCid,
6826
+ envelopeCid: sealed.cid,
6752
6827
  matcher,
6753
6828
  policyCid: created.policyCid,
6754
6829
  policyRootCid: policyRoot.cid,
@@ -7805,6 +7880,119 @@ async function defaultIdempotencyKey(shareId, recipient) {
7805
7880
  return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
7806
7881
  }
7807
7882
 
7883
+ // src/credential-invitation.ts
7884
+ var CredentialInvitationError = class extends Error {
7885
+ constructor(code3, message) {
7886
+ super(message);
7887
+ this.code = code3;
7888
+ this.name = "CredentialInvitationError";
7889
+ }
7890
+ };
7891
+ function canonicalOrigin(value) {
7892
+ let url;
7893
+ try {
7894
+ url = new URL(value);
7895
+ } catch {
7896
+ throw new CredentialInvitationError(
7897
+ "invalid-origin",
7898
+ "credential invitation origin is invalid"
7899
+ );
7900
+ }
7901
+ if (url.origin !== value || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || url.protocol !== "https:")
7902
+ throw new CredentialInvitationError(
7903
+ "invalid-origin",
7904
+ "credential invitation origin is invalid"
7905
+ );
7906
+ return url.origin;
7907
+ }
7908
+ function object2(value) {
7909
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7910
+ }
7911
+ async function validateReceipt(receipt, shareUrl) {
7912
+ if (!object2(receipt) || !object2(receipt.request) || !object2(receipt.admission) || !object2(receipt.proof)) {
7913
+ throw new CredentialInvitationError(
7914
+ "invalid-receipt",
7915
+ "credential invitation receipt is invalid"
7916
+ );
7917
+ }
7918
+ if (receipt.request.returnLink !== shareUrl) {
7919
+ throw new CredentialInvitationError(
7920
+ "invalid-receipt",
7921
+ "credential invitation is not bound to the share link"
7922
+ );
7923
+ }
7924
+ let link;
7925
+ try {
7926
+ link = new URL(shareUrl);
7927
+ } catch {
7928
+ throw new CredentialInvitationError(
7929
+ "invalid-receipt",
7930
+ "credential invitation share link is invalid"
7931
+ );
7932
+ }
7933
+ if (link.search !== "" || link.pathname !== "/s/inline") {
7934
+ throw new CredentialInvitationError(
7935
+ "invalid-receipt",
7936
+ "credential invitation share link must be sealed inline"
7937
+ );
7938
+ }
7939
+ try {
7940
+ await parseSealedInlineShareUrl(shareUrl);
7941
+ } catch {
7942
+ throw new CredentialInvitationError(
7943
+ "invalid-receipt",
7944
+ "credential invitation share link is invalid"
7945
+ );
7946
+ }
7947
+ }
7948
+ async function deliverCredentialInvitation(input) {
7949
+ const credentialsOrigin = canonicalOrigin(input.credentialsOrigin);
7950
+ await validateReceipt(input.receipt, input.shareUrl);
7951
+ const fetchFn = input.fetchFn ?? globalThis.fetch;
7952
+ let response;
7953
+ try {
7954
+ response = await fetchFn(`${credentialsOrigin}/v1/credential-invitations`, {
7955
+ method: "POST",
7956
+ credentials: "omit",
7957
+ redirect: "error",
7958
+ referrerPolicy: "no-referrer",
7959
+ headers: {
7960
+ accept: "application/json",
7961
+ "content-type": "application/json"
7962
+ },
7963
+ body: JSON.stringify(input.receipt),
7964
+ ...input.signal === void 0 ? {} : { signal: input.signal }
7965
+ });
7966
+ } catch {
7967
+ throw new CredentialInvitationError(
7968
+ "transport",
7969
+ "credential invitation delivery is unavailable"
7970
+ );
7971
+ }
7972
+ if (response.status !== 202) {
7973
+ throw new CredentialInvitationError(
7974
+ "rejected",
7975
+ "credential invitation delivery was not accepted"
7976
+ );
7977
+ }
7978
+ let body;
7979
+ try {
7980
+ body = await response.json();
7981
+ } catch {
7982
+ throw new CredentialInvitationError(
7983
+ "invalid-response",
7984
+ "credential invitation response is invalid"
7985
+ );
7986
+ }
7987
+ if (!object2(body) || Object.keys(body).length !== 1 || body.status !== "accepted") {
7988
+ throw new CredentialInvitationError(
7989
+ "invalid-response",
7990
+ "credential invitation response is invalid"
7991
+ );
7992
+ }
7993
+ return { status: "accepted" };
7994
+ }
7995
+
7808
7996
  // src/lifecycle.ts
7809
7997
  function targetKind2(record) {
7810
7998
  if (record.targetKind !== void 0) return record.targetKind;
@@ -7911,7 +8099,7 @@ var DOMAIN = SHARE_V2_PROTOCOL.challengeDomain;
7911
8099
  var PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
7912
8100
  var SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
7913
8101
  var INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
7914
- function object2(value, label) {
8102
+ function object3(value, label) {
7915
8103
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
7916
8104
  return value;
7917
8105
  }
@@ -7970,11 +8158,11 @@ function nativeAction(action) {
7970
8158
  return action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : "tinycloud.kv/get";
7971
8159
  }
7972
8160
  function policyAttenuationForV3(envelope) {
7973
- const policy = object2(envelope.policy, "v3 policy");
8161
+ const policy = object3(envelope.policy, "v3 policy");
7974
8162
  if (!Array.isArray(policy.capabilityCeiling)) throw new Error("v3 policy capability ceiling is invalid");
7975
8163
  const attenuation = {};
7976
8164
  for (const raw of policy.capabilityCeiling) {
7977
- const capability = object2(raw, "v3 policy capability");
8165
+ const capability = object3(raw, "v3 policy capability");
7978
8166
  if (typeof capability.resource !== "string" || attenuation[capability.resource] !== void 0) throw new Error("v3 policy capability is invalid");
7979
8167
  if (capability.kind === "encryption") {
7980
8168
  if (capability.action !== "tinycloud.encryption/decrypt") throw new Error("v3 policy capability is invalid");
@@ -8032,8 +8220,8 @@ function verifyV3PolicyAuthorization(input) {
8032
8220
  const factKeys = Object.keys(fact);
8033
8221
  const legacyShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact);
8034
8222
  const v4Shape = factKeys.length === POLICY_SESSION_FACT_KEYS.length + POLICY_SESSION_V4_AUDIT_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key) || POLICY_SESSION_V4_AUDIT_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact) && POLICY_SESSION_V4_AUDIT_FACT_KEYS.every((key) => key in fact);
8035
- const binding = object2(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8036
- const policy = object2(input.envelope.policy, "v3 policy");
8223
+ const binding = object3(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8224
+ const policy = object3(input.envelope.policy, "v3 policy");
8037
8225
  const now = Math.floor(Date.now() / 1e3);
8038
8226
  if (!legacyShape && !v4Shape || POLICY_SESSION_DIGEST_FACT_KEYS.some((key) => !LOWER_SHA256_HEX.test(fact[key])) || v4Shape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)) || compact.payload.aud !== input.holderDid || compact.payload.iss.split("#", 1)[0] !== fact.nodeAudience || fact.profile !== "policy-session-ucan/v1" || fact.ownerDid !== policy.ownerDid || fact.policyId !== policy.policyId || fact.policyCid !== input.envelope.policyCid || fact.contentSourceDigestHex !== input.envelope.contentSourceDigestHex || fact.enforcerDid !== binding.enforcerDid || fact.nodeAudience !== binding.nodeAudience || fact.recipientDid !== input.holderDid || fact.policyDelegationCid !== input.envelope.policyRoot.cid || fact.enforcementDelegationCid !== input.envelope.enforcementRoot.cid || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== input.envelope.policyRoot.cid || compact.payload.prf[1] !== input.envelope.enforcementRoot.cid || compact.payload.nbf > now || compact.payload.exp <= now || compact.payload.exp - compact.payload.nbf > 60 || canonicalize(compact.payload.att) !== canonicalize(policyAttenuationForV3(input.envelope))) throw new Error("v3 policy delegation signed binding mismatch");
8039
8227
  return compact;
@@ -8124,7 +8312,7 @@ var ShareRecipientClient = class {
8124
8312
  if (this.session === void 0) await this.establish(envelope);
8125
8313
  const response = await this.nativeInvoke({ action: "get", resource: envelope.resource });
8126
8314
  if (!response.ok) throw new Error("share recipient read was rejected");
8127
- const value = object2(await response.json(), "share read response");
8315
+ const value = object3(await response.json(), "share read response");
8128
8316
  const expectedAction = nativeAction("read");
8129
8317
  if (value.type !== "TinyCloudShareInvokeResponse" || value.version !== 2 || value.action !== expectedAction || value.resource !== envelope.resource.path || typeof value.content !== "string" || typeof value.bodyDigest !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.bodyDigest) || !Object.hasOwn(value, "proof")) throw new Error("share read response is invalid");
8130
8318
  const content = fromBase64Url(value.content);
@@ -8139,7 +8327,7 @@ var ShareRecipientClient = class {
8139
8327
  }
8140
8328
  async establishV3(envelope) {
8141
8329
  if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
8142
- const attestedEnforcer = object2(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8330
+ const attestedEnforcer = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8143
8331
  if (attestedEnforcer.enforcerDid !== envelope.target.nodeAudience || typeof attestedEnforcer.nodeAudience !== "string") throw new Error("v3 attested enforcer binding mismatch");
8144
8332
  try {
8145
8333
  ed25519PublicKeyFromDidKey(attestedEnforcer.nodeAudience);
@@ -8154,7 +8342,7 @@ var ShareRecipientClient = class {
8154
8342
  ...this.options.signal === void 0 ? {} : { signal: this.options.signal }
8155
8343
  });
8156
8344
  if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
8157
- const challenge = object2(await challengeResponse.json(), "v3 policy challenge");
8345
+ const challenge = object3(await challengeResponse.json(), "v3 policy challenge");
8158
8346
  if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid || challenge.nodeAudience !== attestedEnforcer.nodeAudience || challenge.enforcerDid !== void 0 && challenge.enforcerDid !== attestedEnforcer.enforcerDid || typeof challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(challenge.expiresAt)) || Date.parse(challenge.expiresAt) <= Date.now()) throw new Error("v3 policy challenge binding mismatch");
8159
8347
  const material = await this.options.buildPresentation({
8160
8348
  challenge,
@@ -8175,7 +8363,7 @@ var ShareRecipientClient = class {
8175
8363
  ...this.options.signal === void 0 ? {} : { signal: this.options.signal }
8176
8364
  });
8177
8365
  if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
8178
- const delegation = object2(await delegationResponse.json(), "v3 policy delegation");
8366
+ const delegation = object3(await delegationResponse.json(), "v3 policy delegation");
8179
8367
  if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
8180
8368
  const compact = verifyV3PolicyAuthorization({ authorization: delegation.authorization, cid: delegation.sessionCid, envelope, holderDid: this.options.holderDid });
8181
8369
  const fact = compact.payload.fct[0];
@@ -8217,7 +8405,7 @@ var ShareRecipientClient = class {
8217
8405
  this.options.onStage?.("decryption");
8218
8406
  const response = await this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json", Authorization: invocation.authorization }, body: JSON.stringify(body), ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
8219
8407
  if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
8220
- const value = object2(await response.json(), "v3 decrypt response");
8408
+ const value = object3(await response.json(), "v3 decrypt response");
8221
8409
  const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
8222
8410
  if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex3((0, import_sha2565.sha256)(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
8223
8411
  const unsigned = { ...value };
@@ -8305,11 +8493,11 @@ function createAddressedAuthorization(input) {
8305
8493
  },
8306
8494
  async verifyResult({ envelope, value, proof }) {
8307
8495
  try {
8308
- const wrapper = object2(proof, "share read proof");
8309
- const response = object2(wrapper.response, "share read response proof");
8496
+ const wrapper = object3(proof, "share read proof");
8497
+ const response = object3(wrapper.response, "share read response proof");
8310
8498
  const envelopeAction = nativeAction("read");
8311
8499
  if (response.type !== "TinyCloudShareInvokeResponse" || response.version !== 2 || response.action !== envelopeAction || response.resource !== envelope.resource.path || typeof response.bodyDigest !== "string" || response.bodyDigest !== value.bodyDigest || response.bodyDigest !== await digestBytes2(value.bytes)) return false;
8312
- const detached = object2(wrapper.detached, "share read detached proof");
8500
+ const detached = object3(wrapper.detached, "share read detached proof");
8313
8501
  if (input.trustedNode === void 0 || detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
8314
8502
  const unsigned = { ...response };
8315
8503
  delete unsigned.proof;
@@ -8378,6 +8566,7 @@ async function openNativeShare(sharing, link) {
8378
8566
  }
8379
8567
  // Annotate the CommonJS export names for ESM import in node:
8380
8568
  0 && (module.exports = {
8569
+ CredentialInvitationError,
8381
8570
  DEFAULT_MAX_CONTENT_BLOB_BYTES,
8382
8571
  DEFAULT_MAX_SEALED_BLOB_BYTES,
8383
8572
  DEFAULT_SHARE_LIFETIME_MS,
@@ -8410,6 +8599,7 @@ async function openNativeShare(sharing, link) {
8410
8599
  createNativeShare,
8411
8600
  createPolicyEnforcementDelegation,
8412
8601
  createShareV2HolderBindingArtifact,
8602
+ deliverCredentialInvitation,
8413
8603
  fromBase64Url,
8414
8604
  historyRecordForPublishedShare,
8415
8605
  inspectShare,