@tinycloud/share-sdk 0.3.0-beta.2 → 1.0.0-beta.3

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
@@ -29,12 +29,11 @@ __export(index_exports, {
29
29
  MemorySenderShareKeyStorage: () => MemorySenderShareKeyStorage,
30
30
  MemorySenderShareRecordStorage: () => MemorySenderShareRecordStorage,
31
31
  MemoryShareCache: () => MemoryShareCache,
32
+ NATIVE_SHARE_FRAGMENT_PARAMETER: () => NATIVE_SHARE_FRAGMENT_PARAMETER,
32
33
  OWNER_SHARE_REGISTRATION_DOMAIN: () => OWNER_SHARE_REGISTRATION_DOMAIN,
33
34
  SHARE_CONTENT_LIMIT: () => SHARE_CONTENT_LIMIT,
34
- SHARE_DELIVERY_AUTHORIZATION_DOMAIN: () => SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
35
35
  SHARE_PUBLISH_RESULT_VERSION: () => SHARE_PUBLISH_RESULT_VERSION,
36
36
  SHARE_RESULT_VERSION: () => SHARE_RESULT_VERSION,
37
- SHARE_SEALED_OVERHEAD: () => SHARE_SEALED_OVERHEAD,
38
37
  SHARE_V2_PROTOCOL: () => SHARE_V2_PROTOCOL,
39
38
  SenderShareStore: () => SenderShareStore,
40
39
  ShareNotifyError: () => ShareNotifyError,
@@ -50,23 +49,21 @@ __export(index_exports, {
50
49
  computeOwnerShareRegistrationCid: () => computeOwnerShareRegistrationCid,
51
50
  createAddressedAuthorization: () => createAddressedAuthorization,
52
51
  createDelegatedShareKey: () => createDelegatedShareKey,
52
+ createNativeShare: () => createNativeShare,
53
53
  createPolicyEnforcementDelegation: () => createPolicyEnforcementDelegation,
54
- createRegisteredPolicyAuthority: () => createRegisteredPolicyAuthority,
55
54
  createShareV2HolderBindingArtifact: () => createShareV2HolderBindingArtifact,
56
55
  fromBase64Url: () => fromBase64Url,
57
56
  historyRecordForPublishedShare: () => historyRecordForPublishedShare,
58
57
  inspectShare: () => inspectShare,
59
- isLegacyShareLink: () => isLegacyShareLink,
60
58
  listShares: () => listShares,
61
- migrateShare: () => migrateShare,
62
59
  normalizeShareTarget: () => normalizeShareTarget,
63
60
  notifyShare: () => notifyShare,
61
+ openNativeShare: () => openNativeShare,
64
62
  parseAddressedEnvelope: () => parseAddressedEnvelope,
63
+ parseNativeShareUrl: () => parseNativeShareUrl,
65
64
  publishAddressedShare: () => publishAddressedShare,
66
65
  publishPolicyShare: () => publishPolicyShare,
67
- publishShare: () => publishShare,
68
66
  publishTargetShare: () => publishTargetShare,
69
- receiveLegacyShare: () => receiveLegacyShare,
70
67
  receiveShare: () => receiveShare,
71
68
  recipientMatchesShareRecord: () => recipientMatchesShareRecord,
72
69
  redactPublishedShare: () => redactPublishedShare,
@@ -75,15 +72,12 @@ __export(index_exports, {
75
72
  restoreDelegatedShareKey: () => restoreDelegatedShareKey,
76
73
  resumeShareAuthorization: () => resumeShareAuthorization,
77
74
  revokeShare: () => revokeShare,
78
- shareDeliveryTrustedKid: () => shareDeliveryTrustedKid,
79
75
  showShare: () => showShare,
80
76
  targetAuthorizationMethod: () => targetAuthorizationMethod,
81
77
  toBase64Url: () => toBase64Url,
82
78
  toShareErrorInfo: () => toShareErrorInfo,
83
79
  validateOwnerSharePolicyRegistration: () => validateOwnerSharePolicyRegistration,
84
- validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes,
85
- validateShareDeliveryAuthorizationBytes: () => validateShareDeliveryAuthorizationBytes,
86
- verifyBearerEnvelope: () => verifyBearerEnvelope
80
+ validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes
87
81
  });
88
82
  module.exports = __toCommonJS(index_exports);
89
83
 
@@ -5442,7 +5436,7 @@ function validateV3Invariants(value, ctx) {
5442
5436
  if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
5443
5437
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
5444
5438
  }
5445
- if (value.attestedEnforcerBinding.enforcerDid !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
5439
+ if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
5446
5440
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
5447
5441
  }
5448
5442
  if (value.policy.contentSource.shareId !== value.contentSource.shareId || value.policy.contentSource.kvResource !== value.contentSource.kvResource || value.policy.contentSource.selector !== value.contentSource.selector || value.policy.contentSource.encryptionNetwork !== value.encryptionNetwork || value.policy.contentSource.encryptedSymmetricKeyDigestHex !== value.contentSource.encryptedSymmetricKeyDigestHex || value.policy.contentSource.keyVersion !== value.contentSource.keyVersion || value.policy.contentSource.mode !== value.contentSource.mode || value.policy.contentSource.initialCiphertextDigestHex !== value.contentSource.initialCiphertextDigestHex) {
@@ -5637,105 +5631,8 @@ function checkBearerDelegation(envelope, options = {}) {
5637
5631
  }
5638
5632
  return { ok: true, delegateeDid: audience };
5639
5633
  }
5640
- function mintBearerDelegation(options) {
5641
- const { issuerPrivateKey, audienceDid, resourceUri, expiresAtSeconds } = options;
5642
- if (!audienceDid.startsWith("did:")) {
5643
- throw new TypeError(`audienceDid must be a DID, got ${audienceDid}`);
5644
- }
5645
- if (resourceUri.length === 0) {
5646
- throw new TypeError("resourceUri must be non-empty");
5647
- }
5648
- if (!Number.isSafeInteger(expiresAtSeconds) || expiresAtSeconds <= 0) {
5649
- throw new TypeError(
5650
- `expiresAtSeconds must be a positive integer (epoch seconds), got ${expiresAtSeconds}`
5651
- );
5652
- }
5653
- const issuerDid = didKeyFromEd25519PublicKey(
5654
- import_ed25519.ed25519.getPublicKey(issuerPrivateKey)
5655
- );
5656
- const header = { alg: "EdDSA", typ: "JWT", ucv: "0.9.1" };
5657
- const payload = {
5658
- iss: issuerDid,
5659
- aud: audienceDid,
5660
- att: [{ with: resourceUri, can: BEARER_READ_ABILITY }],
5661
- prf: [],
5662
- exp: expiresAtSeconds
5663
- };
5664
- const signingInput = `${toBase64Url(utf8Bytes(JSON.stringify(header)))}.${toBase64Url(
5665
- utf8Bytes(JSON.stringify(payload))
5666
- )}`;
5667
- const signature = import_ed25519.ed25519.sign(utf8Bytes(signingInput), issuerPrivateKey);
5668
- return `${signingInput}.${toBase64Url(signature)}`;
5669
- }
5670
5634
  var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
5671
- var SEALED_BLOB_VERSION = 1;
5672
5635
  var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
5673
- var KEY_LENGTH = 32;
5674
- var NONCE_LENGTH = 12;
5675
- var TAG_LENGTH = 16;
5676
- var HEADER_LENGTH = 1;
5677
- function assertKey(key32) {
5678
- if (key32.length !== KEY_LENGTH) {
5679
- throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
5680
- }
5681
- }
5682
- async function importAesKey(key32, usage) {
5683
- assertKey(key32);
5684
- return globalThis.crypto.subtle.importKey(
5685
- "raw",
5686
- key32,
5687
- "AES-GCM",
5688
- false,
5689
- [usage]
5690
- );
5691
- }
5692
- function generateKey() {
5693
- return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
5694
- }
5695
- async function seal(plaintextBytes, key32) {
5696
- const { nonce, ciphertext } = await encryptEnvelope(plaintextBytes, key32);
5697
- const blob = new Uint8Array(HEADER_LENGTH + nonce.length + ciphertext.length);
5698
- blob[0] = SEALED_BLOB_VERSION;
5699
- blob.set(nonce, HEADER_LENGTH);
5700
- blob.set(ciphertext, HEADER_LENGTH + nonce.length);
5701
- return { blob, cid: await computeCid(blob) };
5702
- }
5703
- async function open(blob, key32) {
5704
- if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
5705
- throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
5706
- }
5707
- if (blob[0] !== SEALED_BLOB_VERSION) {
5708
- throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
5709
- }
5710
- const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
5711
- const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
5712
- return decryptEnvelope(nonce, ciphertext, key32);
5713
- }
5714
- async function encryptEnvelope(plaintextBytes, key32) {
5715
- const key = await importAesKey(key32, "encrypt");
5716
- const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
5717
- const ciphertext = new Uint8Array(
5718
- await globalThis.crypto.subtle.encrypt(
5719
- { name: "AES-GCM", iv: nonce, additionalData: AAD },
5720
- key,
5721
- plaintextBytes
5722
- )
5723
- );
5724
- return { nonce, ciphertext };
5725
- }
5726
- async function decryptEnvelope(nonce, ciphertext, key32) {
5727
- if (nonce.length !== NONCE_LENGTH) {
5728
- throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
5729
- }
5730
- const key = await importAesKey(key32, "decrypt");
5731
- return new Uint8Array(
5732
- await globalThis.crypto.subtle.decrypt(
5733
- { name: "AES-GCM", iv: nonce, additionalData: AAD },
5734
- key,
5735
- ciphertext
5736
- )
5737
- );
5738
- }
5739
5636
  function isPlainRecord(value) {
5740
5637
  const proto = Object.getPrototypeOf(value);
5741
5638
  return proto === Object.prototype || proto === null;
@@ -5866,8 +5763,6 @@ function equal(left, right) {
5866
5763
  return left.length === right.length && left.every((byte, index) => byte === right[index]);
5867
5764
  }
5868
5765
  var ED25519_VERIFY_OPTS2 = { zip215: false };
5869
- var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
5870
- var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
5871
5766
  var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
5872
5767
  var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
5873
5768
  var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
@@ -5875,22 +5770,6 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
5875
5770
  var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
5876
5771
  var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
5877
5772
  var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
5878
- function signingBytes(unsigned) {
5879
- const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
5880
- const body = utf8Bytes(canonicalize(unsigned));
5881
- const bytes3 = new Uint8Array(domain.length + body.length);
5882
- bytes3.set(domain);
5883
- bytes3.set(body, domain.length);
5884
- return bytes3;
5885
- }
5886
- function signingBytesV2(unsigned) {
5887
- const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
5888
- const body = utf8Bytes(canonicalize(unsigned));
5889
- const bytes3 = new Uint8Array(domain.length + body.length);
5890
- bytes3.set(domain);
5891
- bytes3.set(body, domain.length);
5892
- return bytes3;
5893
- }
5894
5773
  function signingBytesV3(unsigned) {
5895
5774
  const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
5896
5775
  const body = utf8Bytes(canonicalize(unsigned));
@@ -5899,53 +5778,6 @@ function signingBytesV3(unsigned) {
5899
5778
  bytes3.set(body, domain.length);
5900
5779
  return bytes3;
5901
5780
  }
5902
- function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
5903
- const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
5904
- const publicKey = import_ed255193.ed25519.getPublicKey(ed25519PrivKey);
5905
- const signature = import_ed255193.ed25519.sign(signingBytes(unsigned), ed25519PrivKey);
5906
- return {
5907
- ...unsigned,
5908
- signature: {
5909
- signerDid: didKeyFromEd25519PublicKey(publicKey),
5910
- algorithm: "Ed25519",
5911
- value: toBase64Url(signature)
5912
- }
5913
- };
5914
- }
5915
- function verifyEnvelopeSignatureOnly(envelope) {
5916
- const parsed = shareEnvelopeSchema.parse(envelope);
5917
- const { signature, ...unsigned } = parsed;
5918
- const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
5919
- return import_ed255193.ed25519.verify(
5920
- fromBase64Url(signature.value),
5921
- signingBytes(unsigned),
5922
- publicKey,
5923
- ED25519_VERIFY_OPTS2
5924
- );
5925
- }
5926
- async function verifyEnvelope(envelope, options) {
5927
- const parsed = shareEnvelopeSchema.parse(envelope);
5928
- if (parsed.signature.signerDid !== options.expectedSignerDid) return false;
5929
- if (!verifyEnvelopeSignatureOnly(parsed)) return false;
5930
- if (parsed.authorizationTarget.kind === "policy") {
5931
- const policyBytes = fromBase64Url(parsed.authorizationTarget.policyBytes);
5932
- if (await computeCid(policyBytes) !== parsed.authorizationTarget.policyCid) {
5933
- return false;
5934
- }
5935
- }
5936
- return true;
5937
- }
5938
- function verifyEnvelopeV2SignatureOnly(envelope) {
5939
- const parsed = shareEnvelopeV2Schema.parse(envelope);
5940
- const { signature, ...unsigned } = parsed;
5941
- return import_ed255193.ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5942
- }
5943
- async function verifyEnvelopeV2(envelope, options) {
5944
- const parsed = shareEnvelopeV2Schema.parse(envelope);
5945
- if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
5946
- if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
5947
- return true;
5948
- }
5949
5781
  function verifyEnvelopeV3SignatureOnly(envelope) {
5950
5782
  const parsed = shareEnvelopeV3Schema.parse(envelope);
5951
5783
  const { signature, ...unsigned } = parsed;
@@ -5991,7 +5823,7 @@ async function verifyEnvelopeV3(envelope, options) {
5991
5823
  const binding = parsed.attestedEnforcerBinding;
5992
5824
  const { signature: bindingSignature, ...unsignedBinding } = binding;
5993
5825
  const expectedBindingDigestHex = hex((0, import_sha256.sha256)(new TextEncoder().encode(canonicalize({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
5994
- if (binding.enforcerDid !== parsed.target.nodeAudience || binding.attestationBindingDigestHex !== expectedBindingDigestHex || bindingSignature.signerDid !== binding.nodeAudience || bindingSignature.suite !== "Ed25519" || Date.parse(binding.issuedAt) > Date.now() || Date.parse(binding.expiresAt) <= Date.now() || Date.parse(binding.expiresAt) < Date.parse(parsed.expiry)) return false;
5826
+ if (binding.nodeAudience !== parsed.target.nodeAudience || binding.attestationBindingDigestHex !== expectedBindingDigestHex || bindingSignature.signerDid !== binding.nodeAudience || bindingSignature.suite !== "Ed25519" || Date.parse(binding.issuedAt) > Date.now() || Date.parse(binding.expiresAt) <= Date.now() || Date.parse(binding.expiresAt) < Date.parse(parsed.expiry)) return false;
5995
5827
  try {
5996
5828
  const digest3 = (0, import_sha256.sha256)(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize(unsignedBinding)}`));
5997
5829
  if (!import_ed255193.ed25519.verify(fromBase64Url(bindingSignature.value), digest3, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
@@ -6033,7 +5865,8 @@ function base32Lower(bytes3) {
6033
5865
  return output;
6034
5866
  }
6035
5867
  var KEY_LENGTH2 = 32;
6036
- var INLINE_PREFIX = "#v=2&p=";
5868
+ var INLINE_PREFIX = "#tc2=";
5869
+ var PUBLIC_INLINE_PARAMETER = "tc2";
6037
5870
  var MAX_INLINE_BYTES = 256 * 1024;
6038
5871
  function assertCanonicalCid(cidString) {
6039
5872
  const cid2 = CID.parse(cidString);
@@ -6042,77 +5875,30 @@ function assertCanonicalCid(cidString) {
6042
5875
  throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
6043
5876
  }
6044
5877
  }
6045
- function encodeShareUrl({ origin, ciphertextCid, key32 }) {
6046
- if (key32.length !== KEY_LENGTH2) {
6047
- throw new TypeError(`key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
6048
- }
6049
- assertCanonicalCid(ciphertextCid);
6050
- if (!isCanonicalHttpsOrigin(origin)) {
6051
- throw new TypeError(`origin must be a canonical https origin, got ${origin}`);
6052
- }
6053
- return `${origin}/s/${ciphertextCid}#k=${toBase64Url(key32)}`;
6054
- }
6055
- function parseShareUrl(url, options = {}) {
6056
- const parsed = new URL(url);
6057
- if (parsed.protocol !== "https:") {
6058
- throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
6059
- }
6060
- if (parsed.username !== "" || parsed.password !== "") {
6061
- throw new TypeError("share URL must not carry userinfo");
6062
- }
6063
- if (parsed.search !== "") {
6064
- throw new TypeError("share URL must not have a query string");
6065
- }
6066
- if (options.expectedOrigin !== void 0) {
6067
- if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
6068
- throw new TypeError(
6069
- `expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
6070
- );
6071
- }
6072
- if (parsed.origin !== options.expectedOrigin) {
6073
- throw new TypeError(
6074
- `share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
6075
- );
6076
- }
6077
- }
6078
- const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
6079
- if (!match || match[1] === void 0) {
6080
- throw new TypeError(`not a share URL path: ${parsed.pathname}`);
6081
- }
6082
- const ciphertextCid = match[1];
6083
- assertCanonicalCid(ciphertextCid);
6084
- if (!parsed.hash.startsWith("#k=")) {
6085
- throw new TypeError("share URL is missing the #k= key fragment");
6086
- }
6087
- const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
6088
- if (key32.length !== KEY_LENGTH2) {
6089
- throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
6090
- }
6091
- return { ciphertextCid, key32 };
6092
- }
6093
- async function encodeInlineShareUrl(parts) {
5878
+ async function encodePublicInlineShareUrl(parts) {
6094
5879
  if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
6095
- if (parts.ciphertext.byteLength === 0 || parts.ciphertext.byteLength > MAX_INLINE_BYTES) throw new RangeError("inline ciphertext is outside the allowed size");
6096
- if (parts.key32 !== void 0 && parts.key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
6097
- const ciphertextCid = await computeCid(parts.ciphertext);
6098
- const payload = canonicalize({
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({
6099
5883
  v: 2,
6100
- c: toBase64Url(parts.ciphertext),
6101
- cid: ciphertextCid,
6102
- ...parts.key32 === void 0 ? {} : { k: toBase64Url(parts.key32) }
6103
- });
6104
- const payloadBytes = new TextEncoder().encode(payload);
5884
+ c: toBase64Url(parts.plaintext),
5885
+ cid: ciphertextCid
5886
+ }));
6105
5887
  if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
6106
- return `${parts.origin}/s/inline${INLINE_PREFIX}${toBase64Url(payloadBytes)}`;
5888
+ return `${parts.origin}/viewer?${PUBLIC_INLINE_PARAMETER}=${toBase64Url(payloadBytes)}`;
6107
5889
  }
6108
5890
  function parseInlineShareUrl(url, options = {}) {
6109
5891
  const parsed = new URL(url);
6110
- if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.search !== "") throw new TypeError("inline share URL must be canonical HTTPS without query or userinfo");
5892
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("inline share URL must be canonical HTTPS without userinfo");
6111
5893
  if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
6112
- if (parsed.pathname !== "/s/inline" || !parsed.hash.startsWith(INLINE_PREFIX)) throw new TypeError("not an inline v2 share URL");
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");
6113
5899
  let payloadBytes;
6114
5900
  try {
6115
- payloadBytes = fromBase64Url(parsed.hash.slice(INLINE_PREFIX.length));
5901
+ payloadBytes = fromBase64Url(secretPayload ?? publicPayload);
6116
5902
  } catch {
6117
5903
  throw new TypeError("inline payload is not canonical base64url");
6118
5904
  }
@@ -6125,37 +5911,32 @@ function parseInlineShareUrl(url, options = {}) {
6125
5911
  }
6126
5912
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload must be an object");
6127
5913
  if (canonicalize(value) !== new TextDecoder("utf-8").decode(payloadBytes)) throw new TypeError("inline payload is not canonical JSON");
6128
- const record2 = value;
6129
- const keys = Object.keys(record2);
6130
- if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") || record2.v !== 2 || typeof record2.c !== "string" || typeof record2.cid !== "string") throw new TypeError("inline payload has invalid fields");
5914
+ 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");
6131
5917
  let ciphertext;
6132
5918
  try {
6133
- ciphertext = fromBase64Url(record2.c);
5919
+ ciphertext = fromBase64Url(record.c);
6134
5920
  } catch {
6135
5921
  throw new TypeError("inline ciphertext is not canonical base64url");
6136
5922
  }
6137
5923
  if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
6138
- assertCanonicalCid(record2.cid);
6139
- if (record2.k !== void 0 && typeof record2.k !== "string") throw new TypeError("inline key must be base64url");
5924
+ assertCanonicalCid(record.cid);
5925
+ if (record.k !== void 0 && typeof record.k !== "string") throw new TypeError("inline key must be base64url");
6140
5926
  let key32;
6141
5927
  try {
6142
- key32 = record2.k === void 0 ? void 0 : fromBase64Url(record2.k);
5928
+ key32 = record.k === void 0 ? void 0 : fromBase64Url(record.k);
6143
5929
  } catch {
6144
5930
  throw new TypeError("inline key is not canonical base64url");
6145
5931
  }
6146
5932
  if (key32 !== void 0 && key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
6147
- return { kind: "inline", ciphertextCid: record2.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
6148
- }
6149
- function parseCompactOrInlineShareUrl(url, options = {}) {
6150
- if (new URL(url).pathname === "/s/inline") return parseInlineShareUrl(url, options);
6151
- return { kind: "compact", ...parseShareUrl(url, options) };
5933
+ return { kind: "inline", ciphertextCid: record.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
6152
5934
  }
6153
5935
 
6154
5936
  // src/receive.ts
6155
5937
  var SHARE_RESULT_VERSION = 1;
6156
5938
  var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
6157
5939
  var DEFAULT_MAX_CONTENT_BLOB_BYTES = DEFAULT_MAX_SEALED_BLOB_BYTES;
6158
- var CONTENT_SEALED_OVERHEAD = 1 + 12 + 16;
6159
5940
  var ShareReceiveError = class extends Error {
6160
5941
  code;
6161
5942
  details;
@@ -6165,7 +5946,6 @@ var ShareReceiveError = class extends Error {
6165
5946
  this.code = code3;
6166
5947
  this.details = details;
6167
5948
  }
6168
- /** Machine output is deliberately code-only; diagnostics belong on stderr. */
6169
5949
  toJSON() {
6170
5950
  return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: this.code } };
6171
5951
  }
@@ -6174,338 +5954,79 @@ function toShareErrorInfo(error) {
6174
5954
  if (error instanceof ShareReceiveError) return error.toJSON();
6175
5955
  return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: "fetch-failed" } };
6176
5956
  }
6177
- function boundedBytes(value, limit, code3) {
6178
- if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("fetch-failed", "share byte limit is invalid");
6179
- if (!(value instanceof Uint8Array)) throw new ShareReceiveError("fetch-failed", "registry returned invalid bytes");
6180
- if (value.byteLength > limit) throw new ShareReceiveError(code3, "share blob exceeds the configured byte limit");
6181
- return value;
6182
- }
6183
- async function readResponseBytes(response, limit, tooLargeCode) {
6184
- const contentLength = response.headers.get("content-length");
6185
- if (contentLength !== null && Number.isSafeInteger(Number(contentLength)) && Number(contentLength) > limit) {
6186
- throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
6187
- }
6188
- if (response.body === null) {
6189
- return boundedBytes(new Uint8Array(await response.arrayBuffer()), limit, tooLargeCode);
6190
- }
6191
- const reader = response.body.getReader();
6192
- const chunks = [];
6193
- let total = 0;
6194
- try {
6195
- for (; ; ) {
6196
- const part = await reader.read();
6197
- if (part.done) break;
6198
- total += part.value.byteLength;
6199
- if (total > limit) {
6200
- await reader.cancel();
6201
- throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
6202
- }
6203
- chunks.push(part.value);
6204
- }
6205
- } finally {
6206
- reader.releaseLock();
6207
- }
6208
- const bytes3 = new Uint8Array(total);
6209
- let offset = 0;
6210
- for (const chunk of chunks) {
6211
- bytes3.set(chunk, offset);
6212
- offset += chunk.byteLength;
6213
- }
6214
- return bytes3;
6215
- }
6216
- function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
6217
- if (options.fetchBlob !== void 0) return async (input) => {
6218
- try {
6219
- options.signal?.throwIfAborted();
6220
- const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
6221
- options.signal?.throwIfAborted();
6222
- return boundedBytes(bytes3, limit, tooLargeCode);
6223
- } catch (error) {
6224
- if (options.signal?.aborted) throw options.signal.reason ?? error;
6225
- if (error instanceof ShareReceiveError) throw error;
6226
- throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
6227
- }
6228
- };
6229
- if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "a registry fetch adapter is required");
6230
- const fetchFn = options.fetchFn ?? globalThis.fetch;
6231
- const base3 = options.registryBaseUrl.replace(/\/+$/, "");
6232
- return async ({ cid: cid2 }) => {
6233
- try {
6234
- options.signal?.throwIfAborted();
6235
- const response = await fetchFn(`${base3}/ipfs/${cid2}?format=raw`, {
6236
- headers: { accept: "application/vnd.ipld.raw" },
6237
- redirect: "error",
6238
- ...options.signal === void 0 ? {} : { signal: options.signal }
6239
- });
6240
- options.signal?.throwIfAborted();
6241
- if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
6242
- const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
6243
- options.signal?.throwIfAborted();
6244
- return bytes3;
6245
- } catch (error) {
6246
- if (options.signal?.aborted) throw options.signal.reason ?? error;
6247
- if (error instanceof ShareReceiveError) throw error;
6248
- throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
6249
- }
6250
- };
6251
- }
6252
5957
  function metadataFor(envelope, origin) {
6253
- if (envelope.version === 2 || envelope.version === 3) {
6254
- const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
6255
- return {
6256
- protocol: "tinycloud-share",
6257
- version: 1,
6258
- shareId: envelope.shareId,
6259
- origin,
6260
- target: { ...envelope.target, kind: targetKind3 },
6261
- resource: envelope.resource,
6262
- actions: envelope.actions,
6263
- expiresAt: envelope.expiry,
6264
- display: {
6265
- ...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
6266
- ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
6267
- ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
6268
- },
6269
- ...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
6270
- };
6271
- }
5958
+ const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
6272
5959
  return {
6273
5960
  protocol: "tinycloud-share",
6274
5961
  version: 1,
6275
5962
  shareId: envelope.shareId,
6276
5963
  origin,
6277
- target: { ...envelope.target, kind: "bearer" },
6278
- resource: envelope.target.resource,
6279
- actions: ["read"],
5964
+ target: { ...envelope.target, kind },
5965
+ resource: { ...envelope.resource },
5966
+ actions: [...envelope.actions],
6280
5967
  expiresAt: envelope.expiry,
6281
5968
  display: {
6282
5969
  ...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
6283
5970
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
6284
5971
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
6285
- },
6286
- ...envelope.content === void 0 ? {} : { content: { cid: envelope.content.cid } }
5972
+ }
6287
5973
  };
6288
5974
  }
6289
- function parseEnvelope(bytes3) {
6290
- try {
6291
- const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
6292
- const value = JSON.parse(text);
6293
- if (canonicalize(value) !== text) throw new Error("share envelope is not canonical JSON");
6294
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
6295
- const version = value.version;
6296
- if (version === 3) return shareEnvelopeV3Schema.parse(value);
6297
- if (version === 2) return shareEnvelopeV2Schema.parse(value);
6298
- }
6299
- return shareEnvelopeSchema.parse(value);
6300
- } catch {
6301
- throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
6302
- }
6303
- }
6304
- async function resolveShareEnvelope(link, options = {}) {
5975
+ async function resolvePolicyShare(link, options) {
5976
+ options.signal?.throwIfAborted();
5977
+ let url;
6305
5978
  let parsed;
6306
5979
  try {
6307
- parsed = parseCompactOrInlineShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
5980
+ 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");
6308
5984
  } catch {
6309
5985
  throw new ShareReceiveError("invalid-link", "share link format is invalid");
6310
5986
  }
5987
+ 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");
5990
+ options.signal?.throwIfAborted();
5991
+ let envelope;
6311
5992
  try {
6312
- if (parsed.key32 !== void 0) options.onKeyParsed?.(parsed.key32);
6313
- const url = new URL(link);
6314
- const sealed = parsed.kind === "inline" ? boundedBytes(parsed.ciphertext, options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES, "max-bytes-exceeded") : await registryFetcher(options, options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES)({ origin: url.origin, cid: parsed.ciphertextCid });
6315
- if (await computeCid(sealed) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "registry bytes do not match the link CID");
6316
- let plaintext;
6317
- try {
6318
- plaintext = parsed.key32 === void 0 ? sealed : await open(sealed, parsed.key32);
6319
- } catch {
6320
- throw new ShareReceiveError("decrypt-failed", "share envelope could not be opened");
6321
- }
6322
- return { envelope: parseEnvelope(plaintext), origin: url.origin, cid: parsed.ciphertextCid, kind: parsed.kind };
6323
- } finally {
6324
- parsed.key32?.fill(0);
6325
- }
6326
- }
6327
- function actionName(value) {
6328
- return value === "read" ? "tinycloud.kv/get" : value === "list" ? "tinycloud.kv/list" : value === "edit" ? "tinycloud.kv/put" : value;
6329
- }
6330
- function resourceContains(outer, inner) {
6331
- if (outer.kind === "exact") return inner.kind === "exact" && outer.path === inner.path;
6332
- const prefix = outer.path.endsWith("/") ? outer.path : `${outer.path}/`;
6333
- return inner.path === outer.path || inner.path.startsWith(prefix);
6334
- }
6335
- function policyEvidenceMatches(envelope, evidence) {
6336
- const registeredActions = new Set(evidence.actions.map(actionName));
6337
- return evidence.policyCid === (envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "") && evidence.signerDid === envelope.signature.signerDid && evidence.registrationCid.length > 0 && evidence.shareId === envelope.shareId && canonicalize(evidence.recipientMatcher) === canonicalize(envelope.recipientMatcher) && canonicalize(evidence.target) === canonicalize(envelope.target) && resourceContains(evidence.resource, envelope.resource) && envelope.actions.every((action) => registeredActions.has(actionName(action))) && canonicalize(evidence.contentSource) === canonicalize(envelope.contentSource) && evidence.contentSourceDigest === envelope.contentSourceDigest && evidence.delegationCid === envelope.delegationCid && evidence.authorityMaterialHandle === envelope.authorityMaterialHandle && evidence.authorityMaterialDigest === envelope.authorityMaterialDigest && evidence.expiresAt === envelope.expiry && Number.isFinite(Date.parse(evidence.expiresAt)) && Date.parse(evidence.expiresAt) === Date.parse(envelope.expiry);
6338
- }
6339
- async function verifyV2Envelope(envelope, linkOrigin, options) {
6340
- if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
6341
- throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
6342
- }
6343
- try {
6344
- if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
5993
+ const encoded = new TextDecoder("utf-8", { fatal: true }).decode(parsed.ciphertext);
5994
+ const value = JSON.parse(encoded);
5995
+ if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
5996
+ envelope = shareEnvelopeV3Schema.parse(value);
6345
5997
  } catch {
6346
- throw new ShareReceiveError("signature-invalid", "share signature is invalid");
6347
- }
6348
- const expiry = Date.parse(envelope.expiry);
6349
- if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
6350
- if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
6351
- if (envelope.authorizationTarget.kind === "policy") {
6352
- if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "an external policy authority is required");
6353
- let evidence;
6354
- try {
6355
- evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
6356
- } catch {
6357
- evidence = void 0;
6358
- }
6359
- if (evidence === void 0 || !policyEvidenceMatches(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "share policy authority evidence is not bound to the envelope");
6360
- if (options.trustedSignerDid !== void 0 && (evidence.signerDid !== options.trustedSignerDid || envelope.signature.signerDid !== options.trustedSignerDid)) {
6361
- throw new ShareReceiveError("signature-invalid", "share signer is not trusted");
6362
- }
6363
- } else if (envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
6364
- throw new ShareReceiveError("capability-invalid", "recipient authorization does not match the signed recipient");
6365
- } else {
6366
- if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
6367
- }
6368
- }
6369
- async function verifyV3Envelope(envelope, linkOrigin, options) {
6370
- if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
6371
- throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
5998
+ throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
6372
5999
  }
6373
6000
  try {
6374
6001
  if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
6375
6002
  } catch {
6376
6003
  throw new ShareReceiveError("signature-invalid", "share signature is invalid");
6377
6004
  }
6005
+ options.signal?.throwIfAborted();
6378
6006
  const expiry = Date.parse(envelope.expiry);
6379
6007
  if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
6380
6008
  if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
6381
- }
6382
- async function verifyV1PolicyEnvelope(envelope, linkOrigin, options) {
6383
- if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
6384
- if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
6385
- throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
6386
- }
6387
- throw new ShareReceiveError("unsupported-target", "share policy target is not supported", { reason: "policy-target" });
6388
- }
6389
- async function verifyBearerEnvelope(envelope, linkOrigin, options = {}) {
6390
- if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
6391
- throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
6392
- }
6393
- if (envelope.authorizationTarget.kind !== "bearerKey") throw new ShareReceiveError("unsupported-target", "this receive path only handles bearer shares", { reason: envelope.authorizationTarget.kind === "policy" ? "policy-target" : "recipient-did-target" });
6394
- if (envelope.target.resource.kind !== "exact") throw new ShareReceiveError("unsupported-target", "this receive path only handles exact resources", { reason: "prefix-resource" });
6395
- const expiry = Date.parse(envelope.expiry);
6396
- if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
6397
- try {
6398
- if (!await verifyEnvelope(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
6399
- } catch {
6400
- throw new ShareReceiveError("signature-invalid", "share signature is invalid");
6401
- }
6402
- if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
6403
- const capability = checkBearerDelegation(envelope, options.now === void 0 ? {} : { now: options.now });
6404
- if (!capability.ok) throw new ShareReceiveError("capability-invalid", "share delegation does not authorize the target");
6405
- }
6406
- async function verifyResolved(envelope, linkOrigin, options) {
6407
- await verifyBearerEnvelope(envelope, linkOrigin, options);
6009
+ options.onResolvedAddressedEnvelope?.(envelope, parsed.ciphertextCid);
6010
+ return { envelope, origin: url.origin, cid: parsed.ciphertextCid };
6408
6011
  }
6409
6012
  async function inspectShare(link, options = {}) {
6410
- const resolved = await resolveShareEnvelope(link, options);
6411
- if (resolved.envelope.version === 1 && resolved.envelope.authorizationTarget.kind === "policy") await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
6412
- else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
6413
- else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
6414
- else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
6415
- if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
6416
- return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
6013
+ const resolved = await resolvePolicyShare(link, options);
6014
+ return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
6417
6015
  }
6418
6016
  async function receiveShare(link, options = {}) {
6419
- const resolved = await resolveShareEnvelope(link, options);
6420
- if (resolved.envelope.version === 3) {
6421
- await verifyV3Envelope(resolved.envelope, resolved.origin, options);
6422
- options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
6423
- return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
6424
- }
6425
- if (resolved.envelope.version === 2) {
6426
- await verifyV2Envelope(resolved.envelope, resolved.origin, options);
6427
- options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
6428
- const method = resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
6429
- if (options.authorization === void 0) return { state: "authorization-required", method };
6430
- const result = options.authorizationResumeToken === void 0 ? await options.authorization.begin({ envelope: resolved.envelope, method }) : await options.authorization.resume({ envelope: resolved.envelope, method, resumeToken: options.authorizationResumeToken, ...options.authorizationProof === void 0 ? {} : { proof: options.authorizationProof } });
6431
- if (result.state === "authorization-required") return result;
6432
- if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "share authorization was denied");
6433
- if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
6434
- throw new ShareReceiveError("content-integrity-failed", "share authorization returned an unsigned content result");
6435
- }
6436
- const authorized = result.value;
6437
- try {
6438
- if (!await options.authorization.verifyResult({ envelope: resolved.envelope, value: authorized, proof: authorized.proof })) throw new Error("node proof");
6439
- } catch {
6440
- throw new ShareReceiveError("content-integrity-failed", "node authorization proof is invalid");
6441
- }
6442
- const bytes4 = authorized.bytes.slice();
6443
- const maxBytes = options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES;
6444
- if (bytes4.byteLength > maxBytes) throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
6445
- const digestBytes3 = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes4));
6446
- const bodyDigest = toBase64Url(digestBytes3);
6447
- if (authorized.bodyDigest !== bodyDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content digest does not match");
6448
- if (authorized.contentSourceDigest !== resolved.envelope.contentSourceDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content source does not match");
6449
- const binding = authorized.binding;
6450
- if (binding.shareId !== resolved.envelope.shareId || binding.delegationCid !== resolved.envelope.delegationCid || binding.authorityMaterialHandle !== resolved.envelope.authorityMaterialHandle || binding.authorityMaterialDigest !== resolved.envelope.authorityMaterialDigest || binding.resource.kind !== resolved.envelope.resource.kind || binding.resource.path !== resolved.envelope.resource.path || binding.action !== void 0 && !resolved.envelope.actions.some((action) => action === binding.action || `tinycloud.kv/${action}` === binding.action)) throw new ShareReceiveError("content-integrity-failed", "authorized content binding does not match");
6451
- if (resolved.envelope.metadata.byteLength !== void 0 && resolved.envelope.metadata.byteLength !== bytes4.byteLength) {
6452
- throw new ShareReceiveError("content-integrity-failed", "authorized content length does not match");
6453
- }
6454
- let text2;
6455
- try {
6456
- text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes4);
6457
- } catch {
6458
- }
6459
- return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes4, ...text2 === void 0 ? {} : { text: text2 } };
6460
- }
6461
- if (resolved.envelope.authorizationTarget.kind === "policy") {
6462
- await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
6463
- options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
6464
- return { state: "authorization-required", method: "email-claim" };
6465
- }
6466
- await verifyResolved(resolved.envelope, resolved.origin, options);
6467
- let bytes3 = new Uint8Array();
6468
- if (resolved.envelope.content !== void 0) {
6469
- bytes3 = await registryFetcher(
6470
- options,
6471
- (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES) + CONTENT_SEALED_OVERHEAD,
6472
- "max-bytes-exceeded",
6473
- "content"
6474
- )({ origin: resolved.origin, cid: resolved.envelope.content.cid });
6475
- if (await computeCid(bytes3) !== resolved.envelope.content.cid) throw new ShareReceiveError("content-integrity-failed", "shared content CID does not match");
6476
- const key = fromBase64Url(resolved.envelope.content.key);
6477
- try {
6478
- bytes3 = await open(bytes3, key);
6479
- } catch {
6480
- throw new ShareReceiveError("content-integrity-failed", "shared content could not be opened");
6481
- } finally {
6482
- key.fill(0);
6483
- }
6484
- if (bytes3.byteLength > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) {
6485
- throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
6486
- }
6487
- }
6488
- let text;
6489
- if (bytes3.byteLength !== 0) {
6490
- try {
6491
- text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
6492
- } catch {
6493
- }
6494
- }
6495
- return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes3, ...text === void 0 ? {} : { text } };
6017
+ const resolved = await resolvePolicyShare(link, options);
6018
+ return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
6496
6019
  }
6497
6020
 
6498
6021
  // src/publish.ts
6499
- var import_ed255194 = require("@noble/curves/ed25519");
6500
6022
  var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
6501
- var SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
6502
6023
  var SHARE_PUBLISH_RESULT_VERSION = 1;
6503
6024
  var DEFAULT_SHARE_LIFETIME_MS = 7 * 24 * 60 * 60 * 1e3;
6504
6025
  function redactPublishedShare(result) {
6505
6026
  return {
6506
6027
  protocol: "tinycloud-share",
6507
6028
  version: SHARE_PUBLISH_RESULT_VERSION,
6508
- link: { kind: result.link.kind, cid: result.link.cid },
6029
+ link: { ...result.link },
6509
6030
  metadata: {
6510
6031
  protocol: "tinycloud-share",
6511
6032
  version: 1,
@@ -6515,10 +6036,8 @@ function redactPublishedShare(result) {
6515
6036
  resource: { ...result.metadata.resource },
6516
6037
  actions: [...result.metadata.actions],
6517
6038
  expiresAt: result.metadata.expiresAt,
6518
- display: { ...result.metadata.display },
6519
- ...result.metadata.content === void 0 ? {} : { content: { cid: result.metadata.content.cid } }
6520
- },
6521
- registryDeleteAfter: result.registryDeleteAfter
6039
+ display: { ...result.metadata.display }
6040
+ }
6522
6041
  };
6523
6042
  }
6524
6043
  var SharePublishError = class extends Error {
@@ -6529,237 +6048,6 @@ var SharePublishError = class extends Error {
6529
6048
  this.code = code3;
6530
6049
  }
6531
6050
  };
6532
- function assertSafeFilename(filename) {
6533
- if (filename.length === 0 || filename === "." || filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(filename)) {
6534
- throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
6535
- }
6536
- }
6537
- function assertOrigin(origin, label) {
6538
- if (!isCanonicalHttpsOrigin(origin)) {
6539
- throw new SharePublishError("invalid-argument", `${label} must be a canonical HTTPS origin`);
6540
- }
6541
- }
6542
- async function snapshotSource(source, limit) {
6543
- if (!Number.isSafeInteger(limit) || limit <= 0) {
6544
- throw new SharePublishError("invalid-argument", "maxBytes must be a positive safe integer");
6545
- }
6546
- if (source instanceof Uint8Array) {
6547
- if (source.byteLength > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
6548
- return source.slice();
6549
- }
6550
- const chunks = [];
6551
- let total = 0;
6552
- try {
6553
- for await (const chunk of source) {
6554
- if (!(chunk instanceof Uint8Array)) throw new SharePublishError("invalid-argument", "share source yielded invalid bytes");
6555
- total += chunk.byteLength;
6556
- if (total > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
6557
- chunks.push(chunk);
6558
- }
6559
- } catch (error) {
6560
- if (error instanceof SharePublishError) throw error;
6561
- throw new SharePublishError("upload-failed", "share source could not be read");
6562
- }
6563
- const bytes3 = new Uint8Array(total);
6564
- let offset = 0;
6565
- for (const chunk of chunks) {
6566
- bytes3.set(chunk, offset);
6567
- offset += chunk.byteLength;
6568
- }
6569
- return bytes3;
6570
- }
6571
- function assertTarget(target) {
6572
- if (target.kind !== "bearer") {
6573
- throw new SharePublishError("unsupported-target", "this release publishes bearer shares only");
6574
- }
6575
- }
6576
- function assertRegistryUrl(value, allowInsecure) {
6577
- let url;
6578
- try {
6579
- url = new URL(value);
6580
- } catch {
6581
- throw new SharePublishError("invalid-argument", "registry URL is invalid");
6582
- }
6583
- if (url.username || url.password || url.search || url.hash) throw new SharePublishError("invalid-argument", "registry URL must not contain credentials, query, or fragment");
6584
- if (!allowInsecure && url.protocol !== "https:") throw new SharePublishError("invalid-argument", "production registry uploads require HTTPS");
6585
- if (url.protocol !== "https:" && url.hostname !== "127.0.0.1" && url.hostname !== "localhost") throw new SharePublishError("invalid-argument", "insecure registry uploads are limited to localhost");
6586
- return url;
6587
- }
6588
- async function defaultUpload(options, input) {
6589
- if (options.registryBaseUrl === void 0) throw new SharePublishError("upload-auth-required", "an authenticated share registry is required");
6590
- const base3 = assertRegistryUrl(options.registryBaseUrl, options.allowInsecureRegistry === true).toString().replace(/\/$/, "");
6591
- const localInsecure = options.allowInsecureRegistry === true && (new URL(base3).hostname === "127.0.0.1" || new URL(base3).hostname === "localhost");
6592
- if (options.authorizeUpload === void 0 && !localInsecure) {
6593
- throw new SharePublishError("upload-auth-required", "share upload authorization is required");
6594
- }
6595
- let authorization;
6596
- if (options.authorizeUpload !== void 0) {
6597
- try {
6598
- authorization = await options.authorizeUpload(input);
6599
- } catch {
6600
- throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
6601
- }
6602
- }
6603
- if (authorization !== void 0 && options.authorizationOrigin !== void 0) {
6604
- let authorizationUrl;
6605
- try {
6606
- authorizationUrl = new URL(options.authorizationOrigin);
6607
- } catch {
6608
- throw new SharePublishError("invalid-argument", "upload authorization origin is invalid");
6609
- }
6610
- if (authorizationUrl.origin !== new URL(base3).origin) throw new SharePublishError("upload-auth-required", "upload authorization is scoped to another origin");
6611
- }
6612
- const headers = new Headers({
6613
- "content-type": "application/vnd.ipld.raw",
6614
- "if-none-match": "*",
6615
- "x-delete-after": input.deleteAfter
6616
- });
6617
- if (typeof authorization === "string") {
6618
- headers.set("x-tinycloud-upload-attestation", authorization);
6619
- try {
6620
- const attestation = JSON.parse(authorization);
6621
- if (attestation.retention === void 0) throw new Error("retention");
6622
- headers.set("x-tinycloud-retention", canonicalize(attestation.retention));
6623
- } catch {
6624
- throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
6625
- }
6626
- } else if (authorization !== void 0) {
6627
- new Headers(authorization).forEach((value, key) => headers.set(key, value));
6628
- }
6629
- let response;
6630
- try {
6631
- response = await (options.fetchFn ?? globalThis.fetch)(`${base3}/blobs`, {
6632
- method: "POST",
6633
- headers,
6634
- body: input.blob,
6635
- ...options.credentials === void 0 ? {} : { credentials: options.credentials },
6636
- redirect: "error",
6637
- referrerPolicy: "no-referrer"
6638
- });
6639
- } catch {
6640
- throw new SharePublishError("upload-failed", "share registry is unavailable");
6641
- }
6642
- if (response.status === 401 || response.status === 403) throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
6643
- if (!response.ok) throw new SharePublishError("upload-failed", "share registry rejected the encrypted blob");
6644
- let body;
6645
- try {
6646
- body = await response.json();
6647
- } catch {
6648
- throw new SharePublishError("upload-failed", "share registry returned an invalid response");
6649
- }
6650
- const record2 = typeof body === "object" && body !== null ? body : {};
6651
- if (record2.cid !== input.cid || typeof record2.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share registry returned an unexpected CID or retention value");
6652
- return { cid: input.cid, deleteAfter: record2.deleteAfter };
6653
- }
6654
- async function uploadShareBlob(options, input) {
6655
- const result = await (options.uploadBlob ?? ((value) => defaultUpload(options, value)))(input);
6656
- if (result.cid !== input.cid || typeof result.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share uploader returned an invalid result");
6657
- return result;
6658
- }
6659
- async function publishShare(options) {
6660
- const target = options.target ?? { kind: "bearer" };
6661
- assertTarget(target);
6662
- assertSafeFilename(options.filename);
6663
- assertOrigin(options.origin, "share origin");
6664
- if (options.mediaType !== void 0 && options.mediaType.length === 0) throw new SharePublishError("invalid-argument", "mediaType must not be empty");
6665
- const maxBytes = options.maxBytes ?? SHARE_CONTENT_LIMIT;
6666
- if (maxBytes > SHARE_CONTENT_LIMIT) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the maximum 100 MiB limit");
6667
- const content = await snapshotSource(options.source, maxBytes);
6668
- if (content.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
6669
- if (options.allowBinary !== true) {
6670
- try {
6671
- new TextDecoder("utf-8", { fatal: true }).decode(content);
6672
- } catch {
6673
- throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
6674
- }
6675
- }
6676
- const nowMs = options.now?.() ?? Date.now();
6677
- const expiresAt = options.expiresAt ?? new Date(nowMs + DEFAULT_SHARE_LIFETIME_MS);
6678
- if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
6679
- const expiry = expiresAt.toISOString();
6680
- const nodeAudience = options.nodeAudience ?? "did:web:node.tinycloud.xyz";
6681
- const spaceId = options.spaceId ?? "bearer";
6682
- const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
6683
- const path = `shares/${shareId}/${options.filename}`;
6684
- const sessionPrivateKey = import_ed255194.ed25519.utils.randomPrivateKey();
6685
- const senderPrivateKey = import_ed255194.ed25519.utils.randomPrivateKey();
6686
- let contentKey;
6687
- let envelopeKey;
6688
- try {
6689
- const sessionPublicKey = import_ed255194.ed25519.getPublicKey(sessionPrivateKey);
6690
- const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
6691
- const delegation = mintBearerDelegation({
6692
- issuerPrivateKey: senderPrivateKey,
6693
- audienceDid: sessionDid,
6694
- resourceUri: bearerResourceUri(options.origin, spaceId, path),
6695
- expiresAtSeconds: Math.ceil(expiresAt.getTime() / 1e3)
6696
- });
6697
- contentKey = generateKey();
6698
- const sealedContent = await seal(content, contentKey);
6699
- const unsigned = {
6700
- version: 1,
6701
- shareId,
6702
- delegation,
6703
- authorizationTarget: {
6704
- kind: "bearerKey",
6705
- sessionJwk: { kty: "OKP", crv: "Ed25519", x: toBase64Url(sessionPublicKey), d: toBase64Url(sessionPrivateKey) }
6706
- },
6707
- target: { origin: options.origin, nodeAudience, spaceId, resource: { kind: "exact", path } },
6708
- display: { filename: options.filename },
6709
- expiry,
6710
- content: { cid: sealedContent.cid, key: toBase64Url(contentKey) }
6711
- };
6712
- const envelope = signEnvelope(unsigned, senderPrivateKey);
6713
- envelopeKey = generateKey();
6714
- const sealedEnvelope = await seal(new TextEncoder().encode(canonicalize(envelope)), envelopeKey);
6715
- let inlineUrl;
6716
- if (options.inline === true) {
6717
- try {
6718
- inlineUrl = await encodeInlineShareUrl({ origin: options.origin, ciphertext: sealedEnvelope.blob, key32: envelopeKey });
6719
- } catch (error) {
6720
- throw new SharePublishError("inline-too-large", error instanceof Error ? error.message : "inline share is too large");
6721
- }
6722
- }
6723
- const contentUpload = await uploadShareBlob(options, { blob: sealedContent.blob, cid: sealedContent.cid, deleteAfter: expiry, contentLength: sealedContent.blob.byteLength });
6724
- let url;
6725
- let kind;
6726
- let retention = contentUpload.deleteAfter;
6727
- if (options.inline === true) {
6728
- if (inlineUrl === void 0) throw new SharePublishError("inline-too-large", "inline share is too large");
6729
- url = inlineUrl;
6730
- kind = "inline";
6731
- } else {
6732
- const envelopeUpload = await uploadShareBlob(options, { blob: sealedEnvelope.blob, cid: sealedEnvelope.cid, deleteAfter: expiry, contentLength: sealedEnvelope.blob.byteLength });
6733
- url = encodeShareUrl({ origin: options.origin, ciphertextCid: envelopeUpload.cid, key32: envelopeKey });
6734
- kind = "compact";
6735
- retention = envelopeUpload.deleteAfter;
6736
- }
6737
- const metadata = {
6738
- protocol: "tinycloud-share",
6739
- version: 1,
6740
- shareId,
6741
- origin: options.origin,
6742
- target: { kind: "bearer", origin: options.origin, nodeAudience, spaceId },
6743
- resource: { kind: "exact", path },
6744
- actions: ["read"],
6745
- expiresAt: expiry,
6746
- display: { filename: options.filename },
6747
- content: { cid: sealedContent.cid }
6748
- };
6749
- const result = { protocol: "tinycloud-share", version: SHARE_PUBLISH_RESULT_VERSION, link: { kind, cid: sealedEnvelope.cid }, metadata, registryDeleteAfter: retention };
6750
- Object.defineProperty(result, "toJSON", {
6751
- enumerable: false,
6752
- value: () => redactPublishedShare(result)
6753
- });
6754
- Object.defineProperty(result, "url", { enumerable: false, value: url });
6755
- return result;
6756
- } finally {
6757
- sessionPrivateKey.fill(0);
6758
- senderPrivateKey.fill(0);
6759
- contentKey?.fill(0);
6760
- envelopeKey?.fill(0);
6761
- }
6762
- }
6763
6051
 
6764
6052
  // src/addressed.ts
6765
6053
  async function parseAddressedEnvelope(value) {
@@ -7203,8 +6491,8 @@ function targetAuthorizationMethod(target) {
7203
6491
  }
7204
6492
  async function publishTargetShare(input) {
7205
6493
  const target = normalizeShareTarget(input.target);
7206
- if (target.kind === "bearer") return publishShare(input);
7207
6494
  if (input.targetAdapter === void 0) {
6495
+ if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
7208
6496
  return {
7209
6497
  state: "authorization-required",
7210
6498
  method: targetAuthorizationMethod(target)
@@ -7214,9 +6502,9 @@ async function publishTargetShare(input) {
7214
6502
  const chunks = [];
7215
6503
  let size = 0;
7216
6504
  for await (const chunk of input.source) {
7217
- if (!(chunk instanceof Uint8Array)) throw new TypeError("addressed publication source yielded invalid bytes");
6505
+ if (!(chunk instanceof Uint8Array)) throw new TypeError("share publication source yielded invalid bytes");
7218
6506
  size += chunk.byteLength;
7219
- if (size > (input.maxBytes ?? 100 * 1024 * 1024)) throw new TypeError("addressed publication exceeds maxBytes");
6507
+ if (size > (input.maxBytes ?? SHARE_CONTENT_LIMIT)) throw new SharePublishError("max-bytes-exceeded", "share publication exceeds maxBytes");
7220
6508
  chunks.push(chunk.slice());
7221
6509
  }
7222
6510
  const bytes3 = new Uint8Array(size);
@@ -7227,15 +6515,29 @@ async function publishTargetShare(input) {
7227
6515
  }
7228
6516
  return bytes3;
7229
6517
  })();
6518
+ if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
6519
+ if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
6520
+ throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
6521
+ }
6522
+ if (target.kind === "bearer" && input.allowBinary !== true) {
6523
+ try {
6524
+ new TextDecoder("utf-8", { fatal: true }).decode(source);
6525
+ } catch {
6526
+ throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
6527
+ }
6528
+ }
7230
6529
  const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
7231
6530
  const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
7232
6531
  let totalBytes = 0;
7233
6532
  for (const file of files) {
7234
6533
  totalBytes += file.bytes.byteLength;
7235
6534
  if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
7236
- throw new SharePublishError("max-bytes-exceeded", "addressed publication exceeds the combined byte limit");
6535
+ throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
7237
6536
  }
7238
6537
  }
6538
+ const nowMs = input.now?.() ?? Date.now();
6539
+ const expiresAt = input.expiresAt ?? new Date(nowMs + DEFAULT_SHARE_LIFETIME_MS);
6540
+ if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
7239
6541
  return input.targetAdapter.publish({
7240
6542
  source,
7241
6543
  filename: input.filename,
@@ -7244,18 +6546,8 @@ async function publishTargetShare(input) {
7244
6546
  ...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
7245
6547
  ...input.actions === void 0 ? {} : { actions: input.actions },
7246
6548
  target,
7247
- expiresAt: input.expiresAt ?? new Date((input.now?.() ?? Date.now()) + 7 * 24 * 60 * 60 * 1e3),
6549
+ expiresAt,
7248
6550
  origin: input.origin,
7249
- ...input.inline === void 0 ? {} : { inline: input.inline },
7250
- upload: {
7251
- ...input.registryBaseUrl === void 0 ? {} : { registryBaseUrl: input.registryBaseUrl },
7252
- ...input.fetchFn === void 0 ? {} : { fetchFn: input.fetchFn },
7253
- ...input.authorizeUpload === void 0 ? {} : { authorizeUpload: input.authorizeUpload },
7254
- ...input.authorizationOrigin === void 0 ? {} : { authorizationOrigin: input.authorizationOrigin },
7255
- ...input.credentials === void 0 ? {} : { credentials: input.credentials },
7256
- ...input.allowInsecureRegistry === void 0 ? {} : { allowInsecureRegistry: input.allowInsecureRegistry },
7257
- ...input.uploadBlob === void 0 ? {} : { uploadBlob: input.uploadBlob }
7258
- },
7259
6551
  ...input.notify === void 0 ? {} : { notify: input.notify }
7260
6552
  });
7261
6553
  }
@@ -7347,7 +6639,7 @@ function publicationResult(input) {
7347
6639
  protocol: "tinycloud-share",
7348
6640
  version: SHARE_PUBLISH_RESULT_VERSION,
7349
6641
  url: input.url,
7350
- link: { kind: input.options.inline === true ? "inline" : "compact", cid: input.envelopeCid },
6642
+ link: { kind: "policy", cid: input.envelopeCid },
7351
6643
  metadata: {
7352
6644
  protocol: "tinycloud-share",
7353
6645
  version: 1,
@@ -7366,8 +6658,7 @@ function publicationResult(input) {
7366
6658
  enforcerDid: input.enforcerDid,
7367
6659
  envelopeCid: input.envelopeCid,
7368
6660
  shareCid: input.envelopeCid
7369
- },
7370
- registryDeleteAfter: input.retention
6661
+ }
7371
6662
  };
7372
6663
  Object.defineProperty(result, "toJSON", { enumerable: false, value: () => redactPublishedShare(result) });
7373
6664
  Object.defineProperty(result, "url", { enumerable: false, value: input.url });
@@ -7449,40 +6740,89 @@ async function publishAddressedShare(options) {
7449
6740
  if (envelopeSignature.byteLength !== 64) throw new TypeError("v3 envelope signature must be Ed25519");
7450
6741
  const envelope = { ...unsigned, signature: { signerDid: options.authority.ownerDid, algorithm: "Ed25519", value: toBase64Url(envelopeSignature) } };
7451
6742
  shareEnvelopeV3Schema.parse(envelope);
7452
- const envelopeKey = generateKey();
7453
- try {
7454
- const sealed = await seal(textEncoder.encode(canonicalize(envelope)), envelopeKey);
7455
- let url;
7456
- let retention = options.expiresAt.toISOString();
7457
- if (options.inline === true) url = await encodeInlineShareUrl({ origin: options.shareOrigin, ciphertext: sealed.blob, key32: envelopeKey });
7458
- else {
7459
- const uploaded = await uploadShareBlob({ source: new Uint8Array([1]), filename: options.filename, origin: options.shareOrigin, ...options.upload }, { blob: sealed.blob, cid: sealed.cid, deleteAfter: retention, contentLength: sealed.blob.byteLength });
7460
- retention = uploaded.deleteAfter;
7461
- url = encodeShareUrl({ origin: options.shareOrigin, ciphertextCid: uploaded.cid, key32: envelopeKey });
7462
- }
7463
- await options.publishBinding?.({
7464
- version: 3,
7465
- shareCid: sealed.cid,
7466
- shareId: options.shareId,
7467
- policyCid: created.policyCid,
7468
- policyRootCid: policyRoot.cid,
7469
- enforcementRootCid: enforcementRoot.cid,
7470
- contentSourceDigestHex
7471
- });
7472
- const deliveryMaterial = { envelope, sealedEnvelope: toBase64Url(sealed.blob), envelopeKey: toBase64Url(envelopeKey), shareCid: sealed.cid };
7473
- options.onDeliveryMaterial?.(deliveryMaterial);
7474
- return publicationResult({ options, url, envelopeCid: sealed.cid, matcher, policyCid: created.policyCid, policyRootCid: policyRoot.cid, enforcementRootCid: enforcementRoot.cid, enforcerDid: registration.attestedEnforcerBinding.enforcerDid, expiry, retention, deliveryMaterial });
7475
- } finally {
7476
- envelopeKey.fill(0);
7477
- }
6743
+ 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 };
6747
+ options.onDeliveryMaterial?.(deliveryMaterial);
6748
+ return publicationResult({
6749
+ options,
6750
+ url,
6751
+ envelopeCid,
6752
+ matcher,
6753
+ policyCid: created.policyCid,
6754
+ policyRootCid: policyRoot.cid,
6755
+ enforcementRootCid: enforcementRoot.cid,
6756
+ enforcerDid: registration.attestedEnforcerBinding.enforcerDid,
6757
+ expiry,
6758
+ deliveryMaterial
6759
+ });
7478
6760
  }
7479
6761
 
7480
- // src/policy-authority.ts
7481
- var import_ed255196 = require("@noble/curves/ed25519");
6762
+ // src/cache.ts
6763
+ var import_sha2563 = require("@noble/hashes/sha256");
6764
+ function cacheKey(key) {
6765
+ if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
6766
+ return `${key.cid}\0${key.expiresAt}`;
6767
+ }
6768
+ function digest(bytes3) {
6769
+ return (0, import_sha2563.sha256)(bytes3);
6770
+ }
6771
+ function equalBytes(left, right) {
6772
+ if (left.byteLength !== right.byteLength) return false;
6773
+ return left.every((byte, index) => byte === right[index]);
6774
+ }
6775
+ var MemoryShareCache = class {
6776
+ constructor(now = () => /* @__PURE__ */ new Date()) {
6777
+ this.now = now;
6778
+ }
6779
+ entries = /* @__PURE__ */ new Map();
6780
+ async get(key, now = /* @__PURE__ */ new Date()) {
6781
+ const mapKey = cacheKey(key);
6782
+ const entry = this.entries.get(mapKey);
6783
+ if (entry === void 0) return void 0;
6784
+ if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest(entry.bytes))) {
6785
+ this.entries.delete(mapKey);
6786
+ return void 0;
6787
+ }
6788
+ return entry.bytes.slice();
6789
+ }
6790
+ async getByCid(cid2, now = /* @__PURE__ */ new Date()) {
6791
+ const entries = [...this.entries.values()].filter((entry) => entry.key.cid === cid2);
6792
+ for (const entry of entries) {
6793
+ if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest(entry.bytes))) {
6794
+ this.entries.delete(cacheKey(entry.key));
6795
+ continue;
6796
+ }
6797
+ return {
6798
+ key: { ...entry.key },
6799
+ bytes: entry.bytes.slice(),
6800
+ digest: entry.digest.slice(),
6801
+ metadata: { ...entry.metadata }
6802
+ };
6803
+ }
6804
+ return void 0;
6805
+ }
6806
+ async set(key, bytes3, metadata) {
6807
+ if (Date.parse(key.expiresAt) <= this.now().getTime()) return;
6808
+ this.entries.set(cacheKey(key), {
6809
+ key: { cid: key.cid, expiresAt: key.expiresAt },
6810
+ bytes: bytes3.slice(),
6811
+ digest: digest(bytes3),
6812
+ metadata: { ...metadata }
6813
+ });
6814
+ }
6815
+ async delete(key) {
6816
+ this.entries.delete(cacheKey(key));
6817
+ }
6818
+ async clear() {
6819
+ this.entries.clear();
6820
+ }
6821
+ };
7482
6822
 
7483
6823
  // src/owner-policy.ts
7484
- var import_ed255195 = require("@noble/curves/ed25519");
7485
- var import_sha2563 = require("@noble/hashes/sha256");
6824
+ var import_ed255194 = require("@noble/curves/ed25519");
6825
+ var import_sha2564 = require("@noble/hashes/sha256");
7486
6826
 
7487
6827
  // ../../node_modules/multiformats/dist/src/bases/base32.js
7488
6828
  var base322 = rfc46482({
@@ -8036,10 +7376,10 @@ function fromB64(value) {
8036
7376
  throw new Error("base64url decoding is unavailable");
8037
7377
  }
8038
7378
  function cid(bytesValue) {
8039
- return CID2.createV1(code2, create2(SHA256_CODE2, (0, import_sha2563.sha256)(bytesValue))).toString(base322.encoder);
7379
+ return CID2.createV1(code2, create2(SHA256_CODE2, (0, import_sha2564.sha256)(bytesValue))).toString(base322.encoder);
8040
7380
  }
8041
- function digest(bytesValue) {
8042
- return b64((0, import_sha2563.sha256)(bytesValue));
7381
+ function digest2(bytesValue) {
7382
+ return b64((0, import_sha2564.sha256)(bytesValue));
8043
7383
  }
8044
7384
  function didKeyFromEd25519PublicKey2(publicKey) {
8045
7385
  if (publicKey.length !== 32) throw new Error("share key public key must be 32 bytes");
@@ -8113,9 +7453,9 @@ function dagCborEncode(value) {
8113
7453
  }
8114
7454
  function assertObject(value, keys, label) {
8115
7455
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
8116
- const record2 = value;
8117
- if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) throw new Error(`${label} has unknown or missing fields`);
8118
- return record2;
7456
+ const record = value;
7457
+ if (Object.keys(record).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record, key))) throw new Error(`${label} has unknown or missing fields`);
7458
+ return record;
8119
7459
  }
8120
7460
  async function createDelegatedShareKey(options) {
8121
7461
  if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
@@ -8178,7 +7518,7 @@ async function createPolicyEnforcementDelegation(input) {
8178
7518
  async function canonicalOwnerSharePolicy(policy) {
8179
7519
  const text = canonicalize({ domain: POLICY_DOMAIN, policy });
8180
7520
  const policyBytes = new TextEncoder().encode(text);
8181
- return { bytes: policyBytes, cid: cid(policyBytes), digest: digest(policyBytes) };
7521
+ return { bytes: policyBytes, cid: cid(policyBytes), digest: digest2(policyBytes) };
8182
7522
  }
8183
7523
  function computeOwnerShareRegistrationCid(registration) {
8184
7524
  return cid(new TextEncoder().encode(canonicalize(registration)));
@@ -8217,7 +7557,7 @@ function validateOwnerSharePolicyRegistration(value, expected) {
8217
7557
  const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
8218
7558
  const signatureBytes = fromB64(proof.signature);
8219
7559
  const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
8220
- if (signatureBytes.length !== 64 || !import_ed255195.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
7560
+ if (signatureBytes.length !== 64 || !import_ed255194.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
8221
7561
  return { registration, proof };
8222
7562
  }
8223
7563
  function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
@@ -8230,318 +7570,6 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
8230
7570
  return validateOwnerSharePolicyRegistration(value, expected);
8231
7571
  }
8232
7572
 
8233
- // src/policy-authority.ts
8234
- var ENVELOPE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
8235
- function record(value, keys, label) {
8236
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
8237
- const output = value;
8238
- if (Object.keys(output).length !== keys.length || keys.some((key) => !Object.hasOwn(output, key))) throw new Error(`${label} has unknown or missing fields`);
8239
- return output;
8240
- }
8241
- function requiredString(value, label) {
8242
- if (typeof value !== "string" || value.length === 0) throw new Error(`${label} is invalid`);
8243
- return value;
8244
- }
8245
- function stringArray(value, label) {
8246
- if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && item.length > 0)) throw new Error(`${label} is invalid`);
8247
- return value;
8248
- }
8249
- function parseEnforcement(value) {
8250
- const delegation = record(value, ["cid", "dagCbor", "issuerDid", "audienceDid", "facts", "signature"], "policy enforcement delegation");
8251
- const facts = record(delegation.facts, ["ownerDelegationCid", "policyCid", "shareId", "shareKeyDid", "enforcerDid", "nodeAudience", "spaceId", "path", "actions", "contentSourceDigest", "expiresAt"], "policy enforcement facts");
8252
- return {
8253
- cid: requiredString(delegation.cid, "policy enforcement CID"),
8254
- dagCbor: requiredString(delegation.dagCbor, "policy enforcement bytes"),
8255
- issuerDid: requiredString(delegation.issuerDid, "policy enforcement issuer"),
8256
- audienceDid: requiredString(delegation.audienceDid, "policy enforcement audience"),
8257
- facts: {
8258
- ownerDelegationCid: requiredString(facts.ownerDelegationCid, "owner delegation CID"),
8259
- policyCid: requiredString(facts.policyCid, "policy CID"),
8260
- shareId: requiredString(facts.shareId, "share id"),
8261
- shareKeyDid: requiredString(facts.shareKeyDid, "share key DID"),
8262
- enforcerDid: requiredString(facts.enforcerDid, "enforcer DID"),
8263
- nodeAudience: requiredString(facts.nodeAudience, "node audience"),
8264
- spaceId: requiredString(facts.spaceId, "space id"),
8265
- path: requiredString(facts.path, "resource path"),
8266
- actions: stringArray(facts.actions, "policy actions"),
8267
- contentSourceDigest: requiredString(facts.contentSourceDigest, "content source digest"),
8268
- expiresAt: requiredString(facts.expiresAt, "policy expiry")
8269
- },
8270
- signature: requiredString(delegation.signature, "policy enforcement signature")
8271
- };
8272
- }
8273
- async function verifyEnforcement(delegation) {
8274
- try {
8275
- const dagCbor = fromBase64Url(delegation.dagCbor);
8276
- if (await computeCid(dagCbor) !== delegation.cid) return false;
8277
- const expectedDagCbor = dagCborEncode({
8278
- domain: POLICY_ENFORCEMENT_DOMAIN,
8279
- unsigned: {
8280
- type: "TinyCloudSharePolicyEnforcement",
8281
- version: 2,
8282
- issuerDid: delegation.issuerDid,
8283
- audienceDid: delegation.audienceDid,
8284
- facts: delegation.facts
8285
- }
8286
- });
8287
- if (expectedDagCbor.length !== dagCbor.length || expectedDagCbor.some((byte, index) => byte !== dagCbor[index])) return false;
8288
- const signature = fromBase64Url(delegation.signature);
8289
- return signature.length === 64 && import_ed255196.ed25519.verify(
8290
- signature,
8291
- dagCbor,
8292
- ed25519PublicKeyFromDidKey(delegation.issuerDid),
8293
- { zip215: false }
8294
- );
8295
- } catch {
8296
- return false;
8297
- }
8298
- }
8299
- async function verifyOuterEnvelope(value, envelope, policyCid, expectedEnforcerDid, expectedActions) {
8300
- try {
8301
- const hasDecryption = typeof value === "object" && value !== null && Object.hasOwn(value, "decryption");
8302
- const outer = record(value, ["schema", "version", "shareId", "delegationCid", "policyCid", "target", "resource", "actions", ...hasDecryption ? ["decryption"] : [], "contentSource", "contentSourceDigest", "expiresAt", "envelopeCid", "shareCid", "signature"], "outer envelope");
8303
- const target = record(outer.target, ["origin", "nodeAudience", "enforcerDid", "spaceId"], "outer target");
8304
- const signature = record(outer.signature, ["signerDid", "algorithm", "value"], "outer envelope signature");
8305
- const envelopeCid = requiredString(outer.envelopeCid, "outer envelope CID");
8306
- const shareCid = requiredString(outer.shareCid, "outer share CID");
8307
- const { signature: _signature, envelopeCid: _envelopeCid, shareCid: _shareCid, ...identity } = outer;
8308
- if (outer.schema !== "xyz.tinycloud.share/envelope/v2" || outer.version !== 2 || outer.shareId !== envelope.shareId || outer.delegationCid !== envelope.delegationCid || outer.policyCid !== policyCid || target.origin !== envelope.target.origin || target.nodeAudience !== envelope.target.nodeAudience || target.enforcerDid !== expectedEnforcerDid || target.spaceId !== envelope.target.spaceId || canonicalize(outer.resource) !== canonicalize(envelope.resource) || canonicalize(outer.actions) !== canonicalize(expectedActions) || (hasDecryption ? envelope.decryption === void 0 || canonicalize(outer.decryption) !== canonicalize(envelope.decryption) : envelope.decryption !== void 0) || canonicalize(outer.contentSource) !== canonicalize(envelope.contentSource) || outer.contentSourceDigest !== envelope.contentSourceDigest || outer.expiresAt !== envelope.expiry || signature.signerDid !== envelope.signature.signerDid || signature.algorithm !== "Ed25519") return void 0;
8309
- if (envelopeCid !== await computeCid(new TextEncoder().encode(canonicalize(identity)))) return void 0;
8310
- if (shareCid !== await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: envelope.shareId, policyCid, envelopeCid })))) return void 0;
8311
- const unsigned = { ...identity, envelopeCid, shareCid };
8312
- const signatureBytes = fromBase64Url(requiredString(signature.value, "outer signature"));
8313
- if (signatureBytes.length !== 64 || !import_ed255196.ed25519.verify(
8314
- signatureBytes,
8315
- new TextEncoder().encode(`${ENVELOPE_DOMAIN}${canonicalize(unsigned)}`),
8316
- ed25519PublicKeyFromDidKey(envelope.signature.signerDid),
8317
- { zip215: false }
8318
- )) return void 0;
8319
- return { envelopeCid, shareCid };
8320
- } catch {
8321
- return void 0;
8322
- }
8323
- }
8324
- function createRegisteredPolicyAuthority(options) {
8325
- const proofKey = options.nodeProof.publicKey.slice();
8326
- return {
8327
- async resolve({ policyCid, envelope }) {
8328
- try {
8329
- if (envelope.authorizationTarget.kind !== "policy" || envelope.authorizationTarget.policyCid !== policyCid) return void 0;
8330
- const ownerAuthority = envelope.ownerAuthority;
8331
- if (ownerAuthority?.registrationReceipt === void 0) return void 0;
8332
- const enforcement = parseEnforcement(ownerAuthority.enforcementDelegation);
8333
- if (!await verifyEnforcement(enforcement)) return void 0;
8334
- const policyBytes = fromBase64Url(envelope.authorizationTarget.policyBytes);
8335
- const ownerDelegation = {
8336
- delegationCid: envelope.delegationCid,
8337
- signedDagCbor: new Uint8Array(),
8338
- delegation: {
8339
- delegateDID: envelope.signature.signerDid,
8340
- spaceId: envelope.target.spaceId,
8341
- path: envelope.resource.path,
8342
- actions: envelope.actions,
8343
- expiry: new Date(envelope.expiry)
8344
- }
8345
- };
8346
- const receipt = validateOwnerSharePolicyRegistration(ownerAuthority.registrationReceipt, {
8347
- policy: { bytes: policyBytes, cid: policyCid, proof: "" },
8348
- ownerDelegation,
8349
- enforcementDelegation: enforcement,
8350
- contentSourceDigest: envelope.contentSourceDigest,
8351
- nodeProof: { kid: options.nodeProof.kid, publicKey: proofKey }
8352
- });
8353
- const registration = receipt.registration;
8354
- const outer = await verifyOuterEnvelope(ownerAuthority.outerEnvelope, envelope, policyCid, options.expectedTarget.enforcerDid, registration.actions);
8355
- if (outer === void 0 || ownerAuthority.registrationCid !== registration.registrationCid || ownerAuthority.envelopeCid !== outer.envelopeCid || ownerAuthority.shareCid !== outer.shareCid || envelope.authorityMaterialHandle !== registration.registrationCid || envelope.signature.signerDid !== registration.shareKeyDid || registration.target.origin !== options.expectedTarget.origin || registration.target.nodeAudience !== options.expectedTarget.nodeAudience || registration.enforcerDid !== options.expectedTarget.enforcerDid || enforcement.issuerDid !== registration.shareKeyDid || enforcement.audienceDid !== registration.enforcerDid) return void 0;
8356
- return {
8357
- policyCid: registration.policyCid,
8358
- signerDid: registration.shareKeyDid,
8359
- registrationCid: registration.registrationCid,
8360
- shareId: registration.shareId,
8361
- recipientMatcher: registration.recipientMatcher,
8362
- target: registration.target,
8363
- resource: registration.resource,
8364
- actions: registration.actions,
8365
- contentSource: registration.contentSource,
8366
- contentSourceDigest: registration.contentSourceDigest,
8367
- delegationCid: registration.ownerDelegationCid,
8368
- authorityMaterialHandle: registration.registrationCid,
8369
- authorityMaterialDigest: envelope.authorityMaterialDigest,
8370
- expiresAt: registration.expiresAt
8371
- };
8372
- } catch {
8373
- return void 0;
8374
- }
8375
- }
8376
- };
8377
- }
8378
-
8379
- // src/cache.ts
8380
- var import_sha2564 = require("@noble/hashes/sha256");
8381
- function cacheKey(key) {
8382
- if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
8383
- return `${key.cid}\0${key.expiresAt}`;
8384
- }
8385
- function digest2(bytes3) {
8386
- return (0, import_sha2564.sha256)(bytes3);
8387
- }
8388
- function equalBytes(left, right) {
8389
- if (left.byteLength !== right.byteLength) return false;
8390
- return left.every((byte, index) => byte === right[index]);
8391
- }
8392
- var MemoryShareCache = class {
8393
- constructor(now = () => /* @__PURE__ */ new Date()) {
8394
- this.now = now;
8395
- }
8396
- entries = /* @__PURE__ */ new Map();
8397
- async get(key, now = /* @__PURE__ */ new Date()) {
8398
- const mapKey = cacheKey(key);
8399
- const entry = this.entries.get(mapKey);
8400
- if (entry === void 0) return void 0;
8401
- if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest2(entry.bytes))) {
8402
- this.entries.delete(mapKey);
8403
- return void 0;
8404
- }
8405
- return entry.bytes.slice();
8406
- }
8407
- async getByCid(cid2, now = /* @__PURE__ */ new Date()) {
8408
- const entries = [...this.entries.values()].filter((entry) => entry.key.cid === cid2);
8409
- for (const entry of entries) {
8410
- if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest2(entry.bytes))) {
8411
- this.entries.delete(cacheKey(entry.key));
8412
- continue;
8413
- }
8414
- return {
8415
- key: { ...entry.key },
8416
- bytes: entry.bytes.slice(),
8417
- digest: entry.digest.slice(),
8418
- metadata: { ...entry.metadata }
8419
- };
8420
- }
8421
- return void 0;
8422
- }
8423
- async set(key, bytes3, metadata) {
8424
- if (Date.parse(key.expiresAt) <= this.now().getTime()) return;
8425
- this.entries.set(cacheKey(key), {
8426
- key: { cid: key.cid, expiresAt: key.expiresAt },
8427
- bytes: bytes3.slice(),
8428
- digest: digest2(bytes3),
8429
- metadata: { ...metadata }
8430
- });
8431
- }
8432
- async delete(key) {
8433
- this.entries.delete(cacheKey(key));
8434
- }
8435
- async clear() {
8436
- this.entries.clear();
8437
- }
8438
- };
8439
-
8440
- // src/delivery.ts
8441
- var import_ed255197 = require("@noble/curves/ed25519");
8442
- var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
8443
- function isCanonicalHttpsOrigin2(value) {
8444
- let url;
8445
- try {
8446
- url = new URL(value);
8447
- } catch {
8448
- return false;
8449
- }
8450
- return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
8451
- }
8452
- var AUTHORIZATION_KEYS = [
8453
- "type",
8454
- "version",
8455
- "jti",
8456
- "shareCid",
8457
- "shareId",
8458
- "registrationCid",
8459
- "delegationCid",
8460
- "enforcementDelegationCid",
8461
- "envelopeCid",
8462
- "policyCid",
8463
- "nodeAudience",
8464
- "targetOrigin",
8465
- "openCredentialsAudience",
8466
- "holder",
8467
- "recipientMatcher",
8468
- "deliveryEmail",
8469
- "shareUrl",
8470
- "returnOrigin",
8471
- "documentName",
8472
- "senderDid",
8473
- "senderTrust",
8474
- "contentSource",
8475
- "contentSourceDigest",
8476
- "shareExpiresAt",
8477
- "issuedAt",
8478
- "reportAbuseToken",
8479
- "actions",
8480
- "resource",
8481
- "authorityMaterialHandle",
8482
- "authorityMaterialDigest",
8483
- "requestBodyDigest",
8484
- "idempotencyKey",
8485
- "expiresAt",
8486
- "dataAuthority"
8487
- ];
8488
- function assertObject2(value, keys, label) {
8489
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
8490
- const record2 = value;
8491
- if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) throw new Error(`${label} has unknown or missing fields`);
8492
- return record2;
8493
- }
8494
- function fromB642(value) {
8495
- if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
8496
- if (typeof atob === "function") {
8497
- const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
8498
- return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
8499
- }
8500
- if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
8501
- throw new Error("base64url decoding is unavailable");
8502
- }
8503
- function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
8504
- let value;
8505
- try {
8506
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
8507
- } catch {
8508
- throw new Error("share delivery authorization response is not valid UTF-8 JSON");
8509
- }
8510
- const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
8511
- const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
8512
- const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
8513
- const request = expected.request;
8514
- if (authorization.type !== "TinyCloudShareDeliveryAuthorization" || authorization.version !== 2 || authorization.dataAuthority !== false || authorization.jti !== request.jti || authorization.shareCid !== request.shareCid || authorization.shareId !== request.shareId || authorization.registrationCid !== request.registrationCid || authorization.delegationCid !== request.delegationCid || authorization.enforcementDelegationCid !== request.enforcementDelegationCid || authorization.envelopeCid !== request.envelopeCid || authorization.policyCid !== request.policyCid || authorization.deliveryEmail !== request.recipientEmail || authorization.shareUrl !== request.shareUrl || authorization.documentName !== request.documentName || authorization.requestBodyDigest !== request.requestBodyDigest || authorization.expiresAt !== request.expiresAt || authorization.idempotencyKey !== request.idempotencyKey || authorization.reportAbuseToken !== request.jti || authorization.authorityMaterialHandle !== request.registrationCid) {
8515
- throw new Error("share delivery authorization is not bound to the submitted request");
8516
- }
8517
- const now = Date.now();
8518
- const issuedAt = typeof authorization.issuedAt === "string" ? Date.parse(authorization.issuedAt) : Number.NaN;
8519
- const expiresAt = typeof authorization.expiresAt === "string" ? Date.parse(authorization.expiresAt) : Number.NaN;
8520
- if (!Number.isFinite(issuedAt) || issuedAt > now + 6e4 || issuedAt > expiresAt) throw new Error("share delivery authorization issuedAt is invalid");
8521
- if (!Number.isFinite(expiresAt) || expiresAt <= now) throw new Error("share delivery authorization is expired");
8522
- if (new Date(expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
8523
- if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin2(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
8524
- throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
8525
- }
8526
- if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
8527
- if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
8528
- const encodedKid = expected.nodeProof.publicKey;
8529
- const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
8530
- if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
8531
- const signatureBytes = fromB642(proof.signature);
8532
- const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalize(authorization)}`);
8533
- if (signatureBytes.length !== 64 || !import_ed255197.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
8534
- return { authorization, proof };
8535
- }
8536
- function shareDeliveryTrustedKid(publicKey) {
8537
- if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
8538
- const prefixed = new Uint8Array(34);
8539
- prefixed.set([237, 1]);
8540
- prefixed.set(publicKey, 2);
8541
- const did = `did:key:${base58btc2.encode(prefixed)}`;
8542
- return `${did}#${did.slice("did:key:".length)}`;
8543
- }
8544
-
8545
7573
  // src/history.ts
8546
7574
  var MemoryEncryptedShareHistoryStorage = class {
8547
7575
  values = [];
@@ -8591,8 +7619,8 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
8591
7619
  }
8592
7620
  var MemorySenderShareRecordStorage = class {
8593
7621
  records = /* @__PURE__ */ new Map();
8594
- async put(record2) {
8595
- this.records.set(record2.shareId, record2);
7622
+ async put(record) {
7623
+ this.records.set(record.shareId, record);
8596
7624
  }
8597
7625
  async list() {
8598
7626
  return [...this.records.values()];
@@ -8629,7 +7657,7 @@ var SenderShareStore = class {
8629
7657
  async save(receipt, key) {
8630
7658
  const registration = receipt.registration;
8631
7659
  if (registration.shareKeyDid !== key.did) throw new Error("share key does not match the registered receipt");
8632
- const record2 = {
7660
+ const record = {
8633
7661
  shareId: registration.shareId,
8634
7662
  registrationCid: registration.registrationCid,
8635
7663
  policyCid: registration.policyCid,
@@ -8646,15 +7674,15 @@ var SenderShareStore = class {
8646
7674
  registeredAt: registration.registeredAt,
8647
7675
  expiresAt: registration.expiresAt
8648
7676
  };
8649
- await this.records.put(record2);
8650
- await this.keys.put(record2.shareId, { cryptoKey: key.cryptoKey, publicKey: key.publicKey, extractable: key.extractable });
8651
- return record2;
7677
+ await this.records.put(record);
7678
+ await this.keys.put(record.shareId, { cryptoKey: key.cryptoKey, publicKey: key.publicKey, extractable: key.extractable });
7679
+ return record;
8652
7680
  }
8653
7681
  /** List shares created in any session, most recent first. */
8654
7682
  async list(options) {
8655
7683
  const now = this.now().getTime();
8656
7684
  const records = await this.records.list();
8657
- return records.filter((record2) => (options?.includeRevoked ?? false) || record2.revokedAt === void 0).filter((record2) => (options?.includeExpired ?? false) || Date.parse(record2.expiresAt) > now).sort((left, right) => Date.parse(right.registeredAt) - Date.parse(left.registeredAt));
7685
+ return records.filter((record) => (options?.includeRevoked ?? false) || record.revokedAt === void 0).filter((record) => (options?.includeExpired ?? false) || Date.parse(record.expiresAt) > now).sort((left, right) => Date.parse(right.registeredAt) - Date.parse(left.registeredAt));
8658
7686
  }
8659
7687
  async get(shareId) {
8660
7688
  return this.records.get(shareId);
@@ -8677,17 +7705,17 @@ var SenderShareStore = class {
8677
7705
  * returned CID, then call {@link markRevoked} once that succeeds.
8678
7706
  */
8679
7707
  async revocationTarget(shareId, scope) {
8680
- const record2 = await this.records.get(shareId);
8681
- if (record2 === void 0) throw new Error(`Unknown share: ${shareId}`);
8682
- const delegationCid = scope === "direct" ? record2.enforcementDelegationCid : record2.ownerDelegationCid;
7708
+ const record = await this.records.get(shareId);
7709
+ if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
7710
+ const delegationCid = scope === "direct" ? record.enforcementDelegationCid : record.ownerDelegationCid;
8683
7711
  if (delegationCid === void 0) throw new Error("share has no node-enforced delegation");
8684
7712
  return delegationCid;
8685
7713
  }
8686
7714
  /** Mark a share revoked locally after the caller's node-side revoke call succeeds. */
8687
7715
  async markRevoked(shareId, revokedAt = this.now().toISOString()) {
8688
- const record2 = await this.records.get(shareId);
8689
- if (record2 === void 0) throw new Error(`Unknown share: ${shareId}`);
8690
- const revoked = { ...record2, revokedAt };
7716
+ const record = await this.records.get(shareId);
7717
+ if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
7718
+ const revoked = { ...record, revokedAt };
8691
7719
  await this.records.put(revoked);
8692
7720
  return revoked;
8693
7721
  }
@@ -8703,9 +7731,9 @@ var EncryptedSenderShareHistory = class {
8703
7731
  this.key = key;
8704
7732
  this.now = now;
8705
7733
  }
8706
- async put(record2) {
7734
+ async put(record) {
8707
7735
  const iv = crypto.getRandomValues(new Uint8Array(12));
8708
- const payload = new TextEncoder().encode(JSON.stringify(record2));
7736
+ const payload = new TextEncoder().encode(JSON.stringify(record));
8709
7737
  const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, this.key, payload));
8710
7738
  const encoded = new Uint8Array(iv.length + ciphertext.length);
8711
7739
  encoded.set(iv);
@@ -8738,8 +7766,8 @@ var ShareNotifyError = class extends Error {
8738
7766
  this.name = "ShareNotifyError";
8739
7767
  }
8740
7768
  };
8741
- function recipientMatchesShareRecord(record2, recipient) {
8742
- const matcher = record2.recipientMatcher;
7769
+ function recipientMatchesShareRecord(record, recipient) {
7770
+ const matcher = record.recipientMatcher;
8743
7771
  if (matcher.kind === "bearer" || matcher.kind === "recipientDid") return false;
8744
7772
  if (matcher.kind === "exactEmail") return matcher.value === recipient;
8745
7773
  const at = recipient.lastIndexOf("@");
@@ -8778,59 +7806,66 @@ async function defaultIdempotencyKey(shareId, recipient) {
8778
7806
  }
8779
7807
 
8780
7808
  // src/lifecycle.ts
8781
- function targetKind2(record2) {
8782
- if (record2.targetKind !== void 0) return record2.targetKind;
8783
- return record2.recipientMatcher.kind === "exactEmail" ? "email" : record2.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record2.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
7809
+ function targetKind2(record) {
7810
+ if (record.targetKind !== void 0) return record.targetKind;
7811
+ return record.recipientMatcher.kind === "exactEmail" ? "email" : record.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
7812
+ }
7813
+ function policyNodeAudience(record) {
7814
+ const envelope = record.deliveryMaterial?.envelope;
7815
+ if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record.target.nodeAudience;
7816
+ const binding = envelope.attestedEnforcerBinding;
7817
+ if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record.target.nodeAudience;
7818
+ const nodeAudience = binding.nodeAudience;
7819
+ return typeof nodeAudience === "string" ? nodeAudience : record.target.nodeAudience;
8784
7820
  }
8785
7821
  async function revokeShare(input) {
8786
7822
  const target = targetKind2(input.record);
8787
- if (target === "bearer") return { state: "retention-only", target, reason: "bearer-capability-cannot-be-revoked" };
8788
7823
  if (input.adapter === void 0) return { state: "unsupported", target, reason: "node revocation authority is required", code: "unsupported-target" };
8789
7824
  const scope = input.scope ?? "direct";
8790
7825
  const delegationCid = scope === "ancestor" ? input.record.ownerDelegationCid : input.record.enforcementDelegationCid;
8791
7826
  if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
8792
- await input.adapter.revokeDelegation({ delegationCid, scope });
7827
+ if (target === "bearer") {
7828
+ if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
7829
+ await input.adapter.revokeDelegation({ delegationCid, scope });
7830
+ } else {
7831
+ if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
7832
+ if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
7833
+ await input.adapter.revokePolicyRoot({
7834
+ rootCid: delegationCid,
7835
+ targetRole: scope === "ancestor" ? "policy-authority" : "policy-enforcement",
7836
+ ownerDid: input.record.ownerDid,
7837
+ nodeOrigin: input.record.target.origin,
7838
+ nodeAudience: policyNodeAudience(input.record)
7839
+ });
7840
+ }
8793
7841
  const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
8794
7842
  if (input.records !== void 0) await input.records.put({ ...input.record, revokedAt });
8795
7843
  return { state: "revoked", target, delegationCid, revokedAt };
8796
7844
  }
8797
- function redactRecord(record2, revealLink, link) {
8798
- const matcher = record2.recipientMatcher;
8799
- const revealedLink = revealLink ? link ?? record2.link : void 0;
7845
+ function redactRecord(record, revealLink, link) {
7846
+ const matcher = record.recipientMatcher;
7847
+ const revealedLink = revealLink ? link ?? record.link : void 0;
8800
7848
  return {
8801
- shareId: record2.shareId,
7849
+ shareId: record.shareId,
8802
7850
  target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
8803
7851
  ...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
8804
- expiresAt: record2.expiresAt,
8805
- revoked: record2.revokedAt !== void 0,
7852
+ expiresAt: record.expiresAt,
7853
+ revoked: record.revokedAt !== void 0,
8806
7854
  ...revealedLink === void 0 ? {} : { link: revealedLink }
8807
7855
  };
8808
7856
  }
8809
7857
  async function listShares(storage) {
8810
7858
  const records = await storage.list();
8811
- return records.map((record2) => redactRecord(record2, false));
7859
+ return records.map((record) => redactRecord(record, false));
8812
7860
  }
8813
7861
  async function showShare(input) {
8814
- const record2 = await input.storage.get(input.shareId);
8815
- if (record2 === void 0) throw new Error("share not found");
8816
- return redactRecord(record2, input.revealLink === true, input.link);
8817
- }
8818
-
8819
- // src/legacy.ts
8820
- function isLegacyShareLink(value) {
8821
- return value.startsWith("tc1:");
8822
- }
8823
- async function receiveLegacyShare(link, reader) {
8824
- if (!isLegacyShareLink(link)) throw new Error("legacy link must use tc1:");
8825
- return reader.read(link);
8826
- }
8827
- async function migrateShare(input) {
8828
- const value = await receiveLegacyShare(input.link, input.reader);
8829
- return { protocol: "tinycloud-share", version: 1, legacy: true, value, migrated: await input.publish(value) };
7862
+ const record = await input.storage.get(input.shareId);
7863
+ if (record === void 0) throw new Error("share not found");
7864
+ return redactRecord(record, input.revealLink === true, input.link);
8830
7865
  }
8831
7866
 
8832
7867
  // src/recipient.ts
8833
- var import_ed255198 = require("@noble/curves/ed25519");
7868
+ var import_ed255195 = require("@noble/curves/ed25519");
8834
7869
  var import_sha2565 = require("@noble/hashes/sha256");
8835
7870
 
8836
7871
  // src/protocol.ts
@@ -9006,6 +8041,15 @@ function verifyV3PolicyAuthorization(input) {
9006
8041
  function hex3(value) {
9007
8042
  return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
9008
8043
  }
8044
+ function matchesInitialCiphertextDigest(bytes3, expected) {
8045
+ if (typeof expected !== "string" || !LOWER_SHA256_HEX.test(expected)) return false;
8046
+ const actual = (0, import_sha2565.sha256)(bytes3);
8047
+ let mismatch = 0;
8048
+ for (let index = 0; index < actual.length; index += 1) {
8049
+ mismatch |= actual[index] ^ Number.parseInt(expected.slice(index * 2, index * 2 + 2), 16);
8050
+ }
8051
+ return mismatch === 0;
8052
+ }
9009
8053
  function canonicalHashHex(value) {
9010
8054
  return hex3((0, import_sha2565.sha256)(new TextEncoder().encode(canonicalize(value))));
9011
8055
  }
@@ -9032,12 +8076,12 @@ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
9032
8076
  throw new Error("encrypted content envelope is malformed");
9033
8077
  }
9034
8078
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
9035
- const record2 = value;
8079
+ const record = value;
9036
8080
  const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
9037
- if (Object.keys(record2).some((key) => !allowed.includes(key)) || record2.v !== 1 || record2.networkId !== expected.encryptionNetwork || record2.alg !== "x25519-aes256gcm/v1" || record2.keyVersion !== expected.contentSource.keyVersion || typeof record2.encryptedSymmetricKey !== "string" || typeof record2.encryptedSymmetricKeyHash !== "string" || record2.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record2.encryptedSymmetricKeyHash !== canonicalHashHex(record2.encryptedSymmetricKey) || typeof record2.ciphertext !== "string" || record2.metadata !== void 0 && (record2.metadata === null || typeof record2.metadata !== "object" || Array.isArray(record2.metadata) || Object.values(record2.metadata).some((entry) => typeof entry !== "string"))) {
8081
+ if (Object.keys(record).some((key) => !allowed.includes(key)) || record.v !== 1 || record.networkId !== expected.encryptionNetwork || record.alg !== "x25519-aes256gcm/v1" || record.keyVersion !== expected.contentSource.keyVersion || typeof record.encryptedSymmetricKey !== "string" || typeof record.encryptedSymmetricKeyHash !== "string" || record.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record.encryptedSymmetricKeyHash !== canonicalHashHex(record.encryptedSymmetricKey) || typeof record.ciphertext !== "string" || record.metadata !== void 0 && (record.metadata === null || typeof record.metadata !== "object" || Array.isArray(record.metadata) || Object.values(record.metadata).some((entry) => typeof entry !== "string"))) {
9038
8082
  throw new Error("encrypted content envelope binding is invalid");
9039
8083
  }
9040
- return record2;
8084
+ return record;
9041
8085
  }
9042
8086
  var ShareRecipientClient = class {
9043
8087
  constructor(options) {
@@ -9149,9 +8193,10 @@ var ShareRecipientClient = class {
9149
8193
  const envelope = this.options.envelope;
9150
8194
  const signer = this.nativeSigner ?? this.signer;
9151
8195
  if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || this.v3NodeAudience === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
8196
+ if (!matchesInitialCiphertextDigest(bytes3, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
9152
8197
  const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
9153
8198
  const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
9154
- const receiverPublicKey = toBase64(import_ed255198.x25519.getPublicKey(receiverPrivateKey));
8199
+ const receiverPublicKey = toBase64(import_ed255195.x25519.getPublicKey(receiverPrivateKey));
9155
8200
  const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
9156
8201
  const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: this.v3NodeAudience, networkId: encrypted.networkId, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
9157
8202
  const bodyHash = hex3((0, import_sha2565.sha256)(new TextEncoder().encode(canonicalize(body))));
@@ -9178,10 +8223,10 @@ var ShareRecipientClient = class {
9178
8223
  const unsigned = { ...value };
9179
8224
  delete unsigned.nodeSignature;
9180
8225
  const signature = fromBase64(value.nodeSignature, "v3 decrypt response signature");
9181
- if (signature.length !== 64 || !import_ed255198.ed25519.verify(signature, new TextEncoder().encode(canonicalize(unsigned)), ed25519PublicKeyFromDidKey(body.targetNode), { zip215: false })) throw new Error("v3 decrypt response signature is invalid");
8226
+ if (signature.length !== 64 || !import_ed255195.ed25519.verify(signature, new TextEncoder().encode(canonicalize(unsigned)), ed25519PublicKeyFromDidKey(body.targetNode), { zip215: false })) throw new Error("v3 decrypt response signature is invalid");
9182
8227
  const wrapped = fromBase64(value.wrappedKey, "v3 wrapped content key");
9183
8228
  if (wrapped.length < 61 || wrapped[32] !== 1) throw new Error("v3 wrapped content key is malformed");
9184
- const shared = import_ed255198.x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
8229
+ const shared = import_ed255195.x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
9185
8230
  const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(33));
9186
8231
  shared.fill(0);
9187
8232
  if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
@@ -9265,10 +8310,10 @@ function createAddressedAuthorization(input) {
9265
8310
  const envelopeAction = nativeAction("read");
9266
8311
  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;
9267
8312
  const detached = object2(wrapper.detached, "share read detached proof");
9268
- if (detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
8313
+ if (input.trustedNode === void 0 || detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
9269
8314
  const unsigned = { ...response };
9270
8315
  delete unsigned.proof;
9271
- return import_ed255198.ed25519.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
8316
+ return import_ed255195.ed25519.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
9272
8317
  } catch {
9273
8318
  return false;
9274
8319
  }
@@ -9300,6 +8345,37 @@ async function claimShare(input) {
9300
8345
  async function resumeShareAuthorization(input) {
9301
8346
  return authorizeShare(input);
9302
8347
  }
8348
+
8349
+ // src/native.ts
8350
+ var NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
8351
+ function canonicalViewerUrl(viewerOrigin) {
8352
+ const url = new URL(viewerOrigin);
8353
+ if (url.protocol !== "https:" || url.origin !== viewerOrigin || url.pathname !== "/" || url.search || url.hash) throw new TypeError("viewer origin must be a canonical HTTPS origin");
8354
+ url.pathname = "/viewer";
8355
+ return url;
8356
+ }
8357
+ async function createNativeShare(sharing, input) {
8358
+ if (!input.path || input.path.startsWith("/") || input.path.endsWith("/") || input.path.includes("..") || input.path.includes("//")) throw new TypeError("native share path must be one canonical TinyCloud KV key");
8359
+ const generated = await sharing.generate({ path: input.path, actions: ["tinycloud.kv/get"], expiry: input.expiresAt });
8360
+ if (generated.ok !== true) throw new Error(typeof generated.error?.message === "string" ? generated.error.message : "TinyCloud sharing service rejected delegation generation");
8361
+ if (typeof generated.data?.token !== "string" || typeof generated.data.delegation?.cid !== "string" || !(generated.data.expiresAt instanceof Date)) throw new Error("TinyCloud sharing service returned incomplete delegation metadata");
8362
+ const delegation = sharing.decodeLink(generated.data.token);
8363
+ if (typeof delegation.spaceId !== "string" || delegation.spaceId.length === 0 || typeof delegation.path !== "string" || delegation.path.length === 0) throw new Error("TinyCloud sharing service returned a non-canonical delegation authority");
8364
+ const url = canonicalViewerUrl(input.viewerOrigin);
8365
+ url.hash = `${NATIVE_SHARE_FRAGMENT_PARAMETER}=${encodeURIComponent(generated.data.token)}`;
8366
+ return { url: url.toString(), delegationCid: generated.data.delegation.cid, expiresAt: generated.data.expiresAt, spaceId: delegation.spaceId };
8367
+ }
8368
+ function parseNativeShareUrl(value) {
8369
+ const url = new URL(value);
8370
+ if (url.search || url.pathname !== "/viewer") throw new TypeError("native shares must carry tc1 only in the /viewer URL fragment");
8371
+ const fragment = new URLSearchParams(url.hash.slice(1));
8372
+ const token = fragment.get(NATIVE_SHARE_FRAGMENT_PARAMETER);
8373
+ if (!token || fragment.size !== 1) throw new TypeError("missing native share fragment");
8374
+ return token;
8375
+ }
8376
+ async function openNativeShare(sharing, link) {
8377
+ return sharing.receive(parseNativeShareUrl(link), { autoSubdelegate: false, useSessionKey: false });
8378
+ }
9303
8379
  // Annotate the CommonJS export names for ESM import in node:
9304
8380
  0 && (module.exports = {
9305
8381
  DEFAULT_MAX_CONTENT_BLOB_BYTES,
@@ -9311,12 +8387,11 @@ async function resumeShareAuthorization(input) {
9311
8387
  MemorySenderShareKeyStorage,
9312
8388
  MemorySenderShareRecordStorage,
9313
8389
  MemoryShareCache,
8390
+ NATIVE_SHARE_FRAGMENT_PARAMETER,
9314
8391
  OWNER_SHARE_REGISTRATION_DOMAIN,
9315
8392
  SHARE_CONTENT_LIMIT,
9316
- SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
9317
8393
  SHARE_PUBLISH_RESULT_VERSION,
9318
8394
  SHARE_RESULT_VERSION,
9319
- SHARE_SEALED_OVERHEAD,
9320
8395
  SHARE_V2_PROTOCOL,
9321
8396
  SenderShareStore,
9322
8397
  ShareNotifyError,
@@ -9332,23 +8407,21 @@ async function resumeShareAuthorization(input) {
9332
8407
  computeOwnerShareRegistrationCid,
9333
8408
  createAddressedAuthorization,
9334
8409
  createDelegatedShareKey,
8410
+ createNativeShare,
9335
8411
  createPolicyEnforcementDelegation,
9336
- createRegisteredPolicyAuthority,
9337
8412
  createShareV2HolderBindingArtifact,
9338
8413
  fromBase64Url,
9339
8414
  historyRecordForPublishedShare,
9340
8415
  inspectShare,
9341
- isLegacyShareLink,
9342
8416
  listShares,
9343
- migrateShare,
9344
8417
  normalizeShareTarget,
9345
8418
  notifyShare,
8419
+ openNativeShare,
9346
8420
  parseAddressedEnvelope,
8421
+ parseNativeShareUrl,
9347
8422
  publishAddressedShare,
9348
8423
  publishPolicyShare,
9349
- publishShare,
9350
8424
  publishTargetShare,
9351
- receiveLegacyShare,
9352
8425
  receiveShare,
9353
8426
  recipientMatchesShareRecord,
9354
8427
  redactPublishedShare,
@@ -9357,14 +8430,11 @@ async function resumeShareAuthorization(input) {
9357
8430
  restoreDelegatedShareKey,
9358
8431
  resumeShareAuthorization,
9359
8432
  revokeShare,
9360
- shareDeliveryTrustedKid,
9361
8433
  showShare,
9362
8434
  targetAuthorizationMethod,
9363
8435
  toBase64Url,
9364
8436
  toShareErrorInfo,
9365
8437
  validateOwnerSharePolicyRegistration,
9366
- validateOwnerSharePolicyRegistrationBytes,
9367
- validateShareDeliveryAuthorizationBytes,
9368
- verifyBearerEnvelope
8438
+ validateOwnerSharePolicyRegistrationBytes
9369
8439
  });
9370
8440
  //# sourceMappingURL=index.cjs.map