@tinycloud/sdk-core 3.0.0-beta.8 → 3.0.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8879,25 +8879,33 @@ function parsePublishedShareLink(link, options2 = {}) {
8879
8879
  }
8880
8880
  const origin2 = normalizeOrigin(url.origin);
8881
8881
  if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
8882
- if (url.search !== "" || url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
8883
- if (url.pathname === "/s/inline" && url.hash.startsWith("#v=2&p=")) {
8882
+ if (url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
8883
+ if (url.pathname === "/viewer") {
8884
+ const secretPayload = url.search === "" && url.hash.startsWith("#tc2=") ? url.hash.slice("#tc2=".length) : void 0;
8885
+ const publicPayload = url.hash === "" && url.searchParams.size === 1 ? url.searchParams.get("tc2") ?? void 0 : void 0;
8886
+ if (secretPayload === void 0 && publicPayload === void 0) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
8887
+ if (publicPayload !== void 0 && url.search !== `?tc2=${publicPayload}`) throw new ShareEnvelopeError("invalid-link", "published share link is not canonical");
8884
8888
  let payload;
8885
8889
  try {
8886
- const bytes2 = base64UrlToBytes(url.hash.slice("#v=2&p=".length));
8890
+ const bytes2 = base64UrlToBytes(secretPayload ?? publicPayload);
8887
8891
  if (bytes2.length > (options2.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
8888
- const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
8892
+ const text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
8893
+ const value = JSON.parse(text2);
8889
8894
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("inline payload shape");
8895
+ if (canonicalJson(value) !== text2) throw new Error("inline payload is not canonical JSON");
8890
8896
  payload = value;
8891
8897
  } catch (error) {
8892
8898
  throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
8893
8899
  }
8894
- if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || Object.keys(payload).some((key3) => !["v", "c", "cid", "k"].includes(key3))) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
8900
+ const payloadKeys = Object.keys(payload);
8901
+ if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || payloadKeys.some((key3) => !["v", "c", "cid", "k"].includes(key3)) || publicPayload !== void 0 && (payloadKeys.length !== 3 || payload.k !== void 0)) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
8895
8902
  const sealed = base64UrlToBytes(payload.c);
8896
8903
  if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
8897
8904
  const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
8898
8905
  if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
8899
8906
  return { origin: origin2, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
8900
8907
  }
8908
+ if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
8901
8909
  const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
8902
8910
  if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
8903
8911
  const key = base64UrlToBytes(url.hash.slice(3));
@@ -8917,13 +8925,13 @@ function parseShareUrl(link, options2 = {}) {
8917
8925
  } catch {
8918
8926
  throw new ShareEnvelopeError("invalid-link", "share link must be an absolute URL");
8919
8927
  }
8920
- if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
8921
8928
  const origin2 = normalizeOrigin(url.origin);
8922
8929
  if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
8923
- if (url.pathname === "/s/inline" && url.hash.startsWith("#v=2&p=")) {
8930
+ if (url.pathname === "/viewer" && (url.hash.startsWith("#tc2=") || url.searchParams.has("tc2"))) {
8924
8931
  const published = parsePublishedShareLink(link, options2);
8925
8932
  return { kind: "inline", origin: origin2, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
8926
8933
  }
8934
+ if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
8927
8935
  const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
8928
8936
  if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
8929
8937
  const fragmentParams = url.hash === "" ? void 0 : new URLSearchParams(url.hash.slice(1));
@@ -8946,7 +8954,8 @@ function parseShareUrl(link, options2 = {}) {
8946
8954
  }
8947
8955
  function isV2ShareLink(link) {
8948
8956
  try {
8949
- return new URL(link).pathname.startsWith("/s/");
8957
+ const url = new URL(link);
8958
+ return url.pathname.startsWith("/s/") || url.pathname === "/viewer";
8950
8959
  } catch {
8951
8960
  return link.startsWith(INLINE_PREFIX);
8952
8961
  }
@@ -11759,114 +11768,13 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
11759
11768
  return validateOwnerSharePolicyRegistration(value, expected);
11760
11769
  }
11761
11770
 
11762
- // src/delegations/share-delivery.ts
11763
- import { ed25519 as ed255199 } from "@noble/curves/ed25519";
11764
- var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
11765
- function isCanonicalHttpsOrigin(value) {
11766
- let url;
11767
- try {
11768
- url = new URL(value);
11769
- } catch {
11770
- return false;
11771
- }
11772
- return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
11773
- }
11774
- var AUTHORIZATION_KEYS = [
11775
- "type",
11776
- "version",
11777
- "jti",
11778
- "shareCid",
11779
- "shareId",
11780
- "registrationCid",
11781
- "delegationCid",
11782
- "enforcementDelegationCid",
11783
- "envelopeCid",
11784
- "policyCid",
11785
- "nodeAudience",
11786
- "targetOrigin",
11787
- "openCredentialsAudience",
11788
- "holder",
11789
- "recipientMatcher",
11790
- "deliveryEmail",
11791
- "shareUrl",
11792
- "returnOrigin",
11793
- "documentName",
11794
- "senderDid",
11795
- "senderTrust",
11796
- "contentSource",
11797
- "contentSourceDigest",
11798
- "shareExpiresAt",
11799
- "issuedAt",
11800
- "reportAbuseToken",
11801
- "actions",
11802
- "resource",
11803
- "authorityMaterialHandle",
11804
- "authorityMaterialDigest",
11805
- "requestBodyDigest",
11806
- "idempotencyKey",
11807
- "expiresAt",
11808
- "dataAuthority"
11809
- ];
11810
- function assertObject2(value, keys, label) {
11811
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
11812
- const record2 = value;
11813
- if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) throw new Error(`${label} has unknown or missing fields`);
11814
- return record2;
11815
- }
11816
- function fromB642(value) {
11817
- if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
11818
- if (typeof atob === "function") {
11819
- const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
11820
- return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
11821
- }
11822
- if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
11823
- throw new Error("base64url decoding is unavailable");
11824
- }
11825
- function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
11826
- let value;
11827
- try {
11828
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
11829
- } catch {
11830
- throw new Error("share delivery authorization response is not valid UTF-8 JSON");
11831
- }
11832
- const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
11833
- const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
11834
- const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
11835
- const request = expected.request;
11836
- if (authorization.type !== "TinyCloudShareDeliveryAuthorization" || authorization.version !== 2 || authorization.dataAuthority !== false || authorization.jti !== request.jti || authorization.shareCid !== request.shareCid || authorization.shareId !== request.shareId || authorization.registrationCid !== request.registrationCid || authorization.delegationCid !== request.delegationCid || authorization.enforcementDelegationCid !== request.enforcementDelegationCid || authorization.envelopeCid !== request.envelopeCid || authorization.policyCid !== request.policyCid || authorization.deliveryEmail !== request.recipientEmail || authorization.shareUrl !== request.shareUrl || authorization.documentName !== request.documentName || authorization.requestBodyDigest !== request.requestBodyDigest || authorization.expiresAt !== request.expiresAt || authorization.idempotencyKey !== request.idempotencyKey || authorization.reportAbuseToken !== request.jti || authorization.authorityMaterialHandle !== request.registrationCid) {
11837
- throw new Error("share delivery authorization is not bound to the submitted request");
11838
- }
11839
- if (typeof authorization.issuedAt !== "string" || Date.parse(authorization.issuedAt) > Date.now() + 6e4) throw new Error("share delivery authorization issuedAt is invalid");
11840
- if (new Date(authorization.expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
11841
- if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
11842
- throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
11843
- }
11844
- if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
11845
- if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
11846
- const encodedKid = expected.nodeProof.publicKey;
11847
- const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
11848
- if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
11849
- const signatureBytes = fromB642(proof.signature);
11850
- const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalizeSignedObjectUnsigned(authorization)}`);
11851
- if (signatureBytes.length !== 64 || !ed255199.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
11852
- return { authorization, proof };
11853
- }
11854
- function shareDeliveryTrustedKid(publicKey) {
11855
- if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
11856
- const prefixed = new Uint8Array(34);
11857
- prefixed.set([237, 1]);
11858
- prefixed.set(publicKey, 2);
11859
- const did = `did:key:${base58btc.encode(prefixed)}`;
11860
- return `${did}#${did.slice("did:key:".length)}`;
11861
- }
11862
-
11863
11771
  // src/delegations/share-delivery-v3.ts
11864
- import { ed25519 as ed2551910 } from "@noble/curves/ed25519";
11772
+ import { ed25519 as ed255199 } from "@noble/curves/ed25519";
11865
11773
  import { sha256 as sha25611 } from "@noble/hashes/sha256";
11866
11774
  var CREDENTIAL_INVITATION_REQUEST_DOMAIN = "xyz.tinycloud.credentials/invitation-request/v1\0";
11867
11775
  var DELIVERY_ADMISSION_DOMAIN = "xyz.tinycloud.policy/delivery-admission/v0\0";
11868
- var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
11869
- var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
11776
+ var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
11777
+ var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
11870
11778
  function exactObject2(value, keys, label) {
11871
11779
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
11872
11780
  const record2 = value;
@@ -11910,15 +11818,16 @@ function validateShareDeliveryAuthorizationV3Bytes(bytes2, expected) {
11910
11818
  const contentSource = object3(envelope.contentSource, "v3 content source");
11911
11819
  const envelopeSignature = object3(envelope.signature, "v3 envelope signature");
11912
11820
  const target = object3(envelope.target, "v3 target");
11913
- const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
11914
- if (request.schema !== "xyz.tinycloud.credentials/invitation-request/v1" || admission.schema !== "xyz.tinycloud.policy/delivery-admission/v0" || fields.some((field) => request[field] !== admission[field]) || request.policyId !== envelope.policyCid || request.recipient !== expected.request.recipientEmail || request.resource !== contentSource.kvResource || request.credentialType !== credentialType.id || request.credentialType !== "opencredentials.email/v1" || request.returnLink !== expected.request.shareUrl || request.envelopeRef !== expected.request.shareCid || request.audience !== expected.credentialsAudience || request.expiresAt !== expected.request.expiresAt || request.nonce !== expected.request.jti || admission.ownerDid !== envelopeSignature.signerDid || admission.senderKeyDid !== expected.senderKeyDid || canonicalizeSignedObjectUnsigned(admission.actions) !== canonicalizeSignedObjectUnsigned(["tinycloud.kv/get"]) || signature.suite !== "eddsa-ed25519-sha256-jcs-v1" || signature.signerDid !== target.nodeAudience) throw new Error("v3 share delivery authorization is not bound to the submitted request");
11821
+ const binding = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
11822
+ const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
11823
+ if (request.schema !== "xyz.tinycloud.credentials/invitation-request/v1" || admission.schema !== "xyz.tinycloud.policy/delivery-admission/v0" || fields.some((field) => request[field] !== admission[field]) || request.policyId !== envelope.policyCid || request.recipient !== expected.request.recipientEmail || request.resource !== contentSource.kvResource || request.credentialType !== credentialType.id || request.credentialType !== "opencredentials.email/v1" || request.returnLink !== expected.request.shareUrl || request.envelopeRef !== expected.request.shareCid || request.label !== expected.request.documentName || request.shareExpiresAt !== envelope.expiry || request.audience !== expected.deliveryAudience || request.expiresAt !== expected.request.expiresAt || request.nonce !== expected.request.jti || admission.ownerDid !== envelopeSignature.signerDid || admission.senderKeyDid !== expected.senderKeyDid || canonicalizeSignedObjectUnsigned(admission.actions) !== canonicalizeSignedObjectUnsigned(["tinycloud.kv/get"]) || signature.suite !== "eddsa-ed25519-sha256-jcs-v1" || target.nodeAudience !== binding.nodeAudience || signature.signerDid !== binding.nodeAudience) throw new Error("v3 share delivery authorization is not bound to the submitted request");
11915
11824
  const issuedAt = Date.parse(String(request.issuedAt));
11916
11825
  const expiresAt = Date.parse(String(request.expiresAt));
11917
11826
  if (!Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || expiresAt <= issuedAt || expiresAt - issuedAt > 15 * 6e4) throw new Error("v3 share delivery authorization time is invalid");
11918
11827
  const admissionSignature = decodeBase64Url3(signature.value);
11919
11828
  const { signature: _signature, ...unsignedAdmission } = admission;
11920
11829
  const digest4 = sha25611(new TextEncoder().encode(`${DELIVERY_ADMISSION_DOMAIN}${canonicalizeSignedObjectUnsigned(unsignedAdmission)}`));
11921
- if (admissionSignature.length !== 64 || !ed2551910.verify(admissionSignature, digest4, didKeyBytes2(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
11830
+ if (admissionSignature.length !== 64 || !ed255199.verify(admissionSignature, digest4, didKeyBytes2(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
11922
11831
  return { request, admission };
11923
11832
  }
11924
11833
 
@@ -12042,9 +11951,6 @@ import {
12042
11951
  publishPolicyShare,
12043
11952
  claimShare,
12044
11953
  resumeShareAuthorization,
12045
- isLegacyShareLink,
12046
- receiveLegacyShare,
12047
- migrateShare,
12048
11954
  MemoryEncryptedShareHistoryStorage,
12049
11955
  EncryptedSenderShareHistory
12050
11956
  } from "@tinycloud/share-sdk";
@@ -12052,8 +11958,7 @@ import {
12052
11958
  inspectShare,
12053
11959
  receiveShare,
12054
11960
  ShareReceiveError,
12055
- toShareErrorInfo,
12056
- verifyBearerEnvelope
11961
+ toShareErrorInfo
12057
11962
  } from "@tinycloud/share-sdk";
12058
11963
 
12059
11964
  // src/networkId.ts
@@ -17689,7 +17594,7 @@ function portForProtocol(protocol) {
17689
17594
  }
17690
17595
 
17691
17596
  // src/location.ts
17692
- import { ed25519 as ed2551911 } from "@noble/curves/ed25519";
17597
+ import { ed25519 as ed2551910 } from "@noble/curves/ed25519";
17693
17598
  import { verifyMessage as verifyMessage2 } from "viem";
17694
17599
  var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
17695
17600
  var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
@@ -17697,6 +17602,7 @@ var DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
17697
17602
  var LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
17698
17603
  var LOCAL_LINK_PROBE_TIMEOUT_MS = 750;
17699
17604
  var LOCAL_LINK_HOST_SUFFIX = ".local.tinycloud.link";
17605
+ var OWNER_NODE_BINDING_TIMEOUT_MS = 5e3;
17700
17606
  function createInMemoryLocalNodeIdentityStore() {
17701
17607
  const pins = /* @__PURE__ */ new Map();
17702
17608
  return {
@@ -17829,21 +17735,80 @@ async function verifyLocationRecord(input) {
17829
17735
  }
17830
17736
  return false;
17831
17737
  }
17832
- async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
17738
+ async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
17833
17739
  const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
17834
- const response = await fetchFn(url);
17740
+ const response = await fetchFn(url, {
17741
+ redirect: "error",
17742
+ headers: { accept: "application/json" },
17743
+ ...signal === void 0 ? {} : { signal }
17744
+ });
17835
17745
  if (response.status === 404) {
17836
17746
  return null;
17837
17747
  }
17838
17748
  if (!response.ok) {
17839
17749
  throw new Error(`location registry returned HTTP ${response.status}`);
17840
17750
  }
17841
- const body = await response.json();
17751
+ const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
17752
+ let body;
17753
+ try {
17754
+ body = JSON.parse(text2);
17755
+ } catch {
17756
+ throw new LocationRecordValidationError("registry response is not JSON");
17757
+ }
17842
17758
  if (body.record === void 0) {
17843
17759
  throw new LocationRecordValidationError("registry response missing record");
17844
17760
  }
17845
17761
  return validateLocationRecord(body.record);
17846
17762
  }
17763
+ async function publishLocationRecord(options2) {
17764
+ validateSubject(options2.subject);
17765
+ const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
17766
+ const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
17767
+ const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
17768
+ const current = await fetchLocationRecord(registryUrl, options2.subject, fetchFn);
17769
+ if (current !== null) {
17770
+ if (current.subject !== options2.subject || !await verifyLocationRecord(current)) {
17771
+ throw new LocationRecordValidationError("existing location record signature is invalid");
17772
+ }
17773
+ if (publishedHttpOrigins(current).includes(nodeOrigin)) {
17774
+ return current;
17775
+ }
17776
+ }
17777
+ const record2 = await signLocationRecord({
17778
+ version: 1,
17779
+ subject: options2.subject,
17780
+ multiaddrs: [...current?.multiaddrs ?? [], httpUrlToMultiaddr(nodeOrigin)],
17781
+ updated_at: (options2.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
17782
+ sequence: (current?.sequence ?? -1) + 1
17783
+ }, options2.signer);
17784
+ const response = await fetchFn(
17785
+ `${registryUrl}/v1/locations/${encodeURIComponent(options2.subject)}`,
17786
+ {
17787
+ method: "PUT",
17788
+ redirect: "error",
17789
+ headers: { accept: "application/json", "content-type": "application/json" },
17790
+ body: JSON.stringify(record2)
17791
+ }
17792
+ );
17793
+ if (!response.ok) {
17794
+ throw new Error(`location registry publish returned HTTP ${response.status}`);
17795
+ }
17796
+ const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
17797
+ let body;
17798
+ try {
17799
+ body = JSON.parse(text2);
17800
+ } catch {
17801
+ throw new LocationRecordValidationError("registry publish response is not JSON");
17802
+ }
17803
+ if (body.record === void 0) {
17804
+ throw new LocationRecordValidationError("registry publish response missing record");
17805
+ }
17806
+ const published = validateLocationRecord(body.record);
17807
+ if (canonicalLocationPayload(locationPayloadForRecord(published)) !== canonicalLocationPayload(locationPayloadForRecord(record2)) || published.signature !== record2.signature || !await verifyLocationRecord(published)) {
17808
+ throw new LocationRecordValidationError("registry publish response does not match the signed record");
17809
+ }
17810
+ return published;
17811
+ }
17847
17812
  async function resolveCloudLocation(subject, options2 = {}) {
17848
17813
  validateSubject(subject);
17849
17814
  const verifyRecords = options2.verifyRecords ?? true;
@@ -18094,6 +18059,123 @@ function multiaddrToHttpUrl(input) {
18094
18059
  }
18095
18060
  return uri;
18096
18061
  }
18062
+ function exactHttpOrigin(value, label) {
18063
+ let url;
18064
+ try {
18065
+ url = new URL(value);
18066
+ } catch {
18067
+ throw new LocationRecordValidationError(`${label} must be an absolute URL origin`);
18068
+ }
18069
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
18070
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
18071
+ throw new LocationRecordValidationError(`${label} must use HTTPS (or loopback HTTP)`);
18072
+ }
18073
+ if (url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
18074
+ throw new LocationRecordValidationError(`${label} must be an origin without credentials, path, query, or fragment`);
18075
+ }
18076
+ return url.origin;
18077
+ }
18078
+ function publishedHttpOrigins(record2) {
18079
+ const origins = [];
18080
+ for (const address of record2.multiaddrs) {
18081
+ try {
18082
+ origins.push(exactHttpOrigin(multiaddrToHttpUrl(address), "published node URL"));
18083
+ } catch {
18084
+ }
18085
+ }
18086
+ return origins;
18087
+ }
18088
+ async function boundedResponseText(response, label, maxBytes) {
18089
+ const declaredLength = response.headers.get("content-length");
18090
+ if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
18091
+ throw new LocationRecordValidationError(`${label} response is too large`);
18092
+ }
18093
+ if (response.body === null) return "";
18094
+ const reader = response.body.getReader();
18095
+ const chunks = [];
18096
+ let total = 0;
18097
+ try {
18098
+ while (true) {
18099
+ const { done, value } = await reader.read();
18100
+ if (done) break;
18101
+ if (value === void 0) continue;
18102
+ total += value.byteLength;
18103
+ if (total > maxBytes) {
18104
+ await reader.cancel().catch(() => void 0);
18105
+ throw new LocationRecordValidationError(`${label} response is too large`);
18106
+ }
18107
+ chunks.push(value);
18108
+ }
18109
+ } finally {
18110
+ reader.releaseLock();
18111
+ }
18112
+ const bytes2 = new Uint8Array(total);
18113
+ let offset = 0;
18114
+ for (const chunk of chunks) {
18115
+ bytes2.set(chunk, offset);
18116
+ offset += chunk.byteLength;
18117
+ }
18118
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
18119
+ }
18120
+ function boundedSignal(parent, timeoutMs) {
18121
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
18122
+ throw new LocationRecordValidationError("owner-node binding timeout must be positive");
18123
+ }
18124
+ const controller = new AbortController();
18125
+ const abortFromParent = () => controller.abort(parent?.reason);
18126
+ if (parent?.aborted) abortFromParent();
18127
+ else parent?.addEventListener("abort", abortFromParent, { once: true });
18128
+ const timer = setTimeout(() => controller.abort(new DOMException("owner-node binding timed out", "TimeoutError")), timeoutMs);
18129
+ return {
18130
+ signal: controller.signal,
18131
+ dispose() {
18132
+ clearTimeout(timer);
18133
+ parent?.removeEventListener("abort", abortFromParent);
18134
+ }
18135
+ };
18136
+ }
18137
+ async function verifyOwnerNodeBinding(options2) {
18138
+ validateSubject(options2.ownerDid);
18139
+ validateSubject(options2.nodeDid);
18140
+ const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
18141
+ const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
18142
+ const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
18143
+ const lookup = boundedSignal(options2.signal, options2.timeoutMs ?? OWNER_NODE_BINDING_TIMEOUT_MS);
18144
+ try {
18145
+ const record2 = await fetchLocationRecord(registryUrl, options2.ownerDid, fetchFn, lookup.signal);
18146
+ if (record2 === null) {
18147
+ throw new LocationRecordValidationError("owner has no published location record");
18148
+ }
18149
+ if (record2.subject !== options2.ownerDid || !await verifyLocationRecord(record2)) {
18150
+ throw new LocationRecordValidationError("owner location record signature is invalid");
18151
+ }
18152
+ const publishedOrigins = publishedHttpOrigins(record2);
18153
+ if (!publishedOrigins.includes(nodeOrigin)) {
18154
+ throw new LocationRecordValidationError("share target is not in the owner's signed location record");
18155
+ }
18156
+ const response = await fetchFn(new URL("/info", nodeOrigin), {
18157
+ redirect: "error",
18158
+ headers: { accept: "application/json" },
18159
+ signal: lookup.signal
18160
+ });
18161
+ if (!response.ok) {
18162
+ throw new LocationRecordValidationError(`target node /info returned HTTP ${response.status}`);
18163
+ }
18164
+ const text2 = await boundedResponseText(response, "target node /info", 16 * 1024);
18165
+ let body;
18166
+ try {
18167
+ body = JSON.parse(text2);
18168
+ } catch {
18169
+ throw new LocationRecordValidationError("target node /info response is not JSON");
18170
+ }
18171
+ if (body === null || typeof body !== "object" || Array.isArray(body) || body.nodeId !== options2.nodeDid) {
18172
+ throw new LocationRecordValidationError("target node identity does not match the share attestation");
18173
+ }
18174
+ return Object.freeze({ record: record2, nodeOrigin, nodeDid: options2.nodeDid });
18175
+ } finally {
18176
+ lookup.dispose();
18177
+ }
18178
+ }
18097
18179
  function httpUrlToMultiaddr(input) {
18098
18180
  const url = new URL(input);
18099
18181
  if (url.protocol !== "http:" && url.protocol !== "https:") {
@@ -18261,7 +18343,7 @@ function verifyDidKeySignature(did, payload, signature) {
18261
18343
  "did:key signature must be a base64url Ed25519 signature"
18262
18344
  );
18263
18345
  }
18264
- return ed2551911.verify(
18346
+ return ed2551910.verify(
18265
18347
  signatureBytes,
18266
18348
  new TextEncoder().encode(payload),
18267
18349
  publicKey
@@ -18269,7 +18351,7 @@ function verifyDidKeySignature(did, payload, signature) {
18269
18351
  }
18270
18352
  function verifyDidKeyEd25519Signature(did, payload, signature) {
18271
18353
  const publicKey = ed25519PublicKeyFromDidKey2(did);
18272
- return ed2551911.verify(signature, payload, publicKey);
18354
+ return ed2551910.verify(signature, payload, publicKey);
18273
18355
  }
18274
18356
  function ed25519PublicKeyFromDidKey2(did) {
18275
18357
  const identifier = did.slice("did:key:".length);
@@ -18430,6 +18512,7 @@ export {
18430
18512
  NETWORK_NAME_PATTERN,
18431
18513
  NetworkIdError,
18432
18514
  OPENKEY_ACTION_ID_SEPARATOR,
18515
+ OWNER_NODE_BINDING_TIMEOUT_MS,
18433
18516
  OpenKeyManageKeyError,
18434
18517
  POLICY_CAPABILITY_V1_DOMAIN,
18435
18518
  POLICY_CAPABILITY_V1_SCHEMA,
@@ -18469,7 +18552,6 @@ export {
18469
18552
  SECRET_RECORDS_SCHEMA,
18470
18553
  SERVICE_LONG_TO_SHORT,
18471
18554
  SERVICE_SHORT_TO_LONG,
18472
- SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
18473
18555
  SPACE,
18474
18556
  SQL,
18475
18557
  SQLAction,
@@ -18685,6 +18767,7 @@ export {
18685
18767
  principalDidEquals,
18686
18768
  projectUnifiedPolicyCapabilities,
18687
18769
  projectUnifiedPolicyCapability,
18770
+ publishLocationRecord,
18688
18771
  recapCaveatsEqual,
18689
18772
  registerPolicyV3,
18690
18773
  renewPolicyRootStatusV3,
@@ -18704,7 +18787,6 @@ export {
18704
18787
  serviceError5 as serviceError,
18705
18788
  sha256Base64Url,
18706
18789
  shareCapabilityAllows,
18707
- shareDeliveryTrustedKid,
18708
18790
  signCompactUcanAuthorization,
18709
18791
  signCompactUcanRootAuthorization,
18710
18792
  signLocationRecord,
@@ -18741,7 +18823,6 @@ export {
18741
18823
  validatePolicyStatusSignedShape,
18742
18824
  validatePolicyStatusUnsigned,
18743
18825
  validatePolicyUnsigned,
18744
- validateShareDeliveryAuthorizationBytes,
18745
18826
  validateShareDeliveryAuthorizationV3Bytes,
18746
18827
  validateUnifiedPolicyV2,
18747
18828
  verifyDecryptResponse,
@@ -18749,6 +18830,7 @@ export {
18749
18830
  verifyEip191MessageSignature,
18750
18831
  verifyIssuedCredential,
18751
18832
  verifyLocationRecord,
18833
+ verifyOwnerNodeBinding,
18752
18834
  verifyPolicy,
18753
18835
  verifyPolicyEngineRecord,
18754
18836
  verifyPolicyRootRevocationV3,