@tinycloud/cli 0.9.1-beta.9 → 1.0.0-beta.10

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.
@@ -319,9 +319,9 @@ function capSpecFromAuthMeta(resource, action) {
319
319
  if (nextSlash <= 0) return void 0;
320
320
  const serviceShort = rest.slice(0, nextSlash);
321
321
  const path = rest.slice(nextSlash + 1);
322
- const actionName2 = action.includes("/") ? action.slice(action.indexOf("/") + 1) : action;
322
+ const actionName = action.includes("/") ? action.slice(action.indexOf("/") + 1) : action;
323
323
  const spaceName = spaceUri.startsWith("tinycloud:") ? spaceUri.slice(spaceUri.lastIndexOf(":") + 1) : spaceUri;
324
- return `tinycloud.${serviceShort}:${spaceName}:${path}:${actionName2}`;
324
+ return `tinycloud.${serviceShort}:${spaceName}:${path}:${actionName}`;
325
325
  }
326
326
  function buildNetworkHint() {
327
327
  const readHost = (name2) => {
@@ -13307,7 +13307,7 @@ var init_dist2 = __esm({
13307
13307
  */
13308
13308
  getFullPath(key, prefixOverride) {
13309
13309
  const prefix = prefixOverride ?? this._config.prefix ?? "";
13310
- return prefix ? `${prefix}/${key}` : key;
13310
+ return prefix ? key === "" ? prefix : `${prefix}/${key}` : key;
13311
13311
  }
13312
13312
  /**
13313
13313
  * Get the host URL.
@@ -17609,17 +17609,6 @@ function fromBase64Url(text) {
17609
17609
  function utf8Bytes(text) {
17610
17610
  return new TextEncoder().encode(text);
17611
17611
  }
17612
- function didKeyFromEd25519PublicKey(publicKey) {
17613
- if (publicKey.length !== PUBLIC_KEY_LENGTH) {
17614
- throw new TypeError(
17615
- `ed25519 public key must be ${PUBLIC_KEY_LENGTH} bytes, got ${publicKey.length}`
17616
- );
17617
- }
17618
- const prefixed = new Uint8Array(ED25519_MULTICODEC_PREFIX.length + publicKey.length);
17619
- prefixed.set(ED25519_MULTICODEC_PREFIX, 0);
17620
- prefixed.set(publicKey, ED25519_MULTICODEC_PREFIX.length);
17621
- return `did:key:${base58btc.encode(prefixed)}`;
17622
- }
17623
17612
  function ed25519PublicKeyFromDidKey(did) {
17624
17613
  if (!did.startsWith("did:key:")) {
17625
17614
  throw new TypeError(`not a did:key: ${did}`);
@@ -17631,15 +17620,6 @@ function ed25519PublicKeyFromDidKey(did) {
17631
17620
  }
17632
17621
  return prefixed.slice(ED25519_MULTICODEC_PREFIX.length);
17633
17622
  }
17634
- function getBearerSessionJwk(envelope) {
17635
- const target = envelope.authorizationTarget;
17636
- if (target.kind !== "bearerKey") {
17637
- throw new TypeError(
17638
- `envelope authorizationTarget is "${target.kind}", not "bearerKey"`
17639
- );
17640
- }
17641
- return target.sessionJwk;
17642
- }
17643
17623
  function decodeBase64UrlOrNull(value) {
17644
17624
  try {
17645
17625
  return fromBase64Url(value);
@@ -17697,7 +17677,7 @@ function validateV3Invariants(value, ctx) {
17697
17677
  if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
17698
17678
  ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
17699
17679
  }
17700
- if (value.attestedEnforcerBinding.enforcerDid !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
17680
+ if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
17701
17681
  ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
17702
17682
  }
17703
17683
  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) {
@@ -17712,273 +17692,6 @@ function validateV3Invariants(value, ctx) {
17712
17692
  ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
17713
17693
  }
17714
17694
  }
17715
- function decodeJsonSegment(segment) {
17716
- try {
17717
- const bytes3 = fromBase64Url(segment);
17718
- return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes3));
17719
- } catch {
17720
- return null;
17721
- }
17722
- }
17723
- function isJsonObject(value) {
17724
- return typeof value === "object" && value !== null && !Array.isArray(value);
17725
- }
17726
- function parseCapability(value) {
17727
- if (!isJsonObject(value)) return null;
17728
- const withUri = value["with"];
17729
- const can = value["can"];
17730
- if (typeof withUri !== "string" || withUri.length === 0) return null;
17731
- if (typeof can !== "string" || can.length === 0) return null;
17732
- return { with: withUri, can };
17733
- }
17734
- function parseCanonicalResourceUri(uri) {
17735
- const match = /^(https:\/\/[^/\\]+)\/(.*)$/.exec(uri);
17736
- if (match === null) return null;
17737
- const [, origin, path] = match;
17738
- if (!isCanonicalHttpsOrigin(origin)) return null;
17739
- if (!isCanonicalResourcePath(path)) return null;
17740
- return { origin, segments: path.split("/") };
17741
- }
17742
- function resourceUriCovers(granted, target) {
17743
- const targetParsed = parseCanonicalResourceUri(target);
17744
- if (targetParsed === null) return false;
17745
- const wildcard = granted.endsWith("/*");
17746
- const grantedParsed = parseCanonicalResourceUri(
17747
- wildcard ? granted.slice(0, -2) : granted
17748
- );
17749
- if (grantedParsed === null) return false;
17750
- if (grantedParsed.origin !== targetParsed.origin) return false;
17751
- const grantedSegments = grantedParsed.segments;
17752
- const targetSegments = targetParsed.segments;
17753
- const prefixMatches = grantedSegments.every(
17754
- (segment, index) => segment === targetSegments[index]
17755
- );
17756
- if (!prefixMatches) return false;
17757
- return wildcard ? targetSegments.length > grantedSegments.length : targetSegments.length === grantedSegments.length;
17758
- }
17759
- function bearerResourceUri(origin, spaceId, path) {
17760
- return `${origin}/${spaceId}/${path}`;
17761
- }
17762
- function requiredResourceUri(envelope) {
17763
- const { origin, spaceId, resource } = envelope.target;
17764
- return bearerResourceUri(origin, spaceId, resource.path);
17765
- }
17766
- function checkBearerDelegation(envelope, options = {}) {
17767
- let sessionDid;
17768
- try {
17769
- const jwk = getBearerSessionJwk(envelope);
17770
- if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519") {
17771
- return {
17772
- ok: false,
17773
- detail: `unsupported bearer session key family ${jwk.kty}/${jwk.crv}; this build binds Ed25519 keys only`
17774
- };
17775
- }
17776
- const publicKey = fromBase64Url(jwk.x);
17777
- const privateKey = fromBase64Url(jwk.d);
17778
- if (publicKey.length !== 32 || privateKey.length !== 32) {
17779
- return { ok: false, detail: "bearer session key must contain 32-byte Ed25519 coordinates" };
17780
- }
17781
- const derivedPublicKey = ed25519.getPublicKey(privateKey);
17782
- if (!derivedPublicKey.every((byte, index) => byte === publicKey[index])) {
17783
- return { ok: false, detail: "bearer session private and public keys do not match" };
17784
- }
17785
- sessionDid = didKeyFromEd25519PublicKey(publicKey);
17786
- } catch (error) {
17787
- return {
17788
- ok: false,
17789
- detail: `could not derive did:key from embedded session key: ${error instanceof Error ? error.message : String(error)}`
17790
- };
17791
- }
17792
- const segments = envelope.delegation.split(".");
17793
- if (segments.length !== 3 || segments.some((segment) => segment.length === 0)) {
17794
- return { ok: false, detail: "delegation is not a three-segment JWT-shaped token" };
17795
- }
17796
- const [headerSegment, payloadSegment, signatureSegment] = segments;
17797
- const header = decodeJsonSegment(headerSegment);
17798
- if (!isJsonObject(header)) {
17799
- return { ok: false, detail: "delegation header is not a base64url JSON object" };
17800
- }
17801
- const payload = decodeJsonSegment(payloadSegment);
17802
- if (!isJsonObject(payload)) {
17803
- return { ok: false, detail: "delegation payload is not a base64url JSON object" };
17804
- }
17805
- let signatureBytes;
17806
- try {
17807
- signatureBytes = fromBase64Url(signatureSegment);
17808
- if (signatureBytes.length === 0) {
17809
- return { ok: false, detail: "delegation signature segment is empty" };
17810
- }
17811
- } catch {
17812
- return { ok: false, detail: "delegation signature segment is not base64url" };
17813
- }
17814
- if (header["alg"] !== "EdDSA") {
17815
- return {
17816
- ok: false,
17817
- detail: `delegation alg must be EdDSA, got ${JSON.stringify(header["alg"])}`
17818
- };
17819
- }
17820
- const issuer = payload["iss"];
17821
- if (typeof issuer !== "string" || issuer.length === 0) {
17822
- return { ok: false, detail: "delegation payload has no issuer (iss) DID" };
17823
- }
17824
- let issuerPublicKey;
17825
- try {
17826
- issuerPublicKey = ed25519PublicKeyFromDidKey(issuer);
17827
- } catch (error) {
17828
- return {
17829
- ok: false,
17830
- detail: `delegation issuer is not an ed25519 did:key: ${error instanceof Error ? error.message : String(error)}`
17831
- };
17832
- }
17833
- let signatureValid;
17834
- try {
17835
- signatureValid = ed25519.verify(
17836
- signatureBytes,
17837
- utf8Bytes(`${headerSegment}.${payloadSegment}`),
17838
- issuerPublicKey,
17839
- ED25519_VERIFY_OPTS
17840
- );
17841
- } catch {
17842
- signatureValid = false;
17843
- }
17844
- if (!signatureValid) {
17845
- return { ok: false, detail: "delegation signature does not verify against iss" };
17846
- }
17847
- const nowMs2 = options.now?.() ?? Date.now();
17848
- const exp = payload["exp"];
17849
- if (typeof exp !== "number" || !Number.isSafeInteger(exp) || exp <= 0) {
17850
- return { ok: false, detail: "delegation has no valid expiry (exp) claim" };
17851
- }
17852
- if (nowMs2 >= exp * 1e3) {
17853
- return { ok: false, detail: "delegation is expired" };
17854
- }
17855
- const nbf = payload["nbf"];
17856
- if (nbf !== void 0) {
17857
- if (typeof nbf !== "number" || !Number.isSafeInteger(nbf)) {
17858
- return { ok: false, detail: "delegation nbf claim is not a valid time" };
17859
- }
17860
- if (nowMs2 < nbf * 1e3) {
17861
- return { ok: false, detail: "delegation is not yet valid (nbf)" };
17862
- }
17863
- }
17864
- const audience = payload["aud"];
17865
- if (typeof audience !== "string" || audience.length === 0) {
17866
- return { ok: false, detail: "delegation payload has no audience (aud) DID" };
17867
- }
17868
- if (audience !== sessionDid) {
17869
- return {
17870
- ok: false,
17871
- detail: "delegation audience is not the link's embedded session key"
17872
- };
17873
- }
17874
- const att = payload["att"];
17875
- if (!Array.isArray(att) || att.length === 0) {
17876
- return { ok: false, detail: "delegation payload has no capabilities (att)" };
17877
- }
17878
- const required = requiredResourceUri(envelope);
17879
- const granted = att.map(parseCapability).some(
17880
- (capability) => capability !== null && READ_ABILITIES.has(capability.can) && resourceUriCovers(capability.with, required)
17881
- );
17882
- if (!granted) {
17883
- return {
17884
- ok: false,
17885
- detail: `delegation grants no read capability covering ${required}`
17886
- };
17887
- }
17888
- return { ok: true, delegateeDid: audience };
17889
- }
17890
- function mintBearerDelegation(options) {
17891
- const { issuerPrivateKey, audienceDid, resourceUri, expiresAtSeconds } = options;
17892
- if (!audienceDid.startsWith("did:")) {
17893
- throw new TypeError(`audienceDid must be a DID, got ${audienceDid}`);
17894
- }
17895
- if (resourceUri.length === 0) {
17896
- throw new TypeError("resourceUri must be non-empty");
17897
- }
17898
- if (!Number.isSafeInteger(expiresAtSeconds) || expiresAtSeconds <= 0) {
17899
- throw new TypeError(
17900
- `expiresAtSeconds must be a positive integer (epoch seconds), got ${expiresAtSeconds}`
17901
- );
17902
- }
17903
- const issuerDid = didKeyFromEd25519PublicKey(
17904
- ed25519.getPublicKey(issuerPrivateKey)
17905
- );
17906
- const header = { alg: "EdDSA", typ: "JWT", ucv: "0.9.1" };
17907
- const payload = {
17908
- iss: issuerDid,
17909
- aud: audienceDid,
17910
- att: [{ with: resourceUri, can: BEARER_READ_ABILITY }],
17911
- prf: [],
17912
- exp: expiresAtSeconds
17913
- };
17914
- const signingInput = `${toBase64Url(utf8Bytes(JSON.stringify(header)))}.${toBase64Url(
17915
- utf8Bytes(JSON.stringify(payload))
17916
- )}`;
17917
- const signature = ed25519.sign(utf8Bytes(signingInput), issuerPrivateKey);
17918
- return `${signingInput}.${toBase64Url(signature)}`;
17919
- }
17920
- function assertKey(key32) {
17921
- if (key32.length !== KEY_LENGTH) {
17922
- throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
17923
- }
17924
- }
17925
- async function importAesKey(key32, usage) {
17926
- assertKey(key32);
17927
- return globalThis.crypto.subtle.importKey(
17928
- "raw",
17929
- key32,
17930
- "AES-GCM",
17931
- false,
17932
- [usage]
17933
- );
17934
- }
17935
- function generateKey() {
17936
- return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
17937
- }
17938
- async function seal(plaintextBytes, key32) {
17939
- const { nonce, ciphertext } = await encryptEnvelope(plaintextBytes, key32);
17940
- const blob = new Uint8Array(HEADER_LENGTH + nonce.length + ciphertext.length);
17941
- blob[0] = SEALED_BLOB_VERSION;
17942
- blob.set(nonce, HEADER_LENGTH);
17943
- blob.set(ciphertext, HEADER_LENGTH + nonce.length);
17944
- return { blob, cid: await computeCid(blob) };
17945
- }
17946
- async function open(blob, key32) {
17947
- if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
17948
- throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
17949
- }
17950
- if (blob[0] !== SEALED_BLOB_VERSION) {
17951
- throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
17952
- }
17953
- const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
17954
- const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
17955
- return decryptEnvelope(nonce, ciphertext, key32);
17956
- }
17957
- async function encryptEnvelope(plaintextBytes, key32) {
17958
- const key = await importAesKey(key32, "encrypt");
17959
- const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
17960
- const ciphertext = new Uint8Array(
17961
- await globalThis.crypto.subtle.encrypt(
17962
- { name: "AES-GCM", iv: nonce, additionalData: AAD },
17963
- key,
17964
- plaintextBytes
17965
- )
17966
- );
17967
- return { nonce, ciphertext };
17968
- }
17969
- async function decryptEnvelope(nonce, ciphertext, key32) {
17970
- if (nonce.length !== NONCE_LENGTH) {
17971
- throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
17972
- }
17973
- const key = await importAesKey(key32, "decrypt");
17974
- return new Uint8Array(
17975
- await globalThis.crypto.subtle.decrypt(
17976
- { name: "AES-GCM", iv: nonce, additionalData: AAD },
17977
- key,
17978
- ciphertext
17979
- )
17980
- );
17981
- }
17982
17695
  function isPlainRecord(value) {
17983
17696
  const proto = Object.getPrototypeOf(value);
17984
17697
  return proto === Object.prototype || proto === null;
@@ -18077,22 +17790,6 @@ function assertExactKeys(value, keys, label) {
18077
17790
  function equal(left, right) {
18078
17791
  return left.length === right.length && left.every((byte, index) => byte === right[index]);
18079
17792
  }
18080
- function signingBytes(unsigned) {
18081
- const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
18082
- const body = utf8Bytes(canonicalize2(unsigned));
18083
- const bytes3 = new Uint8Array(domain.length + body.length);
18084
- bytes3.set(domain);
18085
- bytes3.set(body, domain.length);
18086
- return bytes3;
18087
- }
18088
- function signingBytesV2(unsigned) {
18089
- const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
18090
- const body = utf8Bytes(canonicalize2(unsigned));
18091
- const bytes3 = new Uint8Array(domain.length + body.length);
18092
- bytes3.set(domain);
18093
- bytes3.set(body, domain.length);
18094
- return bytes3;
18095
- }
18096
17793
  function signingBytesV3(unsigned) {
18097
17794
  const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
18098
17795
  const body = utf8Bytes(canonicalize2(unsigned));
@@ -18101,53 +17798,6 @@ function signingBytesV3(unsigned) {
18101
17798
  bytes3.set(body, domain.length);
18102
17799
  return bytes3;
18103
17800
  }
18104
- function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
18105
- const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
18106
- const publicKey = ed25519.getPublicKey(ed25519PrivKey);
18107
- const signature = ed25519.sign(signingBytes(unsigned), ed25519PrivKey);
18108
- return {
18109
- ...unsigned,
18110
- signature: {
18111
- signerDid: didKeyFromEd25519PublicKey(publicKey),
18112
- algorithm: "Ed25519",
18113
- value: toBase64Url(signature)
18114
- }
18115
- };
18116
- }
18117
- function verifyEnvelopeSignatureOnly(envelope) {
18118
- const parsed = shareEnvelopeSchema.parse(envelope);
18119
- const { signature, ...unsigned } = parsed;
18120
- const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
18121
- return ed25519.verify(
18122
- fromBase64Url(signature.value),
18123
- signingBytes(unsigned),
18124
- publicKey,
18125
- ED25519_VERIFY_OPTS2
18126
- );
18127
- }
18128
- async function verifyEnvelope(envelope, options) {
18129
- const parsed = shareEnvelopeSchema.parse(envelope);
18130
- if (parsed.signature.signerDid !== options.expectedSignerDid) return false;
18131
- if (!verifyEnvelopeSignatureOnly(parsed)) return false;
18132
- if (parsed.authorizationTarget.kind === "policy") {
18133
- const policyBytes = fromBase64Url(parsed.authorizationTarget.policyBytes);
18134
- if (await computeCid(policyBytes) !== parsed.authorizationTarget.policyCid) {
18135
- return false;
18136
- }
18137
- }
18138
- return true;
18139
- }
18140
- function verifyEnvelopeV2SignatureOnly(envelope) {
18141
- const parsed = shareEnvelopeV2Schema.parse(envelope);
18142
- const { signature, ...unsigned } = parsed;
18143
- return ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
18144
- }
18145
- async function verifyEnvelopeV2(envelope, options) {
18146
- const parsed = shareEnvelopeV2Schema.parse(envelope);
18147
- if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
18148
- if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
18149
- return true;
18150
- }
18151
17801
  function verifyEnvelopeV3SignatureOnly(envelope) {
18152
17802
  const parsed = shareEnvelopeV3Schema.parse(envelope);
18153
17803
  const { signature, ...unsigned } = parsed;
@@ -18193,7 +17843,7 @@ async function verifyEnvelopeV3(envelope, options) {
18193
17843
  const binding = parsed.attestedEnforcerBinding;
18194
17844
  const { signature: bindingSignature, ...unsignedBinding } = binding;
18195
17845
  const expectedBindingDigestHex = hex(sha2562(new TextEncoder().encode(canonicalize2({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
18196
- 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;
17846
+ 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;
18197
17847
  try {
18198
17848
  const digest3 = sha2562(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize2(unsignedBinding)}`));
18199
17849
  if (!ed25519.verify(fromBase64Url(bindingSignature.value), digest3, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
@@ -18241,77 +17891,18 @@ function assertCanonicalCid(cidString) {
18241
17891
  throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
18242
17892
  }
18243
17893
  }
18244
- function encodeShareUrl({ origin, ciphertextCid, key32 }) {
18245
- if (key32.length !== KEY_LENGTH2) {
18246
- throw new TypeError(`key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
18247
- }
18248
- assertCanonicalCid(ciphertextCid);
18249
- if (!isCanonicalHttpsOrigin(origin)) {
18250
- throw new TypeError(`origin must be a canonical https origin, got ${origin}`);
18251
- }
18252
- return `${origin}/s/${ciphertextCid}#k=${toBase64Url(key32)}`;
18253
- }
18254
- function parseShareUrl(url, options = {}) {
18255
- const parsed = new URL(url);
18256
- if (parsed.protocol !== "https:") {
18257
- throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
18258
- }
18259
- if (parsed.username !== "" || parsed.password !== "") {
18260
- throw new TypeError("share URL must not carry userinfo");
18261
- }
18262
- if (parsed.search !== "") {
18263
- throw new TypeError("share URL must not have a query string");
18264
- }
18265
- if (options.expectedOrigin !== void 0) {
18266
- if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
18267
- throw new TypeError(
18268
- `expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
18269
- );
18270
- }
18271
- if (parsed.origin !== options.expectedOrigin) {
18272
- throw new TypeError(
18273
- `share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
18274
- );
18275
- }
18276
- }
18277
- const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
18278
- if (!match || match[1] === void 0) {
18279
- throw new TypeError(`not a share URL path: ${parsed.pathname}`);
18280
- }
18281
- const ciphertextCid = match[1];
18282
- assertCanonicalCid(ciphertextCid);
18283
- if (!parsed.hash.startsWith("#k=")) {
18284
- throw new TypeError("share URL is missing the #k= key fragment");
18285
- }
18286
- const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
18287
- if (key32.length !== KEY_LENGTH2) {
18288
- throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
18289
- }
18290
- return { ciphertextCid, key32 };
18291
- }
18292
- async function encodeInlineShareUrl(parts) {
18293
- if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
18294
- if (parts.ciphertext.byteLength === 0 || parts.ciphertext.byteLength > MAX_INLINE_BYTES) throw new RangeError("inline ciphertext is outside the allowed size");
18295
- if (parts.key32 !== void 0 && parts.key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
18296
- const ciphertextCid = await computeCid(parts.ciphertext);
18297
- const payload = canonicalize2({
18298
- v: 2,
18299
- c: toBase64Url(parts.ciphertext),
18300
- cid: ciphertextCid,
18301
- ...parts.key32 === void 0 ? {} : { k: toBase64Url(parts.key32) }
18302
- });
18303
- const payloadBytes = new TextEncoder().encode(payload);
18304
- if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
18305
- return `${parts.origin}/s/inline${INLINE_PREFIX}${toBase64Url(payloadBytes)}`;
18306
- }
18307
17894
  function parseInlineShareUrl(url, options = {}) {
18308
17895
  const parsed = new URL(url);
18309
- if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.search !== "") throw new TypeError("inline share URL must be canonical HTTPS without query or userinfo");
17896
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("inline share URL must be canonical HTTPS without userinfo");
18310
17897
  if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
18311
- if (parsed.pathname !== "/s/inline" || !parsed.hash.startsWith(INLINE_PREFIX)) throw new TypeError("not an inline v2 share URL");
17898
+ if (parsed.pathname !== "/viewer") throw new TypeError("not a TinyCloud policy share URL");
17899
+ const secretPayload = parsed.search === "" && parsed.hash.startsWith(INLINE_PREFIX) ? parsed.hash.slice(INLINE_PREFIX.length) : void 0;
17900
+ const publicPayload = parsed.hash === "" && parsed.searchParams.size === 1 ? parsed.searchParams.get(PUBLIC_INLINE_PARAMETER) ?? void 0 : void 0;
17901
+ if (secretPayload === void 0 && publicPayload === void 0) throw new TypeError("not a canonical TinyCloud policy share URL");
17902
+ if (publicPayload !== void 0 && parsed.search !== `?${PUBLIC_INLINE_PARAMETER}=${publicPayload}`) throw new TypeError("public inline URL is not canonical");
18312
17903
  let payloadBytes;
18313
17904
  try {
18314
- payloadBytes = fromBase64Url(parsed.hash.slice(INLINE_PREFIX.length));
17905
+ payloadBytes = fromBase64Url(secretPayload ?? publicPayload);
18315
17906
  } catch {
18316
17907
  throw new TypeError("inline payload is not canonical base64url");
18317
17908
  }
@@ -18324,356 +17915,95 @@ function parseInlineShareUrl(url, options = {}) {
18324
17915
  }
18325
17916
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload must be an object");
18326
17917
  if (canonicalize2(value) !== new TextDecoder("utf-8").decode(payloadBytes)) throw new TypeError("inline payload is not canonical JSON");
18327
- const record2 = value;
18328
- const keys = Object.keys(record2);
18329
- 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");
17918
+ const record = value;
17919
+ const keys = Object.keys(record);
17920
+ 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");
18330
17921
  let ciphertext;
18331
17922
  try {
18332
- ciphertext = fromBase64Url(record2.c);
17923
+ ciphertext = fromBase64Url(record.c);
18333
17924
  } catch {
18334
17925
  throw new TypeError("inline ciphertext is not canonical base64url");
18335
17926
  }
18336
17927
  if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
18337
- assertCanonicalCid(record2.cid);
18338
- if (record2.k !== void 0 && typeof record2.k !== "string") throw new TypeError("inline key must be base64url");
17928
+ assertCanonicalCid(record.cid);
17929
+ if (record.k !== void 0 && typeof record.k !== "string") throw new TypeError("inline key must be base64url");
18339
17930
  let key32;
18340
17931
  try {
18341
- key32 = record2.k === void 0 ? void 0 : fromBase64Url(record2.k);
17932
+ key32 = record.k === void 0 ? void 0 : fromBase64Url(record.k);
18342
17933
  } catch {
18343
17934
  throw new TypeError("inline key is not canonical base64url");
18344
17935
  }
18345
17936
  if (key32 !== void 0 && key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
18346
- return { kind: "inline", ciphertextCid: record2.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
18347
- }
18348
- function parseCompactOrInlineShareUrl(url, options = {}) {
18349
- if (new URL(url).pathname === "/s/inline") return parseInlineShareUrl(url, options);
18350
- return { kind: "compact", ...parseShareUrl(url, options) };
18351
- }
18352
- function boundedBytes(value, limit, code3) {
18353
- if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("fetch-failed", "share byte limit is invalid");
18354
- if (!(value instanceof Uint8Array)) throw new ShareReceiveError("fetch-failed", "registry returned invalid bytes");
18355
- if (value.byteLength > limit) throw new ShareReceiveError(code3, "share blob exceeds the configured byte limit");
18356
- return value;
18357
- }
18358
- async function readResponseBytes(response, limit, tooLargeCode) {
18359
- const contentLength = response.headers.get("content-length");
18360
- if (contentLength !== null && Number.isSafeInteger(Number(contentLength)) && Number(contentLength) > limit) {
18361
- throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
18362
- }
18363
- if (response.body === null) {
18364
- return boundedBytes(new Uint8Array(await response.arrayBuffer()), limit, tooLargeCode);
18365
- }
18366
- const reader = response.body.getReader();
18367
- const chunks = [];
18368
- let total = 0;
18369
- try {
18370
- for (; ; ) {
18371
- const part = await reader.read();
18372
- if (part.done) break;
18373
- total += part.value.byteLength;
18374
- if (total > limit) {
18375
- await reader.cancel();
18376
- throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
18377
- }
18378
- chunks.push(part.value);
18379
- }
18380
- } finally {
18381
- reader.releaseLock();
18382
- }
18383
- const bytes3 = new Uint8Array(total);
18384
- let offset = 0;
18385
- for (const chunk of chunks) {
18386
- bytes3.set(chunk, offset);
18387
- offset += chunk.byteLength;
18388
- }
18389
- return bytes3;
18390
- }
18391
- function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
18392
- if (options.fetchBlob !== void 0) return async (input) => {
18393
- try {
18394
- options.signal?.throwIfAborted();
18395
- const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
18396
- options.signal?.throwIfAborted();
18397
- return boundedBytes(bytes3, limit, tooLargeCode);
18398
- } catch (error) {
18399
- if (options.signal?.aborted) throw options.signal.reason ?? error;
18400
- if (error instanceof ShareReceiveError) throw error;
18401
- throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
18402
- }
18403
- };
18404
- if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "a registry fetch adapter is required");
18405
- const fetchFn = options.fetchFn ?? globalThis.fetch;
18406
- const base33 = options.registryBaseUrl.replace(/\/+$/, "");
18407
- return async ({ cid: cid2 }) => {
18408
- try {
18409
- options.signal?.throwIfAborted();
18410
- const response = await fetchFn(`${base33}/ipfs/${cid2}?format=raw`, {
18411
- headers: { accept: "application/vnd.ipld.raw" },
18412
- redirect: "error",
18413
- ...options.signal === void 0 ? {} : { signal: options.signal }
18414
- });
18415
- options.signal?.throwIfAborted();
18416
- if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
18417
- const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
18418
- options.signal?.throwIfAborted();
18419
- return bytes3;
18420
- } catch (error) {
18421
- if (options.signal?.aborted) throw options.signal.reason ?? error;
18422
- if (error instanceof ShareReceiveError) throw error;
18423
- throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
18424
- }
18425
- };
17937
+ return { kind: "inline", ciphertextCid: record.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
18426
17938
  }
18427
17939
  function metadataFor(envelope, origin) {
18428
- if (envelope.version === 2 || envelope.version === 3) {
18429
- const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
18430
- return {
18431
- protocol: "tinycloud-share",
18432
- version: 1,
18433
- shareId: envelope.shareId,
18434
- origin,
18435
- target: { ...envelope.target, kind: targetKind3 },
18436
- resource: envelope.resource,
18437
- actions: envelope.actions,
18438
- expiresAt: envelope.expiry,
18439
- display: {
18440
- ...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
18441
- ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
18442
- ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
18443
- },
18444
- ...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
18445
- };
18446
- }
17940
+ const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
18447
17941
  return {
18448
17942
  protocol: "tinycloud-share",
18449
17943
  version: 1,
18450
17944
  shareId: envelope.shareId,
18451
17945
  origin,
18452
- target: { ...envelope.target, kind: "bearer" },
18453
- resource: envelope.target.resource,
18454
- actions: ["read"],
17946
+ target: { ...envelope.target, kind },
17947
+ resource: { ...envelope.resource },
17948
+ actions: [...envelope.actions],
18455
17949
  expiresAt: envelope.expiry,
18456
17950
  display: {
18457
17951
  ...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
18458
17952
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
18459
17953
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
18460
- },
18461
- ...envelope.content === void 0 ? {} : { content: { cid: envelope.content.cid } }
17954
+ }
18462
17955
  };
18463
17956
  }
18464
- function parseEnvelope(bytes3) {
18465
- try {
18466
- const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
18467
- const value = JSON.parse(text);
18468
- if (canonicalize2(value) !== text) throw new Error("share envelope is not canonical JSON");
18469
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
18470
- const version2 = value.version;
18471
- if (version2 === 3) return shareEnvelopeV3Schema.parse(value);
18472
- if (version2 === 2) return shareEnvelopeV2Schema.parse(value);
18473
- }
18474
- return shareEnvelopeSchema.parse(value);
18475
- } catch {
18476
- throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
18477
- }
18478
- }
18479
- async function resolveShareEnvelope(link2, options = {}) {
17957
+ async function resolvePolicyShare(link2, options) {
17958
+ options.signal?.throwIfAborted();
17959
+ let url;
18480
17960
  let parsed;
18481
17961
  try {
18482
- parsed = parseCompactOrInlineShareUrl(link2, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
17962
+ url = new URL(link2);
17963
+ if (url.pathname !== "/viewer" || url.hash !== "" || url.searchParams.size !== 1 || !url.searchParams.has("tc2")) throw new Error("not public Policy/v3");
17964
+ parsed = parseInlineShareUrl(link2, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
17965
+ if (parsed.key32 !== void 0) throw new Error("secret inline transport is retired");
18483
17966
  } catch {
18484
17967
  throw new ShareReceiveError("invalid-link", "share link format is invalid");
18485
17968
  }
17969
+ const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
17970
+ if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "public policy envelope exceeds the configured byte limit");
17971
+ if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "public envelope bytes do not match the link CID");
17972
+ options.signal?.throwIfAborted();
17973
+ let envelope;
18486
17974
  try {
18487
- if (parsed.key32 !== void 0) options.onKeyParsed?.(parsed.key32);
18488
- const url = new URL(link2);
18489
- 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 });
18490
- if (await computeCid(sealed) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "registry bytes do not match the link CID");
18491
- let plaintext;
18492
- try {
18493
- plaintext = parsed.key32 === void 0 ? sealed : await open(sealed, parsed.key32);
18494
- } catch {
18495
- throw new ShareReceiveError("decrypt-failed", "share envelope could not be opened");
18496
- }
18497
- return { envelope: parseEnvelope(plaintext), origin: url.origin, cid: parsed.ciphertextCid, kind: parsed.kind };
18498
- } finally {
18499
- parsed.key32?.fill(0);
18500
- }
18501
- }
18502
- function actionName(value) {
18503
- return value === "read" ? "tinycloud.kv/get" : value === "list" ? "tinycloud.kv/list" : value === "edit" ? "tinycloud.kv/put" : value;
18504
- }
18505
- function resourceContains(outer, inner) {
18506
- if (outer.kind === "exact") return inner.kind === "exact" && outer.path === inner.path;
18507
- const prefix = outer.path.endsWith("/") ? outer.path : `${outer.path}/`;
18508
- return inner.path === outer.path || inner.path.startsWith(prefix);
18509
- }
18510
- function policyEvidenceMatches(envelope, evidence) {
18511
- const registeredActions = new Set(evidence.actions.map(actionName));
18512
- return evidence.policyCid === (envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "") && evidence.signerDid === envelope.signature.signerDid && evidence.registrationCid.length > 0 && evidence.shareId === envelope.shareId && canonicalize2(evidence.recipientMatcher) === canonicalize2(envelope.recipientMatcher) && canonicalize2(evidence.target) === canonicalize2(envelope.target) && resourceContains(evidence.resource, envelope.resource) && envelope.actions.every((action) => registeredActions.has(actionName(action))) && canonicalize2(evidence.contentSource) === canonicalize2(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);
18513
- }
18514
- async function verifyV2Envelope(envelope, linkOrigin, options) {
18515
- if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
18516
- throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
18517
- }
18518
- try {
18519
- if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
17975
+ const encoded = new TextDecoder("utf-8", { fatal: true }).decode(parsed.ciphertext);
17976
+ const value = JSON.parse(encoded);
17977
+ if (canonicalize2(value) !== encoded) throw new Error("non-canonical envelope");
17978
+ envelope = shareEnvelopeV3Schema.parse(value);
18520
17979
  } catch {
18521
- throw new ShareReceiveError("signature-invalid", "share signature is invalid");
18522
- }
18523
- const expiry = Date.parse(envelope.expiry);
18524
- if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
18525
- if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
18526
- if (envelope.authorizationTarget.kind === "policy") {
18527
- if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "an external policy authority is required");
18528
- let evidence;
18529
- try {
18530
- evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
18531
- } catch {
18532
- evidence = void 0;
18533
- }
18534
- if (evidence === void 0 || !policyEvidenceMatches(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "share policy authority evidence is not bound to the envelope");
18535
- if (options.trustedSignerDid !== void 0 && (evidence.signerDid !== options.trustedSignerDid || envelope.signature.signerDid !== options.trustedSignerDid)) {
18536
- throw new ShareReceiveError("signature-invalid", "share signer is not trusted");
18537
- }
18538
- } else if (envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
18539
- throw new ShareReceiveError("capability-invalid", "recipient authorization does not match the signed recipient");
18540
- } else {
18541
- if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
18542
- }
18543
- }
18544
- async function verifyV3Envelope(envelope, linkOrigin, options) {
18545
- if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
18546
- throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
17980
+ throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
18547
17981
  }
18548
17982
  try {
18549
17983
  if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
18550
17984
  } catch {
18551
17985
  throw new ShareReceiveError("signature-invalid", "share signature is invalid");
18552
17986
  }
17987
+ options.signal?.throwIfAborted();
18553
17988
  const expiry = Date.parse(envelope.expiry);
18554
17989
  if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
18555
17990
  if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
18556
- }
18557
- async function verifyV1PolicyEnvelope(envelope, linkOrigin, options) {
18558
- if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
18559
- if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
18560
- throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
18561
- }
18562
- throw new ShareReceiveError("unsupported-target", "share policy target is not supported", { reason: "policy-target" });
18563
- }
18564
- async function verifyBearerEnvelope(envelope, linkOrigin, options = {}) {
18565
- if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
18566
- throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
18567
- }
18568
- 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" });
18569
- if (envelope.target.resource.kind !== "exact") throw new ShareReceiveError("unsupported-target", "this receive path only handles exact resources", { reason: "prefix-resource" });
18570
- const expiry = Date.parse(envelope.expiry);
18571
- if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
18572
- try {
18573
- if (!await verifyEnvelope(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
18574
- } catch {
18575
- throw new ShareReceiveError("signature-invalid", "share signature is invalid");
18576
- }
18577
- if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
18578
- const capability = checkBearerDelegation(envelope, options.now === void 0 ? {} : { now: options.now });
18579
- if (!capability.ok) throw new ShareReceiveError("capability-invalid", "share delegation does not authorize the target");
18580
- }
18581
- async function verifyResolved(envelope, linkOrigin, options) {
18582
- await verifyBearerEnvelope(envelope, linkOrigin, options);
17991
+ options.onResolvedAddressedEnvelope?.(envelope, parsed.ciphertextCid);
17992
+ return { envelope, origin: url.origin, cid: parsed.ciphertextCid };
18583
17993
  }
18584
17994
  async function inspectShare(link2, options = {}) {
18585
- const resolved = await resolveShareEnvelope(link2, options);
18586
- if (resolved.envelope.version === 1 && resolved.envelope.authorizationTarget.kind === "policy") await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
18587
- else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
18588
- else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
18589
- else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
18590
- if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
18591
- return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
17995
+ const resolved = await resolvePolicyShare(link2, options);
17996
+ return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
18592
17997
  }
18593
17998
  async function receiveShare(link2, options = {}) {
18594
- const resolved = await resolveShareEnvelope(link2, options);
18595
- if (resolved.envelope.version === 3) {
18596
- await verifyV3Envelope(resolved.envelope, resolved.origin, options);
18597
- options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
18598
- return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
18599
- }
18600
- if (resolved.envelope.version === 2) {
18601
- await verifyV2Envelope(resolved.envelope, resolved.origin, options);
18602
- options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
18603
- const method = resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
18604
- if (options.authorization === void 0) return { state: "authorization-required", method };
18605
- 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 } });
18606
- if (result.state === "authorization-required") return result;
18607
- if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "share authorization was denied");
18608
- if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
18609
- throw new ShareReceiveError("content-integrity-failed", "share authorization returned an unsigned content result");
18610
- }
18611
- const authorized = result.value;
18612
- try {
18613
- if (!await options.authorization.verifyResult({ envelope: resolved.envelope, value: authorized, proof: authorized.proof })) throw new Error("node proof");
18614
- } catch {
18615
- throw new ShareReceiveError("content-integrity-failed", "node authorization proof is invalid");
18616
- }
18617
- const bytes4 = authorized.bytes.slice();
18618
- const maxBytes = options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES;
18619
- if (bytes4.byteLength > maxBytes) throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
18620
- const digestBytes3 = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes4));
18621
- const bodyDigest = toBase64Url(digestBytes3);
18622
- if (authorized.bodyDigest !== bodyDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content digest does not match");
18623
- if (authorized.contentSourceDigest !== resolved.envelope.contentSourceDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content source does not match");
18624
- const binding = authorized.binding;
18625
- 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");
18626
- if (resolved.envelope.metadata.byteLength !== void 0 && resolved.envelope.metadata.byteLength !== bytes4.byteLength) {
18627
- throw new ShareReceiveError("content-integrity-failed", "authorized content length does not match");
18628
- }
18629
- let text2;
18630
- try {
18631
- text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes4);
18632
- } catch {
18633
- }
18634
- return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes4, ...text2 === void 0 ? {} : { text: text2 } };
18635
- }
18636
- if (resolved.envelope.authorizationTarget.kind === "policy") {
18637
- await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
18638
- options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
18639
- return { state: "authorization-required", method: "email-claim" };
18640
- }
18641
- await verifyResolved(resolved.envelope, resolved.origin, options);
18642
- let bytes3 = new Uint8Array();
18643
- if (resolved.envelope.content !== void 0) {
18644
- bytes3 = await registryFetcher(
18645
- options,
18646
- (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES) + CONTENT_SEALED_OVERHEAD,
18647
- "max-bytes-exceeded",
18648
- "content"
18649
- )({ origin: resolved.origin, cid: resolved.envelope.content.cid });
18650
- if (await computeCid(bytes3) !== resolved.envelope.content.cid) throw new ShareReceiveError("content-integrity-failed", "shared content CID does not match");
18651
- const key = fromBase64Url(resolved.envelope.content.key);
18652
- try {
18653
- bytes3 = await open(bytes3, key);
18654
- } catch {
18655
- throw new ShareReceiveError("content-integrity-failed", "shared content could not be opened");
18656
- } finally {
18657
- key.fill(0);
18658
- }
18659
- if (bytes3.byteLength > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) {
18660
- throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
18661
- }
18662
- }
18663
- let text;
18664
- if (bytes3.byteLength !== 0) {
18665
- try {
18666
- text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
18667
- } catch {
18668
- }
18669
- }
18670
- return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes3, ...text === void 0 ? {} : { text } };
17999
+ const resolved = await resolvePolicyShare(link2, options);
18000
+ return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
18671
18001
  }
18672
18002
  function redactPublishedShare(result) {
18673
18003
  return {
18674
18004
  protocol: "tinycloud-share",
18675
18005
  version: SHARE_PUBLISH_RESULT_VERSION,
18676
- link: { kind: result.link.kind, cid: result.link.cid },
18006
+ link: { ...result.link },
18677
18007
  metadata: {
18678
18008
  protocol: "tinycloud-share",
18679
18009
  version: 1,
@@ -18683,242 +18013,9 @@ function redactPublishedShare(result) {
18683
18013
  resource: { ...result.metadata.resource },
18684
18014
  actions: [...result.metadata.actions],
18685
18015
  expiresAt: result.metadata.expiresAt,
18686
- display: { ...result.metadata.display },
18687
- ...result.metadata.content === void 0 ? {} : { content: { cid: result.metadata.content.cid } }
18688
- },
18689
- registryDeleteAfter: result.registryDeleteAfter
18690
- };
18691
- }
18692
- function assertSafeFilename(filename) {
18693
- if (filename.length === 0 || filename === "." || filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(filename)) {
18694
- throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
18695
- }
18696
- }
18697
- function assertOrigin(origin, label) {
18698
- if (!isCanonicalHttpsOrigin(origin)) {
18699
- throw new SharePublishError("invalid-argument", `${label} must be a canonical HTTPS origin`);
18700
- }
18701
- }
18702
- async function snapshotSource(source, limit) {
18703
- if (!Number.isSafeInteger(limit) || limit <= 0) {
18704
- throw new SharePublishError("invalid-argument", "maxBytes must be a positive safe integer");
18705
- }
18706
- if (source instanceof Uint8Array) {
18707
- if (source.byteLength > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
18708
- return source.slice();
18709
- }
18710
- const chunks = [];
18711
- let total = 0;
18712
- try {
18713
- for await (const chunk of source) {
18714
- if (!(chunk instanceof Uint8Array)) throw new SharePublishError("invalid-argument", "share source yielded invalid bytes");
18715
- total += chunk.byteLength;
18716
- if (total > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
18717
- chunks.push(chunk);
18718
- }
18719
- } catch (error) {
18720
- if (error instanceof SharePublishError) throw error;
18721
- throw new SharePublishError("upload-failed", "share source could not be read");
18722
- }
18723
- const bytes3 = new Uint8Array(total);
18724
- let offset = 0;
18725
- for (const chunk of chunks) {
18726
- bytes3.set(chunk, offset);
18727
- offset += chunk.byteLength;
18728
- }
18729
- return bytes3;
18730
- }
18731
- function assertTarget(target) {
18732
- if (target.kind !== "bearer") {
18733
- throw new SharePublishError("unsupported-target", "this release publishes bearer shares only");
18734
- }
18735
- }
18736
- function assertRegistryUrl(value, allowInsecure) {
18737
- let url;
18738
- try {
18739
- url = new URL(value);
18740
- } catch {
18741
- throw new SharePublishError("invalid-argument", "registry URL is invalid");
18742
- }
18743
- if (url.username || url.password || url.search || url.hash) throw new SharePublishError("invalid-argument", "registry URL must not contain credentials, query, or fragment");
18744
- if (!allowInsecure && url.protocol !== "https:") throw new SharePublishError("invalid-argument", "production registry uploads require HTTPS");
18745
- 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");
18746
- return url;
18747
- }
18748
- async function defaultUpload(options, input) {
18749
- if (options.registryBaseUrl === void 0) throw new SharePublishError("upload-auth-required", "an authenticated share registry is required");
18750
- const base33 = assertRegistryUrl(options.registryBaseUrl, options.allowInsecureRegistry === true).toString().replace(/\/$/, "");
18751
- const localInsecure = options.allowInsecureRegistry === true && (new URL(base33).hostname === "127.0.0.1" || new URL(base33).hostname === "localhost");
18752
- if (options.authorizeUpload === void 0 && !localInsecure) {
18753
- throw new SharePublishError("upload-auth-required", "share upload authorization is required");
18754
- }
18755
- let authorization;
18756
- if (options.authorizeUpload !== void 0) {
18757
- try {
18758
- authorization = await options.authorizeUpload(input);
18759
- } catch {
18760
- throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
18761
- }
18762
- }
18763
- if (authorization !== void 0 && options.authorizationOrigin !== void 0) {
18764
- let authorizationUrl;
18765
- try {
18766
- authorizationUrl = new URL(options.authorizationOrigin);
18767
- } catch {
18768
- throw new SharePublishError("invalid-argument", "upload authorization origin is invalid");
18769
- }
18770
- if (authorizationUrl.origin !== new URL(base33).origin) throw new SharePublishError("upload-auth-required", "upload authorization is scoped to another origin");
18771
- }
18772
- const headers = new Headers({
18773
- "content-type": "application/vnd.ipld.raw",
18774
- "if-none-match": "*",
18775
- "x-delete-after": input.deleteAfter
18776
- });
18777
- if (typeof authorization === "string") {
18778
- headers.set("x-tinycloud-upload-attestation", authorization);
18779
- try {
18780
- const attestation = JSON.parse(authorization);
18781
- if (attestation.retention === void 0) throw new Error("retention");
18782
- headers.set("x-tinycloud-retention", canonicalize2(attestation.retention));
18783
- } catch {
18784
- throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
18785
- }
18786
- } else if (authorization !== void 0) {
18787
- new Headers(authorization).forEach((value, key) => headers.set(key, value));
18788
- }
18789
- let response;
18790
- try {
18791
- response = await (options.fetchFn ?? globalThis.fetch)(`${base33}/blobs`, {
18792
- method: "POST",
18793
- headers,
18794
- body: input.blob,
18795
- ...options.credentials === void 0 ? {} : { credentials: options.credentials },
18796
- redirect: "error",
18797
- referrerPolicy: "no-referrer"
18798
- });
18799
- } catch {
18800
- throw new SharePublishError("upload-failed", "share registry is unavailable");
18801
- }
18802
- if (response.status === 401 || response.status === 403) throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
18803
- if (!response.ok) throw new SharePublishError("upload-failed", "share registry rejected the encrypted blob");
18804
- let body;
18805
- try {
18806
- body = await response.json();
18807
- } catch {
18808
- throw new SharePublishError("upload-failed", "share registry returned an invalid response");
18809
- }
18810
- const record2 = typeof body === "object" && body !== null ? body : {};
18811
- if (record2.cid !== input.cid || typeof record2.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share registry returned an unexpected CID or retention value");
18812
- return { cid: input.cid, deleteAfter: record2.deleteAfter };
18813
- }
18814
- async function uploadShareBlob(options, input) {
18815
- const result = await (options.uploadBlob ?? ((value) => defaultUpload(options, value)))(input);
18816
- if (result.cid !== input.cid || typeof result.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share uploader returned an invalid result");
18817
- return result;
18818
- }
18819
- async function publishShare(options) {
18820
- const target = options.target ?? { kind: "bearer" };
18821
- assertTarget(target);
18822
- assertSafeFilename(options.filename);
18823
- assertOrigin(options.origin, "share origin");
18824
- if (options.mediaType !== void 0 && options.mediaType.length === 0) throw new SharePublishError("invalid-argument", "mediaType must not be empty");
18825
- const maxBytes = options.maxBytes ?? SHARE_CONTENT_LIMIT;
18826
- if (maxBytes > SHARE_CONTENT_LIMIT) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the maximum 100 MiB limit");
18827
- const content = await snapshotSource(options.source, maxBytes);
18828
- if (content.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
18829
- if (options.allowBinary !== true) {
18830
- try {
18831
- new TextDecoder("utf-8", { fatal: true }).decode(content);
18832
- } catch {
18833
- throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
18016
+ display: { ...result.metadata.display }
18834
18017
  }
18835
- }
18836
- const nowMs2 = options.now?.() ?? Date.now();
18837
- const expiresAt = options.expiresAt ?? new Date(nowMs2 + DEFAULT_SHARE_LIFETIME_MS);
18838
- if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs2) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
18839
- const expiry = expiresAt.toISOString();
18840
- const nodeAudience = options.nodeAudience ?? "did:web:node.tinycloud.xyz";
18841
- const spaceId = options.spaceId ?? "bearer";
18842
- const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
18843
- const path = `shares/${shareId}/${options.filename}`;
18844
- const sessionPrivateKey = ed25519.utils.randomPrivateKey();
18845
- const senderPrivateKey = ed25519.utils.randomPrivateKey();
18846
- let contentKey;
18847
- let envelopeKey;
18848
- try {
18849
- const sessionPublicKey = ed25519.getPublicKey(sessionPrivateKey);
18850
- const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
18851
- const delegation = mintBearerDelegation({
18852
- issuerPrivateKey: senderPrivateKey,
18853
- audienceDid: sessionDid,
18854
- resourceUri: bearerResourceUri(options.origin, spaceId, path),
18855
- expiresAtSeconds: Math.ceil(expiresAt.getTime() / 1e3)
18856
- });
18857
- contentKey = generateKey();
18858
- const sealedContent = await seal(content, contentKey);
18859
- const unsigned = {
18860
- version: 1,
18861
- shareId,
18862
- delegation,
18863
- authorizationTarget: {
18864
- kind: "bearerKey",
18865
- sessionJwk: { kty: "OKP", crv: "Ed25519", x: toBase64Url(sessionPublicKey), d: toBase64Url(sessionPrivateKey) }
18866
- },
18867
- target: { origin: options.origin, nodeAudience, spaceId, resource: { kind: "exact", path } },
18868
- display: { filename: options.filename },
18869
- expiry,
18870
- content: { cid: sealedContent.cid, key: toBase64Url(contentKey) }
18871
- };
18872
- const envelope = signEnvelope(unsigned, senderPrivateKey);
18873
- envelopeKey = generateKey();
18874
- const sealedEnvelope = await seal(new TextEncoder().encode(canonicalize2(envelope)), envelopeKey);
18875
- let inlineUrl;
18876
- if (options.inline === true) {
18877
- try {
18878
- inlineUrl = await encodeInlineShareUrl({ origin: options.origin, ciphertext: sealedEnvelope.blob, key32: envelopeKey });
18879
- } catch (error) {
18880
- throw new SharePublishError("inline-too-large", error instanceof Error ? error.message : "inline share is too large");
18881
- }
18882
- }
18883
- const contentUpload = await uploadShareBlob(options, { blob: sealedContent.blob, cid: sealedContent.cid, deleteAfter: expiry, contentLength: sealedContent.blob.byteLength });
18884
- let url;
18885
- let kind;
18886
- let retention = contentUpload.deleteAfter;
18887
- if (options.inline === true) {
18888
- if (inlineUrl === void 0) throw new SharePublishError("inline-too-large", "inline share is too large");
18889
- url = inlineUrl;
18890
- kind = "inline";
18891
- } else {
18892
- const envelopeUpload = await uploadShareBlob(options, { blob: sealedEnvelope.blob, cid: sealedEnvelope.cid, deleteAfter: expiry, contentLength: sealedEnvelope.blob.byteLength });
18893
- url = encodeShareUrl({ origin: options.origin, ciphertextCid: envelopeUpload.cid, key32: envelopeKey });
18894
- kind = "compact";
18895
- retention = envelopeUpload.deleteAfter;
18896
- }
18897
- const metadata = {
18898
- protocol: "tinycloud-share",
18899
- version: 1,
18900
- shareId,
18901
- origin: options.origin,
18902
- target: { kind: "bearer", origin: options.origin, nodeAudience, spaceId },
18903
- resource: { kind: "exact", path },
18904
- actions: ["read"],
18905
- expiresAt: expiry,
18906
- display: { filename: options.filename },
18907
- content: { cid: sealedContent.cid }
18908
- };
18909
- const result = { protocol: "tinycloud-share", version: SHARE_PUBLISH_RESULT_VERSION, link: { kind, cid: sealedEnvelope.cid }, metadata, registryDeleteAfter: retention };
18910
- Object.defineProperty(result, "toJSON", {
18911
- enumerable: false,
18912
- value: () => redactPublishedShare(result)
18913
- });
18914
- Object.defineProperty(result, "url", { enumerable: false, value: url });
18915
- return result;
18916
- } finally {
18917
- sessionPrivateKey.fill(0);
18918
- senderPrivateKey.fill(0);
18919
- contentKey?.fill(0);
18920
- envelopeKey?.fill(0);
18921
- }
18018
+ };
18922
18019
  }
18923
18020
  function authorizationMethodForTarget(target) {
18924
18021
  if (target.kind === "recipientDid") return "openkey-device";
@@ -19218,8 +18315,8 @@ function targetAuthorizationMethod(target) {
19218
18315
  }
19219
18316
  async function publishTargetShare(input) {
19220
18317
  const target = normalizeShareTarget(input.target);
19221
- if (target.kind === "bearer") return publishShare(input);
19222
18318
  if (input.targetAdapter === void 0) {
18319
+ if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
19223
18320
  return {
19224
18321
  state: "authorization-required",
19225
18322
  method: targetAuthorizationMethod(target)
@@ -19229,9 +18326,9 @@ async function publishTargetShare(input) {
19229
18326
  const chunks = [];
19230
18327
  let size2 = 0;
19231
18328
  for await (const chunk of input.source) {
19232
- if (!(chunk instanceof Uint8Array)) throw new TypeError("addressed publication source yielded invalid bytes");
18329
+ if (!(chunk instanceof Uint8Array)) throw new TypeError("share publication source yielded invalid bytes");
19233
18330
  size2 += chunk.byteLength;
19234
- if (size2 > (input.maxBytes ?? 100 * 1024 * 1024)) throw new TypeError("addressed publication exceeds maxBytes");
18331
+ if (size2 > (input.maxBytes ?? SHARE_CONTENT_LIMIT)) throw new SharePublishError("max-bytes-exceeded", "share publication exceeds maxBytes");
19235
18332
  chunks.push(chunk.slice());
19236
18333
  }
19237
18334
  const bytes3 = new Uint8Array(size2);
@@ -19242,15 +18339,29 @@ async function publishTargetShare(input) {
19242
18339
  }
19243
18340
  return bytes3;
19244
18341
  })();
18342
+ if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
18343
+ if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
18344
+ throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
18345
+ }
18346
+ if (target.kind === "bearer" && input.allowBinary !== true) {
18347
+ try {
18348
+ new TextDecoder("utf-8", { fatal: true }).decode(source);
18349
+ } catch {
18350
+ throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
18351
+ }
18352
+ }
19245
18353
  const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
19246
18354
  const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
19247
18355
  let totalBytes = 0;
19248
18356
  for (const file of files) {
19249
18357
  totalBytes += file.bytes.byteLength;
19250
18358
  if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
19251
- throw new SharePublishError("max-bytes-exceeded", "addressed publication exceeds the combined byte limit");
18359
+ throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
19252
18360
  }
19253
18361
  }
18362
+ const nowMs2 = input.now?.() ?? Date.now();
18363
+ const expiresAt = input.expiresAt ?? new Date(nowMs2 + DEFAULT_SHARE_LIFETIME_MS);
18364
+ if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs2) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
19254
18365
  return input.targetAdapter.publish({
19255
18366
  source,
19256
18367
  filename: input.filename,
@@ -19259,18 +18370,8 @@ async function publishTargetShare(input) {
19259
18370
  ...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
19260
18371
  ...input.actions === void 0 ? {} : { actions: input.actions },
19261
18372
  target,
19262
- expiresAt: input.expiresAt ?? new Date((input.now?.() ?? Date.now()) + 7 * 24 * 60 * 60 * 1e3),
18373
+ expiresAt,
19263
18374
  origin: input.origin,
19264
- ...input.inline === void 0 ? {} : { inline: input.inline },
19265
- upload: {
19266
- ...input.registryBaseUrl === void 0 ? {} : { registryBaseUrl: input.registryBaseUrl },
19267
- ...input.fetchFn === void 0 ? {} : { fetchFn: input.fetchFn },
19268
- ...input.authorizeUpload === void 0 ? {} : { authorizeUpload: input.authorizeUpload },
19269
- ...input.authorizationOrigin === void 0 ? {} : { authorizationOrigin: input.authorizationOrigin },
19270
- ...input.credentials === void 0 ? {} : { credentials: input.credentials },
19271
- ...input.allowInsecureRegistry === void 0 ? {} : { allowInsecureRegistry: input.allowInsecureRegistry },
19272
- ...input.uploadBlob === void 0 ? {} : { uploadBlob: input.uploadBlob }
19273
- },
19274
18375
  ...input.notify === void 0 ? {} : { notify: input.notify }
19275
18376
  });
19276
18377
  }
@@ -19459,8 +18560,8 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
19459
18560
  ...result.metadata.display.filename === void 0 ? {} : { filename: result.metadata.display.filename }
19460
18561
  };
19461
18562
  }
19462
- function recipientMatchesShareRecord(record2, recipient) {
19463
- const matcher = record2.recipientMatcher;
18563
+ function recipientMatchesShareRecord(record, recipient) {
18564
+ const matcher = record.recipientMatcher;
19464
18565
  if (matcher.kind === "bearer" || matcher.kind === "recipientDid") return false;
19465
18566
  if (matcher.kind === "exactEmail") return matcher.value === recipient;
19466
18567
  const at = recipient.lastIndexOf("@");
@@ -19497,65 +18598,80 @@ async function defaultIdempotencyKey(shareId, recipient) {
19497
18598
  ));
19498
18599
  return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
19499
18600
  }
19500
- function targetKind2(record2) {
19501
- if (record2.targetKind !== void 0) return record2.targetKind;
19502
- return record2.recipientMatcher.kind === "exactEmail" ? "email" : record2.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record2.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
18601
+ function targetKind2(record) {
18602
+ if (record.targetKind !== void 0) return record.targetKind;
18603
+ return record.recipientMatcher.kind === "exactEmail" ? "email" : record.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
18604
+ }
18605
+ function policyNodeAudience(record) {
18606
+ const envelope = record.deliveryMaterial?.envelope;
18607
+ if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record.target.nodeAudience;
18608
+ const binding = envelope.attestedEnforcerBinding;
18609
+ if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record.target.nodeAudience;
18610
+ const nodeAudience = binding.nodeAudience;
18611
+ return typeof nodeAudience === "string" ? nodeAudience : record.target.nodeAudience;
19503
18612
  }
19504
18613
  async function revokeShare(input) {
19505
18614
  const target = targetKind2(input.record);
19506
- if (target === "bearer") return { state: "retention-only", target, reason: "bearer-capability-cannot-be-revoked" };
19507
18615
  if (input.adapter === void 0) return { state: "unsupported", target, reason: "node revocation authority is required", code: "unsupported-target" };
19508
18616
  const scope = input.scope ?? "direct";
19509
18617
  const delegationCid = scope === "ancestor" ? input.record.ownerDelegationCid : input.record.enforcementDelegationCid;
19510
18618
  if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
19511
- await input.adapter.revokeDelegation({ delegationCid, scope });
18619
+ if (target === "bearer") {
18620
+ if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
18621
+ await input.adapter.revokeDelegation({ delegationCid, scope });
18622
+ } else {
18623
+ if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
18624
+ if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
18625
+ await input.adapter.revokePolicyRoot({
18626
+ rootCid: delegationCid,
18627
+ targetRole: scope === "ancestor" ? "policy-authority" : "policy-enforcement",
18628
+ ownerDid: input.record.ownerDid,
18629
+ nodeOrigin: input.record.target.origin,
18630
+ nodeAudience: policyNodeAudience(input.record)
18631
+ });
18632
+ }
19512
18633
  const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
19513
18634
  if (input.records !== void 0) await input.records.put({ ...input.record, revokedAt });
19514
18635
  return { state: "revoked", target, delegationCid, revokedAt };
19515
18636
  }
19516
- function redactRecord(record2, revealLink, link2) {
19517
- const matcher = record2.recipientMatcher;
19518
- const revealedLink = revealLink ? link2 ?? record2.link : void 0;
18637
+ function redactRecord(record, revealLink, link2) {
18638
+ const matcher = record.recipientMatcher;
18639
+ const revealedLink = revealLink ? link2 ?? record.link : void 0;
19519
18640
  return {
19520
- shareId: record2.shareId,
18641
+ shareId: record.shareId,
19521
18642
  target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
19522
18643
  ...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
19523
- expiresAt: record2.expiresAt,
19524
- revoked: record2.revokedAt !== void 0,
18644
+ expiresAt: record.expiresAt,
18645
+ revoked: record.revokedAt !== void 0,
19525
18646
  ...revealedLink === void 0 ? {} : { link: revealedLink }
19526
18647
  };
19527
18648
  }
19528
18649
  async function listShares(storage) {
19529
18650
  const records = await storage.list();
19530
- return records.map((record2) => redactRecord(record2, false));
18651
+ return records.map((record) => redactRecord(record, false));
19531
18652
  }
19532
18653
  async function showShare(input) {
19533
- const record2 = await input.storage.get(input.shareId);
19534
- if (record2 === void 0) throw new Error("share not found");
19535
- return redactRecord(record2, input.revealLink === true, input.link);
19536
- }
19537
- function isLegacyShareLink(value) {
19538
- return value.startsWith("tc1:");
19539
- }
19540
- async function receiveLegacyShare(link2, reader) {
19541
- if (!isLegacyShareLink(link2)) throw new Error("legacy link must use tc1:");
19542
- return reader.read(link2);
18654
+ const record = await input.storage.get(input.shareId);
18655
+ if (record === void 0) throw new Error("share not found");
18656
+ return redactRecord(record, input.revealLink === true, input.link);
19543
18657
  }
19544
- async function migrateShare(input) {
19545
- const value = await receiveLegacyShare(input.link, input.reader);
19546
- return { protocol: "tinycloud-share", version: 1, legacy: true, value, migrated: await input.publish(value) };
18658
+ function parseNativeShareUrl(value) {
18659
+ const url = new URL(value);
18660
+ if (url.search || url.pathname !== "/viewer") throw new TypeError("native shares must carry tc1 only in the /viewer URL fragment");
18661
+ const fragment = new URLSearchParams(url.hash.slice(1));
18662
+ const token = fragment.get(NATIVE_SHARE_FRAGMENT_PARAMETER);
18663
+ if (!token || fragment.size !== 1) throw new TypeError("missing native share fragment");
18664
+ return token;
19547
18665
  }
19548
- var __defProp2, __export2, external_exports2, util2, objectUtil2, ZodParsedType2, getParsedType2, ZodIssueCode2, quotelessJson2, ZodError2, errorMap2, en_default2, overrideErrorMap2, makeIssue2, EMPTY_PATH2, ParseStatus2, INVALID2, DIRTY2, OK2, isAborted2, isDirty2, isValid2, isAsync2, errorUtil2, ParseInputLazyPath2, handleResult2, ZodType2, cuidRegex2, cuid2Regex2, ulidRegex2, uuidRegex2, nanoidRegex2, jwtRegex2, durationRegex2, emailRegex2, _emojiRegex2, emojiRegex2, ipv4Regex2, ipv4CidrRegex2, ipv6Regex2, ipv6CidrRegex2, base64Regex2, base64urlRegex2, dateRegexSource2, dateRegex2, ZodString2, ZodNumber2, ZodBigInt2, ZodBoolean2, ZodDate2, ZodSymbol2, ZodUndefined2, ZodNull2, ZodAny2, ZodUnknown2, ZodNever2, ZodVoid2, ZodArray2, ZodObject2, ZodUnion2, getDiscriminator2, ZodDiscriminatedUnion2, ZodIntersection2, ZodTuple2, ZodRecord2, ZodMap2, ZodSet2, ZodFunction2, ZodLazy2, ZodLiteral2, ZodEnum2, ZodNativeEnum2, ZodPromise2, ZodEffects2, ZodOptional2, ZodNullable2, ZodDefault2, ZodCatch2, ZodNaN2, BRAND2, ZodBranded2, ZodPipeline2, ZodReadonly2, late2, ZodFirstPartyTypeKind2, instanceOfType2, stringType2, numberType2, nanType2, bigIntType2, booleanType2, dateType2, symbolType2, undefinedType2, nullType2, anyType2, unknownType2, neverType2, voidType2, arrayType2, objectType2, strictObjectType2, unionType2, discriminatedUnionType2, intersectionType2, tupleType2, recordType2, mapType2, setType2, functionType2, lazyType2, literalType2, enumType2, nativeEnumType2, promiseType2, effectsType2, optionalType2, nullableType2, preprocessType2, pipelineType2, ostring2, onumber2, oboolean2, coerce2, NEVER2, empty, src, _brrp__multiformats_scope_baseX, base_x_default, Encoder, Decoder, ComposedDecoder, Codec, base32, base32upper, base32pad, base32padupper, base32hex, base32hexupper, base32hexpad, base32hexpadupper, base32z, base36, base36upper, base58btc, base58flickr, encode_1, MSB, REST, MSBALL, INT, decode2, MSB$1, REST$1, N1, N2, N3, N4, N5, N6, N7, N8, N9, length, varint, _brrp_varint, varint_default, Digest, cache, CID, DAG_PB_CODE, SHA_256_CODE, cidSymbol, code, SHA256_CODE, base64, base64pad, base64url, base64urlpad, ED25519_MULTICODEC_PREFIX, PUBLIC_KEY_LENGTH, base64UrlString, sessionJwkCommonFields, okpPrivateJwkSchema, ecPrivateJwkSchema, sessionJwkSchema, policyTargetSchema, bearerKeyTargetSchema, recipientDidTargetSchema, authorizationTargetSchema, resourceSelectorSchema, targetSchema, displaySchema, contentPointerSchema, signatureSchema, unsignedShareEnvelopeSchema, shareEnvelopeSchema, recipientMatcherSchema, shareActionSchema, kvContentSourceSchema, sqlContentSourceSchema, contentSourceSchema, v2TargetSchema, shareDecryptionSchema, ownerAuthoritySchema, contentMetadataSchema, unsignedShareEnvelopeV2BaseSchema, unsignedShareEnvelopeV2Schema, shareEnvelopeV2Schema, unifiedResourceSchema, unifiedEncryptionNetworkSchema, unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema, unifiedCapabilitySchema, unifiedContentSourceSchema, unifiedPolicyV1Schema, policyCredentialRequirementV1Schema, unifiedPolicyV2Schema, unifiedPolicySchema, unifiedRootSchema, attestedEnforcerBindingV2Schema, v3TargetSchema, unsignedShareEnvelopeV3BaseSchema, unsignedShareEnvelopeV3Schema, shareEnvelopeV3Schema, BEARER_READ_ABILITY, READ_ABILITIES, ED25519_VERIFY_OPTS, ENVELOPE_AAD_LABEL, SEALED_BLOB_VERSION, AAD, KEY_LENGTH, NONCE_LENGTH, TAG_LENGTH, HEADER_LENGTH, ED25519_VERIFY_OPTS2, ENVELOPE_SIGNATURE_DOMAIN, ENVELOPE_V2_SIGNATURE_DOMAIN, ENVELOPE_V3_SIGNATURE_DOMAIN, POLICY_V1_SIGNATURE_DOMAIN, POLICY_V2_SIGNATURE_DOMAIN, CONTENT_SOURCE_V1_DOMAIN, POLICY_CAPABILITY_V1_DOMAIN, NATIVE_PROJECTION_V1_DOMAIN, ATTESTED_ENFORCER_V2_DOMAIN, KEY_LENGTH2, INLINE_PREFIX, MAX_INLINE_BYTES, SHARE_RESULT_VERSION, DEFAULT_MAX_SEALED_BLOB_BYTES, DEFAULT_MAX_CONTENT_BLOB_BYTES, CONTENT_SEALED_OVERHEAD, ShareReceiveError, SHARE_CONTENT_LIMIT, SHARE_SEALED_OVERHEAD, SHARE_PUBLISH_RESULT_VERSION, DEFAULT_SHARE_LIFETIME_MS, SharePublishError, empty2, src2, _brrp__multiformats_scope_baseX2, base_x_default2, Encoder2, Decoder2, ComposedDecoder2, Codec2, base58btc2, base58flickr2, textEncoder, base322, base32upper2, base32pad2, base32padupper2, base32hex2, base32hexupper2, base32hexpad2, base32hexpadupper2, base32z2, base362, base36upper2, encode_12, MSB2, REST2, MSBALL2, INT2, decode6, MSB$12, REST$12, N12, N22, N32, N42, N52, N62, N72, N82, N92, length2, varint2, _brrp_varint2, varint_default2, Digest2, cache2, CID2, DAG_PB_CODE2, SHA_256_CODE2, cidSymbol2, MAX_CONTENT_BYTES, ShareNotifyError, SHARE_V2_PROTOCOL, DOMAIN, PRESENTATION_DOMAIN, SESSION_DOMAIN, INVOCATION_DOMAIN;
18666
+ var __defProp2, __export2, external_exports2, util2, objectUtil2, ZodParsedType2, getParsedType2, ZodIssueCode2, quotelessJson2, ZodError2, errorMap2, en_default2, overrideErrorMap2, makeIssue2, EMPTY_PATH2, ParseStatus2, INVALID2, DIRTY2, OK2, isAborted2, isDirty2, isValid2, isAsync2, errorUtil2, ParseInputLazyPath2, handleResult2, ZodType2, cuidRegex2, cuid2Regex2, ulidRegex2, uuidRegex2, nanoidRegex2, jwtRegex2, durationRegex2, emailRegex2, _emojiRegex2, emojiRegex2, ipv4Regex2, ipv4CidrRegex2, ipv6Regex2, ipv6CidrRegex2, base64Regex2, base64urlRegex2, dateRegexSource2, dateRegex2, ZodString2, ZodNumber2, ZodBigInt2, ZodBoolean2, ZodDate2, ZodSymbol2, ZodUndefined2, ZodNull2, ZodAny2, ZodUnknown2, ZodNever2, ZodVoid2, ZodArray2, ZodObject2, ZodUnion2, getDiscriminator2, ZodDiscriminatedUnion2, ZodIntersection2, ZodTuple2, ZodRecord2, ZodMap2, ZodSet2, ZodFunction2, ZodLazy2, ZodLiteral2, ZodEnum2, ZodNativeEnum2, ZodPromise2, ZodEffects2, ZodOptional2, ZodNullable2, ZodDefault2, ZodCatch2, ZodNaN2, BRAND2, ZodBranded2, ZodPipeline2, ZodReadonly2, late2, ZodFirstPartyTypeKind2, instanceOfType2, stringType2, numberType2, nanType2, bigIntType2, booleanType2, dateType2, symbolType2, undefinedType2, nullType2, anyType2, unknownType2, neverType2, voidType2, arrayType2, objectType2, strictObjectType2, unionType2, discriminatedUnionType2, intersectionType2, tupleType2, recordType2, mapType2, setType2, functionType2, lazyType2, literalType2, enumType2, nativeEnumType2, promiseType2, effectsType2, optionalType2, nullableType2, preprocessType2, pipelineType2, ostring2, onumber2, oboolean2, coerce2, NEVER2, empty, src, _brrp__multiformats_scope_baseX, base_x_default, Encoder, Decoder, ComposedDecoder, Codec, base32, base32upper, base32pad, base32padupper, base32hex, base32hexupper, base32hexpad, base32hexpadupper, base32z, base36, base36upper, base58btc, base58flickr, encode_1, MSB, REST, MSBALL, INT, decode2, MSB$1, REST$1, N1, N2, N3, N4, N5, N6, N7, N8, N9, length, varint, _brrp_varint, varint_default, Digest, cache, CID, DAG_PB_CODE, SHA_256_CODE, cidSymbol, code, SHA256_CODE, base64, base64pad, base64url, base64urlpad, ED25519_MULTICODEC_PREFIX, PUBLIC_KEY_LENGTH, base64UrlString, sessionJwkCommonFields, okpPrivateJwkSchema, ecPrivateJwkSchema, sessionJwkSchema, policyTargetSchema, bearerKeyTargetSchema, recipientDidTargetSchema, authorizationTargetSchema, resourceSelectorSchema, targetSchema, displaySchema, contentPointerSchema, signatureSchema, unsignedShareEnvelopeSchema, shareEnvelopeSchema, recipientMatcherSchema, shareActionSchema, kvContentSourceSchema, sqlContentSourceSchema, contentSourceSchema, v2TargetSchema, shareDecryptionSchema, ownerAuthoritySchema, contentMetadataSchema, unsignedShareEnvelopeV2BaseSchema, unsignedShareEnvelopeV2Schema, shareEnvelopeV2Schema, unifiedResourceSchema, unifiedEncryptionNetworkSchema, unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema, unifiedCapabilitySchema, unifiedContentSourceSchema, unifiedPolicyV1Schema, policyCredentialRequirementV1Schema, unifiedPolicyV2Schema, unifiedPolicySchema, unifiedRootSchema, attestedEnforcerBindingV2Schema, v3TargetSchema, unsignedShareEnvelopeV3BaseSchema, unsignedShareEnvelopeV3Schema, shareEnvelopeV3Schema, ENVELOPE_AAD_LABEL, AAD, ED25519_VERIFY_OPTS2, ENVELOPE_V3_SIGNATURE_DOMAIN, POLICY_V1_SIGNATURE_DOMAIN, POLICY_V2_SIGNATURE_DOMAIN, CONTENT_SOURCE_V1_DOMAIN, POLICY_CAPABILITY_V1_DOMAIN, NATIVE_PROJECTION_V1_DOMAIN, ATTESTED_ENFORCER_V2_DOMAIN, KEY_LENGTH2, INLINE_PREFIX, PUBLIC_INLINE_PARAMETER, MAX_INLINE_BYTES, SHARE_RESULT_VERSION, DEFAULT_MAX_SEALED_BLOB_BYTES, ShareReceiveError, SHARE_CONTENT_LIMIT, SHARE_PUBLISH_RESULT_VERSION, DEFAULT_SHARE_LIFETIME_MS, SharePublishError, empty2, src2, _brrp__multiformats_scope_baseX2, base_x_default2, Encoder2, Decoder2, ComposedDecoder2, Codec2, base58btc2, base58flickr2, textEncoder, base322, base32upper2, base32pad2, base32padupper2, base32hex2, base32hexupper2, base32hexpad2, base32hexpadupper2, base32z2, base362, base36upper2, encode_12, MSB2, REST2, MSBALL2, INT2, decode6, MSB$12, REST$12, N12, N22, N32, N42, N52, N62, N72, N82, N92, length2, varint2, _brrp_varint2, varint_default2, Digest2, cache2, CID2, DAG_PB_CODE2, SHA_256_CODE2, cidSymbol2, MAX_CONTENT_BYTES, ShareNotifyError, SHARE_V2_PROTOCOL, DOMAIN, PRESENTATION_DOMAIN, SESSION_DOMAIN, INVOCATION_DOMAIN, NATIVE_SHARE_FRAGMENT_PARAMETER;
19549
18667
  var init_dist3 = __esm({
19550
18668
  "../share-sdk/dist/index.js"() {
19551
18669
  "use strict";
19552
18670
  init_sha2();
19553
18671
  init_ed25519();
19554
- init_ed25519();
19555
18672
  init_blake3();
19556
18673
  init_ed25519();
19557
18674
  init_sha256();
19558
- init_ed25519();
19559
18675
  __defProp2 = Object.defineProperty;
19560
18676
  __export2 = (target, all) => {
19561
18677
  for (var name2 in all)
@@ -24162,19 +23278,9 @@ var init_dist3 = __esm({
24162
23278
  }).strict();
24163
23279
  unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
24164
23280
  shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
24165
- BEARER_READ_ABILITY = "kv/get";
24166
- READ_ABILITIES = /* @__PURE__ */ new Set([BEARER_READ_ABILITY]);
24167
- ED25519_VERIFY_OPTS = { zip215: false };
24168
23281
  ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
24169
- SEALED_BLOB_VERSION = 1;
24170
23282
  AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
24171
- KEY_LENGTH = 32;
24172
- NONCE_LENGTH = 12;
24173
- TAG_LENGTH = 16;
24174
- HEADER_LENGTH = 1;
24175
23283
  ED25519_VERIFY_OPTS2 = { zip215: false };
24176
- ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
24177
- ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
24178
23284
  ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
24179
23285
  POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
24180
23286
  POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
@@ -24183,12 +23289,11 @@ var init_dist3 = __esm({
24183
23289
  NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
24184
23290
  ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
24185
23291
  KEY_LENGTH2 = 32;
24186
- INLINE_PREFIX = "#v=2&p=";
23292
+ INLINE_PREFIX = "#tc2=";
23293
+ PUBLIC_INLINE_PARAMETER = "tc2";
24187
23294
  MAX_INLINE_BYTES = 256 * 1024;
24188
23295
  SHARE_RESULT_VERSION = 1;
24189
23296
  DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
24190
- DEFAULT_MAX_CONTENT_BLOB_BYTES = DEFAULT_MAX_SEALED_BLOB_BYTES;
24191
- CONTENT_SEALED_OVERHEAD = 1 + 12 + 16;
24192
23297
  ShareReceiveError = class extends Error {
24193
23298
  code;
24194
23299
  details;
@@ -24198,13 +23303,11 @@ var init_dist3 = __esm({
24198
23303
  this.code = code3;
24199
23304
  this.details = details;
24200
23305
  }
24201
- /** Machine output is deliberately code-only; diagnostics belong on stderr. */
24202
23306
  toJSON() {
24203
23307
  return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: this.code } };
24204
23308
  }
24205
23309
  };
24206
23310
  SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
24207
- SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
24208
23311
  SHARE_PUBLISH_RESULT_VERSION = 1;
24209
23312
  DEFAULT_SHARE_LIFETIME_MS = 7 * 24 * 60 * 60 * 1e3;
24210
23313
  SharePublishError = class extends Error {
@@ -24696,6 +23799,7 @@ var init_dist3 = __esm({
24696
23799
  PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
24697
23800
  SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
24698
23801
  INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
23802
+ NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
24699
23803
  }
24700
23804
  });
24701
23805
 
@@ -25848,16 +24952,26 @@ async function verifyLocationRecord(input) {
25848
24952
  }
25849
24953
  return false;
25850
24954
  }
25851
- async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
24955
+ async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
25852
24956
  const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
25853
- const response = await fetchFn(url);
24957
+ const response = await fetchFn(url, {
24958
+ redirect: "error",
24959
+ headers: { accept: "application/json" },
24960
+ ...signal === void 0 ? {} : { signal }
24961
+ });
25854
24962
  if (response.status === 404) {
25855
24963
  return null;
25856
24964
  }
25857
24965
  if (!response.ok) {
25858
24966
  throw new Error(`location registry returned HTTP ${response.status}`);
25859
24967
  }
25860
- const body = await response.json();
24968
+ const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
24969
+ let body;
24970
+ try {
24971
+ body = JSON.parse(text2);
24972
+ } catch {
24973
+ throw new LocationRecordValidationError("registry response is not JSON");
24974
+ }
25861
24975
  if (body.record === void 0) {
25862
24976
  throw new LocationRecordValidationError("registry response missing record");
25863
24977
  }
@@ -26033,6 +25147,38 @@ function multiaddrToHttpUrl(input) {
26033
25147
  }
26034
25148
  return uri;
26035
25149
  }
25150
+ async function boundedResponseText(response, label, maxBytes) {
25151
+ const declaredLength = response.headers.get("content-length");
25152
+ if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
25153
+ throw new LocationRecordValidationError(`${label} response is too large`);
25154
+ }
25155
+ if (response.body === null) return "";
25156
+ const reader = response.body.getReader();
25157
+ const chunks = [];
25158
+ let total = 0;
25159
+ try {
25160
+ while (true) {
25161
+ const { done, value } = await reader.read();
25162
+ if (done) break;
25163
+ if (value === void 0) continue;
25164
+ total += value.byteLength;
25165
+ if (total > maxBytes) {
25166
+ await reader.cancel().catch(() => void 0);
25167
+ throw new LocationRecordValidationError(`${label} response is too large`);
25168
+ }
25169
+ chunks.push(value);
25170
+ }
25171
+ } finally {
25172
+ reader.releaseLock();
25173
+ }
25174
+ const bytes2 = new Uint8Array(total);
25175
+ let offset = 0;
25176
+ for (const chunk of chunks) {
25177
+ bytes2.set(chunk, offset);
25178
+ offset += chunk.byteLength;
25179
+ }
25180
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
25181
+ }
26036
25182
  function validateSubject(subject) {
26037
25183
  if (typeof subject !== "string" || subject.length === 0) {
26038
25184
  throw new LocationRecordValidationError(
@@ -29246,7 +28392,7 @@ var init_profiles = __esm({
29246
28392
  // src/commands/account.ts
29247
28393
  init_profiles();
29248
28394
  init_constants();
29249
- import open2 from "open";
28395
+ import open from "open";
29250
28396
  import { readFile as readFile3 } from "fs/promises";
29251
28397
 
29252
28398
  // src/lib/sdk.ts
@@ -30617,7 +29763,7 @@ function registerAccountCommand(program) {
30617
29763
  billing.command(name2).description(`${name2 === "status" ? "Show" : "Open"} account billing page`).option("--open", "Open account.tinycloud.xyz in your browser").action(async (options) => {
30618
29764
  try {
30619
29765
  if (options.open) {
30620
- await open2(ACCOUNT_BILLING_URL);
29766
+ await open(ACCOUNT_BILLING_URL);
30621
29767
  }
30622
29768
  outputJson({ url: ACCOUNT_BILLING_URL, opened: Boolean(options.open) });
30623
29769
  } catch (error) {
@@ -30909,8 +30055,8 @@ async function callbackFlow(did, options = {}) {
30909
30055
  }
30910
30056
  if (openBrowser) {
30911
30057
  try {
30912
- const open4 = (await import("open")).default;
30913
- await open4(authUrl);
30058
+ const open3 = (await import("open")).default;
30059
+ await open3(authUrl);
30914
30060
  } catch {
30915
30061
  server.close();
30916
30062
  throw new Error("Failed to open browser");
@@ -30999,7 +30145,7 @@ function ensureWasm() {
30999
30145
  wasmInitialized = true;
31000
30146
  }
31001
30147
  }
31002
- function generateKey2() {
30148
+ function generateKey() {
31003
30149
  ensureWasm();
31004
30150
  const mgr = new TCWSessionManager();
31005
30151
  const keyId = mgr.createSessionKey("cli");
@@ -31262,7 +30408,7 @@ async function ensureShareDeviceAuthorization(input) {
31262
30408
  }
31263
30409
  let key = await ProfileManager.getKey(input.profileName);
31264
30410
  if (!key) {
31265
- const generated = generateKey2();
30411
+ const generated = generateKey();
31266
30412
  key = generated.jwk;
31267
30413
  await ProfileManager.setKey(input.profileName, key);
31268
30414
  }
@@ -32324,7 +31470,7 @@ async function rotateAuthKey(profileName, host, options = {}) {
32324
31470
  return;
32325
31471
  }
32326
31472
  const { jwk, did } = await withSpinner("Generating session key...", async () => {
32327
- return generateKey2();
31473
+ return generateKey();
32328
31474
  });
32329
31475
  await ProfileManager.setKey(profileName, jwk);
32330
31476
  await ProfileManager.clearSession(profileName);
@@ -32407,7 +31553,7 @@ async function handleLocalAuth(profileName, host, options = {}) {
32407
31553
  const hasKey = await ProfileManager.getKey(profileName);
32408
31554
  if (options.forceSessionKey || !hasKey) {
32409
31555
  const { jwk, did: generatedSessionDid } = await withSpinner("Generating session key...", async () => {
32410
- return generateKey2();
31556
+ return generateKey();
32411
31557
  });
32412
31558
  await ProfileManager.setKey(profileName, jwk);
32413
31559
  sessionDid = generatedSessionDid;
@@ -33122,7 +32268,7 @@ function registerInitCommand(program) {
33122
32268
  }
33123
32269
  await ProfileManager.ensureConfigDir();
33124
32270
  const { jwk, did } = await withSpinner("Generating key...", async () => {
33125
- return generateKey2();
32271
+ return generateKey();
33126
32272
  });
33127
32273
  await ProfileManager.setKey(profileName, jwk);
33128
32274
  const profileConfig = {
@@ -33642,7 +32788,7 @@ function registerProfileCommand(program) {
33642
32788
  throw new CLIError("PROFILE_EXISTS", `Profile "${name2}" already exists`, ExitCode.ERROR);
33643
32789
  }
33644
32790
  await ProfileManager.ensureConfigDir();
33645
- const { jwk, did } = generateKey2();
32791
+ const { jwk, did } = generateKey();
33646
32792
  await ProfileManager.setKey(name2, jwk);
33647
32793
  await ProfileManager.setProfile(name2, {
33648
32794
  name: name2,
@@ -34777,8 +33923,8 @@ function registerSecretsCommand(program, openKeyAcquisition) {
34777
33923
  });
34778
33924
  secrets.command("manage").description("Open the TinyCloud Secrets Manager in your browser").action(async () => {
34779
33925
  try {
34780
- const open4 = (await import("open")).default;
34781
- await open4("https://secrets.tinycloud.xyz");
33926
+ const open3 = (await import("open")).default;
33927
+ await open3("https://secrets.tinycloud.xyz");
34782
33928
  outputJson({ opened: "https://secrets.tinycloud.xyz" });
34783
33929
  } catch (error) {
34784
33930
  handleError(error);
@@ -34788,7 +33934,6 @@ function registerSecretsCommand(program, openKeyAcquisition) {
34788
33934
 
34789
33935
  // src/commands/share.ts
34790
33936
  init_dist3();
34791
- import { readFile as readFile10 } from "fs/promises";
34792
33937
  init_errors();
34793
33938
 
34794
33939
  // src/share/output.ts
@@ -34828,7 +33973,7 @@ function receiveJson(result, path) {
34828
33973
 
34829
33974
  // src/share/io.ts
34830
33975
  import { constants } from "fs";
34831
- import { lstat, mkdir as mkdir3, mkdtemp, open as open3, readFile as readFile9, realpath, stat as stat2, link, rename as rename2, rm as rm2, unlink } from "fs/promises";
33976
+ import { lstat, mkdir as mkdir3, mkdtemp, open as open2, readFile as readFile9, realpath, stat as stat2, link, rename as rename2, rm as rm2, unlink } from "fs/promises";
34832
33977
  import { randomBytes as randomBytes4 } from "crypto";
34833
33978
  import { basename as basename2, join as join6, resolve as resolve2, sep } from "path";
34834
33979
  var MAX_SHARE_STDIN_BYTES = 100 * 1024 * 1024;
@@ -34896,7 +34041,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
34896
34041
  const outputDirectory = resolve2(directory);
34897
34042
  await assertDirectory(outputDirectory);
34898
34043
  const safeName = safeFilename(filename);
34899
- const directoryHandle = await open3(outputDirectory, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0));
34044
+ const directoryHandle = await open2(outputDirectory, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0));
34900
34045
  const stableDirectory = await realpath(outputDirectory);
34901
34046
  const outputPath = join6(stableDirectory, safeName);
34902
34047
  const directoryIdentity = await directoryHandle.stat();
@@ -34920,7 +34065,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
34920
34065
  if (error.code !== "ENOENT") throw error;
34921
34066
  }
34922
34067
  temporaryPath = stagingPath;
34923
- handle = await open3(temporaryPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 384);
34068
+ handle = await open2(temporaryPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 384);
34924
34069
  await handle.writeFile(bytes);
34925
34070
  await handle.close();
34926
34071
  handle = void 0;
@@ -34950,8 +34095,6 @@ async function writeShareOutput(directory, filename, bytes, force) {
34950
34095
 
34951
34096
  // src/commands/share.ts
34952
34097
  var SHARE_ORIGIN = "https://share.tinycloud.xyz";
34953
- var DEFAULT_REGISTRY = `${SHARE_ORIGIN}/api/share/link-only/registry`;
34954
- var DEFAULT_READ_REGISTRY = "https://registry.tinycloud.xyz";
34955
34098
  var shareServices = {};
34956
34099
  function parseShareTarget(value) {
34957
34100
  if (value === "anyone" || value === "bearer") return { kind: "bearer" };
@@ -34961,26 +34104,11 @@ function parseShareTarget(value) {
34961
34104
  if (value.includes("@")) return { kind: "email", address: value };
34962
34105
  throw new CLIError("INVALID_ARGUMENT", "--to must be anyone, a did:, an email address, or domain:example.com", 2);
34963
34106
  }
34964
- function publishServices(insecureLocalRegistry = false) {
34965
- return {
34966
- ...shareServices.uploadBlob === void 0 ? {} : { uploadBlob: shareServices.uploadBlob },
34967
- ...shareServices.authorizeUpload === void 0 ? {} : { authorizeUpload: shareServices.authorizeUpload },
34968
- ...shareServices.authorizeUpload === void 0 || insecureLocalRegistry ? {} : { authorizationOrigin: SHARE_ORIGIN },
34969
- ...shareServices.credentials === void 0 ? {} : { credentials: shareServices.credentials },
34970
- ...shareServices.fetchFn === void 0 ? {} : { fetchFn: shareServices.fetchFn }
34971
- };
34972
- }
34973
- function fetchServices() {
34974
- return {
34975
- ...shareServices.fetchFn === void 0 ? {} : { fetchFn: shareServices.fetchFn },
34976
- ...shareServices.trustedPolicyAuthority === void 0 ? {} : { trustedPolicyAuthority: shareServices.trustedPolicyAuthority }
34977
- };
34978
- }
34979
34107
  function shareCliError(error) {
34980
34108
  if (error instanceof CLIError) return error;
34981
34109
  if (error instanceof SharePublishError) {
34982
- const exit = error.code === "upload-auth-required" ? 3 : error.code === "upload-failed" ? 4 : error.code === "max-bytes-exceeded" || error.code === "inline-too-large" ? 7 : error.code === "unsupported-target" || error.code === "invalid-argument" ? 2 : 1;
34983
- const code3 = error.code === "upload-auth-required" ? "UPLOAD_AUTH_REQUIRED" : error.code === "upload-failed" ? "UNAVAILABLE" : error.code === "max-bytes-exceeded" ? "MAX_BYTES_EXCEEDED" : error.code === "inline-too-large" ? "INLINE_TOO_LARGE" : error.code === "unsupported-target" ? "UNSUPPORTED_LINK" : error.code === "invalid-argument" ? "INVALID_ARGUMENT" : "ERROR";
34110
+ const exit = error.code === "authority-required" ? 3 : error.code === "max-bytes-exceeded" ? 7 : 2;
34111
+ const code3 = error.code === "authority-required" ? "AUTH_REQUIRED" : error.code === "max-bytes-exceeded" ? "MAX_BYTES_EXCEEDED" : "INVALID_ARGUMENT";
34984
34112
  return new CLIError(code3, error.message, exit);
34985
34113
  }
34986
34114
  if (error instanceof ShareReceiveError) {
@@ -35011,6 +34139,15 @@ function inputUrl(value, stdin) {
35011
34139
  if (value === void 0 || value.length === 0) throw new CLIError("INVALID_ARGUMENT", "a share URL or - is required", 2);
35012
34140
  return Promise.resolve(value);
35013
34141
  }
34142
+ async function inspectShareInputOnce(value, stdin, expectedOrigin, dependencies = {}) {
34143
+ const link2 = stdin || value === "-" ? await (dependencies.read ?? readBoundedUrlStdin)() : await inputUrl(value, false);
34144
+ try {
34145
+ parseNativeShareUrl(link2);
34146
+ } catch {
34147
+ return (dependencies.inspect ?? inspectShare)(link2, { expectedOrigin });
34148
+ }
34149
+ throw new CLIError("UNSUPPORTED_LINK", "native bearer links are opaque; receive the link to verify access", 2);
34150
+ }
35014
34151
  function jsonOutput(options, command) {
35015
34152
  return options.json === true || command.optsWithGlobals().json === true;
35016
34153
  }
@@ -35041,19 +34178,6 @@ function requestedActions(values) {
35041
34178
  if (actions.some((value) => value !== "read" && value !== "list" && value !== "edit")) throw new CLIError("INVALID_ARGUMENT", "--action must be read, list, or edit", 2);
35042
34179
  return [...new Set(actions)];
35043
34180
  }
35044
- async function authorizationProof(options) {
35045
- const encoded = options.authorizationProofFile === void 0 ? void 0 : await readFile10(options.authorizationProofFile, "utf8").catch(() => {
35046
- throw new CLIError("INVALID_ARGUMENT", "authorization proof file could not be read", 2);
35047
- });
35048
- if (encoded === void 0) return void 0;
35049
- try {
35050
- const value = JSON.parse(encoded);
35051
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("object");
35052
- return value;
35053
- } catch {
35054
- throw new CLIError("INVALID_ARGUMENT", "authorization proof must be a JSON object", 2);
35055
- }
35056
- }
35057
34181
  function assertAggregateInputLimit(inputs, maxBytes) {
35058
34182
  const limit = maxBytes ?? MAX_SHARE_STDIN_BYTES;
35059
34183
  let total = 0;
@@ -35064,10 +34188,9 @@ function assertAggregateInputLimit(inputs, maxBytes) {
35064
34188
  }
35065
34189
  function registerShareCommand(program) {
35066
34190
  const share = program.command("share").description("Publish and consume TinyCloud Share links");
35067
- share.command("publish <files...>").description("Publish one or more bounded files as a Share").option("--name <filename>", "Filename for stdin input").option("--to <target>", "Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Share lifetime", "7d").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--inline", "Embed the sealed envelope in the URL fragment").option("--compact", "Use a CID-addressed compact link (default)").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Authenticated registry upload endpoint", DEFAULT_REGISTRY).option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).option("--insecure-registry", "Allow an explicit localhost HTTP registry for hermetic tests").action(async (files, options, command) => {
34191
+ share.command("publish <files...>").description("Publish one or more bounded files as a Share").option("--name <filename>", "Filename for stdin input").option("--to <target>", "Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Share lifetime", "7d").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).action(async (files, options, command) => {
35068
34192
  try {
35069
34193
  const json = jsonOutput(options, command);
35070
- if (options.inline && options.compact) throw new CLIError("INVALID_ARGUMENT", "--inline and --compact are mutually exclusive", 2);
35071
34194
  const maxBytes = byteLimit(options.maxBytes);
35072
34195
  if (files.length === 0 || files.includes("-") && files.length > 1) throw new CLIError("INVALID_ARGUMENT", "stdin must be the only publish input", 2);
35073
34196
  const inputs = await Promise.all(files.map((file) => readShareInput(file, files.length === 1 ? options.name : void 0, maxBytes)));
@@ -35089,13 +34212,9 @@ function registerShareCommand(program) {
35089
34212
  actions,
35090
34213
  expiresAt: expires(options.expires),
35091
34214
  origin: options.viewerOrigin,
35092
- inline: options.inline === true,
35093
34215
  ...maxBytes === void 0 ? {} : { maxBytes },
35094
- registryBaseUrl: options.registry,
35095
- allowInsecureRegistry: options.insecureRegistry === true,
35096
34216
  notify: options.notify === true,
35097
- targetAdapter: shareServices.targetAdapter,
35098
- ...publishServices(options.insecureRegistry === true)
34217
+ targetAdapter: shareServices.targetAdapter
35099
34218
  });
35100
34219
  if ("state" in result) {
35101
34220
  if (json) {
@@ -35117,44 +34236,44 @@ function registerShareCommand(program) {
35117
34236
  handleError(shareCliError(error));
35118
34237
  }
35119
34238
  });
35120
- share.command("inspect [url]").description("Verify a share link and print safe metadata").option("--stdin", "Read the complete URL from stdin").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Registry read endpoint", DEFAULT_READ_REGISTRY).option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options, command) => {
34239
+ share.command("inspect [url]").description("Verify a share link and print safe metadata").option("--stdin", "Read the complete URL from stdin").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options, command) => {
35121
34240
  try {
35122
34241
  const json = jsonOutput(options, command);
35123
- const link2 = await inputUrl(url, options.stdin === true);
35124
- const result = await inspectShare(link2, { registryBaseUrl: options.registry, expectedOrigin: options.viewerOrigin, ...fetchServices() });
34242
+ const result = await inspectShareInputOnce(url, options.stdin === true, options.viewerOrigin);
35125
34243
  if (json) writeJson2(result);
35126
34244
  else inspectHuman(result);
35127
34245
  } catch (error) {
35128
34246
  handleError(shareCliError(error));
35129
34247
  }
35130
34248
  });
35131
- share.command("receive [url]").description("Verify and receive a share link").option("--stdin", "Read the complete URL from stdin").option("--output <directory>", "Create the file in this directory").option("--stdout", "Write verified plaintext bytes to stdout").option("--force", "Allow replacing an existing non-symlink output").option("--max-bytes <bytes>", "Bound received content bytes").option("--resume-token <token>", "Resume a previously returned recipient authorization step").option("--authorization-proof-file <path>", "Read the JSON authorization proof from a file").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Registry read endpoint", DEFAULT_READ_REGISTRY).option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).option("--legacy", "Read a legacy tc1: link (read-only)").action(async (url, options, command) => {
34249
+ share.command("receive [url]").description("Verify and receive a share link").option("--stdin", "Read the complete URL from stdin").option("--output <directory>", "Create the file in this directory").option("--stdout", "Write verified plaintext bytes to stdout").option("--force", "Allow replacing an existing non-symlink output").option("--max-bytes <bytes>", "Bound received content bytes").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options, command) => {
35132
34250
  try {
35133
34251
  const json = jsonOutput(options, command);
35134
34252
  if (options.stdout && json) throw new CLIError("INVALID_ARGUMENT", "--stdout and --json are mutually exclusive", 2);
35135
34253
  const maxBytes = byteLimit(options.maxBytes);
35136
34254
  const link2 = await inputUrl(url, options.stdin === true);
35137
- const proof = await authorizationProof(options);
35138
- if (options.legacy) {
35139
- if (!isLegacyShareLink(link2) || shareServices.legacyReader === void 0) throw new CLIError("UNSUPPORTED_LINK", "legacy receive requires an installed read-only tc1 adapter", 2);
35140
- const bytes = await receiveLegacyShare(link2, shareServices.legacyReader);
34255
+ let nativeLink = false;
34256
+ try {
34257
+ parseNativeShareUrl(link2);
34258
+ nativeLink = true;
34259
+ } catch {
34260
+ }
34261
+ if (nativeLink) {
34262
+ if (shareServices.nativeReader === void 0) throw new CLIError("AUTH_REQUIRED", "native TinyCloud receive is not configured", 3);
34263
+ const native = await shareServices.nativeReader(link2);
34264
+ if (maxBytes !== void 0 && native.bytes.byteLength > maxBytes) throw new CLIError("MAX_BYTES_EXCEEDED", "shared content exceeds max-bytes", 7);
35141
34265
  if (options.stdout) {
35142
- process.stdout.write(Buffer.from(bytes));
34266
+ process.stdout.write(Buffer.from(native.bytes));
35143
34267
  return;
35144
34268
  }
35145
- const output3 = await writeShareOutput(options.output ?? ".", "share.md", bytes, options.force === true);
35146
- if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, path: output3 });
34269
+ const output3 = await writeShareOutput(options.output ?? ".", native.filename, native.bytes, options.force === true);
34270
+ if (json) writeJson2({ protocol: "tinycloud-share", version: 1, path: output3, transport: "native" });
35147
34271
  else receiveHuman(output3);
35148
34272
  return;
35149
34273
  }
35150
34274
  const result = await receiveShare(link2, {
35151
- registryBaseUrl: options.registry,
35152
34275
  expectedOrigin: options.viewerOrigin,
35153
- ...fetchServices(),
35154
- ...maxBytes === void 0 ? {} : { maxContentBlobBytes: maxBytes },
35155
- ...shareServices.authorization === void 0 ? {} : { authorization: shareServices.authorization },
35156
- ...options.resumeToken === void 0 ? {} : { authorizationResumeToken: options.resumeToken },
35157
- ...proof === void 0 ? {} : { authorizationProof: proof }
34276
+ ...maxBytes === void 0 ? {} : { maxContentBlobBytes: maxBytes }
35158
34277
  });
35159
34278
  if ("state" in result) {
35160
34279
  if (json) {
@@ -35175,50 +34294,6 @@ function registerShareCommand(program) {
35175
34294
  handleError(shareCliError(error));
35176
34295
  }
35177
34296
  });
35178
- share.command("migrate [url]").description("Read a legacy tc1 link and re-mint a modern Share link").option("--stdin", "Read the complete legacy link from stdin").option("--name <filename>", "Filename for the migrated content", "migrated.md").option("--to <target>", "Modern Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Modern share lifetime", "7d").option("--max-bytes <bytes>", "Bound migrated content bytes").option("--inline", "Embed the sealed envelope in the URL fragment").option("--registry <url>", "Authenticated registry upload endpoint", DEFAULT_REGISTRY).option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).option("--insecure-registry", "Allow an explicit localhost HTTP registry for hermetic tests").option("--json", "Print versioned redacted JSON").action(async (url, options, command) => {
35179
- try {
35180
- const json = jsonOutput(options, command);
35181
- if (shareServices.legacyReader === void 0) throw new CLIError("UNSUPPORTED_LINK", "legacy migration requires an installed read-only tc1 adapter", 2);
35182
- const link2 = await inputUrl(url, options.stdin === true);
35183
- if (!isLegacyShareLink(link2)) throw new CLIError("UNSUPPORTED_LINK", "only tc1: links can be migrated", 2);
35184
- const maxBytes = byteLimit(options.maxBytes) ?? MAX_SHARE_STDIN_BYTES;
35185
- const migrated = await migrateShare({
35186
- link: link2,
35187
- reader: shareServices.legacyReader,
35188
- publish: async (bytes) => {
35189
- if (bytes.byteLength > maxBytes) throw new SharePublishError("max-bytes-exceeded", "legacy content exceeds the configured byte limit");
35190
- const result = await publishTargetShare({
35191
- source: bytes,
35192
- filename: options.name,
35193
- mediaType: "text/markdown",
35194
- target: parseShareTarget(options.to),
35195
- expiresAt: expires(options.expires),
35196
- origin: options.viewerOrigin,
35197
- inline: options.inline === true,
35198
- registryBaseUrl: options.registry,
35199
- allowInsecureRegistry: options.insecureRegistry === true,
35200
- notify: options.notify === true,
35201
- targetAdapter: shareServices.targetAdapter,
35202
- ...publishServices(options.insecureRegistry === true)
35203
- });
35204
- if ("state" in result) throw new CLIError(result.method === "openkey-device" ? "DEVICE_AUTH_REQUIRED" : "CLAIM_REQUIRED", "recipient authorization is required; continue through the configured authority adapter", 6);
35205
- const record = await rememberPublishedShare(result);
35206
- if (options.notify === true) {
35207
- const target = parseShareTarget(options.to);
35208
- if (target.kind !== "email") throw new CLIError("INVALID_ARGUMENT", "--notify requires an exact email target", 2);
35209
- if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
35210
- const delivery = await notifyShare({ shareId: record.shareId, recipient: target.address, record, adapter: shareServices.delivery });
35211
- if (delivery.state === "partial-failure") process.exitCode = 9;
35212
- }
35213
- return result;
35214
- }
35215
- });
35216
- if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, migrated: redactPublishedShare(migrated.migrated) });
35217
- else publishHuman(migrated.migrated);
35218
- } catch (error) {
35219
- handleError(shareCliError(error));
35220
- }
35221
- });
35222
34297
  share.command("list").description("List encrypted sender history without complete bearer URLs").option("--json", "Print versioned redacted JSON").action(async (options, command) => {
35223
34298
  try {
35224
34299
  const json = jsonOutput(options, command);
@@ -36127,7 +35202,7 @@ init_profiles();
36127
35202
  init_formatter();
36128
35203
  init_errors();
36129
35204
  init_constants();
36130
- import { readFile as readFile11 } from "fs/promises";
35205
+ import { readFile as readFile10 } from "fs/promises";
36131
35206
  import { writeFile as writeFile8 } from "fs/promises";
36132
35207
  import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
36133
35208
  async function readStdin4() {
@@ -36185,7 +35260,7 @@ function registerVaultCommand(program) {
36185
35260
  throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
36186
35261
  }
36187
35262
  if (options.file) {
36188
- putValue = new Uint8Array(await readFile11(options.file));
35263
+ putValue = new Uint8Array(await readFile10(options.file));
36189
35264
  } else if (options.stdin) {
36190
35265
  putValue = new Uint8Array(await readStdin4());
36191
35266
  } else {
@@ -36304,7 +35379,7 @@ init_profiles();
36304
35379
  init_formatter();
36305
35380
  init_errors();
36306
35381
  init_constants();
36307
- import { readFile as readFile12 } from "fs/promises";
35382
+ import { readFile as readFile11 } from "fs/promises";
36308
35383
  import { writeFile as writeFile9 } from "fs/promises";
36309
35384
  var VARIABLES_PREFIX = "variables/";
36310
35385
  async function readStdin5() {
@@ -36405,7 +35480,7 @@ function registerVarsCommand(program) {
36405
35480
  throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
36406
35481
  }
36407
35482
  if (options.file) {
36408
- varValue = await readFile12(options.file, "utf-8");
35483
+ varValue = await readFile11(options.file, "utf-8");
36409
35484
  } else if (options.stdin) {
36410
35485
  varValue = (await readStdin5()).toString("utf-8");
36411
35486
  } else {