@tinycloud/sdk-core 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -69,6 +69,8 @@ __export(index_exports, {
69
69
  DEFAULT_EXPIRY: () => DEFAULT_EXPIRY,
70
70
  DEFAULT_KEY_VERSION: () => import_sdk_services7.DEFAULT_KEY_VERSION,
71
71
  DEFAULT_KNOWLEDGE_ROOT: () => DEFAULT_KNOWLEDGE_ROOT,
72
+ DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY: () => DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY,
73
+ DEFAULT_LOCAL_NODE_URL: () => DEFAULT_LOCAL_NODE_URL,
72
74
  DEFAULT_MANIFEST_SPACE: () => DEFAULT_MANIFEST_SPACE,
73
75
  DEFAULT_MANIFEST_VERSION: () => DEFAULT_MANIFEST_VERSION,
74
76
  DEFAULT_SIGNED_READ_URL_EXPIRY_MS: () => DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
@@ -103,6 +105,9 @@ __export(index_exports, {
103
105
  KV: () => import_bootstrap3.KV,
104
106
  KVService: () => import_sdk_services7.KVService,
105
107
  LISTEN_SQL_STATEMENT_CATALOG: () => LISTEN_SQL_STATEMENT_CATALOG,
108
+ LOCAL_LINK_HOST_SUFFIX: () => LOCAL_LINK_HOST_SUFFIX,
109
+ LOCAL_LINK_PROBE_TIMEOUT_MS: () => LOCAL_LINK_PROBE_TIMEOUT_MS,
110
+ LOCAL_LOOPBACK_PROBE_TIMEOUT_MS: () => LOCAL_LOOPBACK_PROBE_TIMEOUT_MS,
106
111
  LocationRecordValidationError: () => LocationRecordValidationError,
107
112
  ManifestValidationError: () => ManifestValidationError,
108
113
  NETWORK_NAME_PATTERN: () => import_sdk_services7.NETWORK_NAME_PATTERN,
@@ -198,6 +203,8 @@ __export(index_exports, {
198
203
  createAndSignPolicyStatus: () => createAndSignPolicyStatus,
199
204
  createAndSignSignedObject: () => createAndSignSignedObject,
200
205
  createCapabilityKeyRegistry: () => createCapabilityKeyRegistry,
206
+ createInMemoryLocalNodeIdentityStore: () => createInMemoryLocalNodeIdentityStore,
207
+ createLocalStorageLocalNodeIdentityStore: () => createLocalStorageLocalNodeIdentityStore,
201
208
  createOpenKeyCallbackSigningStrategy: () => createOpenKeyCallbackSigningStrategy,
202
209
  createSharingService: () => createSharingService,
203
210
  createSpaceService: () => createSpaceService,
@@ -213,6 +220,7 @@ __export(index_exports, {
213
220
  didCacheKey: () => didCacheKey,
214
221
  didEquals: () => didEquals,
215
222
  disableTinyCloudDebug: () => import_sdk_services7.disableTinyCloudDebug,
223
+ discoverLocalTinyCloudNode: () => discoverLocalTinyCloudNode,
216
224
  discoverNetwork: () => import_sdk_services7.discoverNetwork,
217
225
  enableTinyCloudDebug: () => import_sdk_services7.enableTinyCloudDebug,
218
226
  encryptToNetwork: () => import_sdk_services7.encryptToNetwork,
@@ -12449,6 +12457,54 @@ var import_ed255193 = require("@noble/curves/ed25519");
12449
12457
  var import_viem5 = require("viem");
12450
12458
  var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
12451
12459
  var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
12460
+ var DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
12461
+ var LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
12462
+ var LOCAL_LINK_PROBE_TIMEOUT_MS = 750;
12463
+ var LOCAL_LINK_HOST_SUFFIX = ".local.tinycloud.link";
12464
+ function createInMemoryLocalNodeIdentityStore() {
12465
+ const pins = /* @__PURE__ */ new Map();
12466
+ return {
12467
+ get: (url) => pins.get(url),
12468
+ set: (url, nodeDid) => {
12469
+ pins.set(url, nodeDid);
12470
+ }
12471
+ };
12472
+ }
12473
+ var DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY = "tinycloud.localNodePins.v1";
12474
+ function defaultWebStorage() {
12475
+ try {
12476
+ return globalThis.localStorage;
12477
+ } catch {
12478
+ return void 0;
12479
+ }
12480
+ }
12481
+ function createLocalStorageLocalNodeIdentityStore(storage, storageKey = DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY) {
12482
+ const backing = storage ?? defaultWebStorage();
12483
+ if (!backing) {
12484
+ return createInMemoryLocalNodeIdentityStore();
12485
+ }
12486
+ const readPins = () => {
12487
+ const raw = backing.getItem(storageKey);
12488
+ if (!raw) return {};
12489
+ try {
12490
+ const parsed = JSON.parse(raw);
12491
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
12492
+ return parsed;
12493
+ }
12494
+ } catch {
12495
+ }
12496
+ backing.removeItem(storageKey);
12497
+ return {};
12498
+ };
12499
+ return {
12500
+ get: (url) => readPins()[url],
12501
+ set: (url, nodeDid) => {
12502
+ const pins = readPins();
12503
+ pins[url] = nodeDid;
12504
+ backing.setItem(storageKey, JSON.stringify(pins));
12505
+ }
12506
+ };
12507
+ }
12452
12508
  var LocationRecordValidationError = class extends Error {
12453
12509
  constructor(message) {
12454
12510
  super(`Location record validation failed: ${message}`);
@@ -12575,6 +12631,33 @@ async function resolveCloudLocation(subject, options2 = {}) {
12575
12631
  };
12576
12632
  }
12577
12633
  async function resolveTinyCloudHosts(subject, options2 = {}) {
12634
+ const hasExplicitHosts = options2.explicitHosts !== void 0 && options2.explicitHosts.length > 0;
12635
+ const fetchFn = memoizeFetch(options2.fetch ?? globalThis.fetch);
12636
+ if (!hasExplicitHosts && (options2.autoDiscoverLocalNode ?? true)) {
12637
+ const local = await discoverLocalTinyCloudNode({
12638
+ subject,
12639
+ autoDiscoverLocalNode: true,
12640
+ localNodeUrl: options2.localNodeUrl,
12641
+ localLinkName: options2.localLinkName,
12642
+ registryUrl: options2.registryUrl,
12643
+ expectedNodeDid: options2.expectedNodeDid,
12644
+ identityStore: options2.localNodeIdentityStore,
12645
+ verifyRecords: options2.verifyRecords,
12646
+ fetch: fetchFn
12647
+ });
12648
+ if (local) {
12649
+ const multiaddrs = [httpUrlToMultiaddr(local.url)];
12650
+ const candidate = { source: local.source, multiaddrs };
12651
+ const location2 = {
12652
+ subject,
12653
+ source: local.source,
12654
+ multiaddrs,
12655
+ attempts: [{ source: local.source, candidate }],
12656
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
12657
+ };
12658
+ return { hosts: [local.url], location: location2 };
12659
+ }
12660
+ }
12578
12661
  const location = await resolveCloudLocation(subject, {
12579
12662
  explicitMultiaddrs: hostsToMultiaddrs(options2.explicitHosts),
12580
12663
  blockchain: options2.blockchain,
@@ -12582,7 +12665,7 @@ async function resolveTinyCloudHosts(subject, options2 = {}) {
12582
12665
  fallbackMultiaddrs: hostsToMultiaddrs(
12583
12666
  options2.fallbackHosts === null ? void 0 : options2.fallbackHosts ?? [DEFAULT_TINYCLOUD_FALLBACK_HOST]
12584
12667
  ),
12585
- fetch: options2.fetch,
12668
+ fetch: fetchFn,
12586
12669
  verifyRecords: options2.verifyRecords
12587
12670
  });
12588
12671
  return {
@@ -12590,6 +12673,181 @@ async function resolveTinyCloudHosts(subject, options2 = {}) {
12590
12673
  location
12591
12674
  };
12592
12675
  }
12676
+ function memoizeFetch(fetchFn) {
12677
+ const cache2 = /* @__PURE__ */ new Map();
12678
+ return (async (input, init) => {
12679
+ const key = String(input);
12680
+ let cached = cache2.get(key);
12681
+ if (!cached) {
12682
+ cached = fetchFn(input, init);
12683
+ cache2.set(key, cached);
12684
+ }
12685
+ const response = await cached;
12686
+ return response.clone();
12687
+ });
12688
+ }
12689
+ async function discoverLocalTinyCloudNode(options2 = {}) {
12690
+ if ((options2.autoDiscoverLocalNode ?? true) === false) {
12691
+ return null;
12692
+ }
12693
+ const fetchFn = options2.fetch ?? globalThis.fetch;
12694
+ const identityStore = options2.identityStore ?? defaultLocalNodeIdentityStore;
12695
+ const staticCandidates = [
12696
+ {
12697
+ source: "local-loopback",
12698
+ url: options2.localNodeUrl ?? DEFAULT_LOCAL_NODE_URL,
12699
+ timeoutMs: LOCAL_LOOPBACK_PROBE_TIMEOUT_MS
12700
+ }
12701
+ ];
12702
+ if (options2.localLinkName) {
12703
+ if (isValidDnsLabel(options2.localLinkName)) {
12704
+ staticCandidates.push({
12705
+ source: "local-link",
12706
+ url: `https://${options2.localLinkName}${LOCAL_LINK_HOST_SUFFIX}`,
12707
+ timeoutMs: LOCAL_LINK_PROBE_TIMEOUT_MS
12708
+ });
12709
+ } else {
12710
+ debugLog(
12711
+ `localLinkName "${options2.localLinkName}" is not a valid DNS label; skipping`
12712
+ );
12713
+ }
12714
+ }
12715
+ for (const candidate of staticCandidates) {
12716
+ const adopted = await probeAndVerifyLocalCandidate(
12717
+ candidate,
12718
+ fetchFn,
12719
+ identityStore,
12720
+ options2.expectedNodeDid
12721
+ );
12722
+ if (adopted) {
12723
+ return adopted;
12724
+ }
12725
+ }
12726
+ const registryCandidates = await fetchRegistryLocalLinkCandidates(
12727
+ options2,
12728
+ fetchFn,
12729
+ staticCandidates
12730
+ );
12731
+ for (const candidate of registryCandidates) {
12732
+ const adopted = await probeAndVerifyLocalCandidate(
12733
+ candidate,
12734
+ fetchFn,
12735
+ identityStore,
12736
+ options2.expectedNodeDid
12737
+ );
12738
+ if (adopted) {
12739
+ return adopted;
12740
+ }
12741
+ }
12742
+ return null;
12743
+ }
12744
+ var defaultLocalNodeIdentityStore = createInMemoryLocalNodeIdentityStore();
12745
+ function debugLog(message) {
12746
+ console.debug(`[tinycloud:location] ${message}`);
12747
+ }
12748
+ async function probeAndVerifyLocalCandidate(candidate, fetchFn, identityStore, expectedNodeDid) {
12749
+ const healthy = await probeHealthz(candidate.url, candidate.timeoutMs, fetchFn);
12750
+ if (!healthy) {
12751
+ return null;
12752
+ }
12753
+ const info = await fetchLocalNodeInfo(candidate.url, candidate.timeoutMs, fetchFn);
12754
+ if (!info?.nodeDid) {
12755
+ debugLog(`local node at ${candidate.url} did not report a nodeId DID; skipping`);
12756
+ return null;
12757
+ }
12758
+ const pinnedNodeDid = await identityStore.get(candidate.url);
12759
+ const expected = expectedNodeDid ?? pinnedNodeDid;
12760
+ if (expected !== void 0 && expected !== info.nodeDid) {
12761
+ debugLog(
12762
+ `local node DID mismatch at ${candidate.url}: expected ${expected}, got ${info.nodeDid}`
12763
+ );
12764
+ return null;
12765
+ }
12766
+ if (pinnedNodeDid === void 0 || expectedNodeDid !== void 0) {
12767
+ await identityStore.set(candidate.url, info.nodeDid);
12768
+ }
12769
+ return { source: candidate.source, url: candidate.url, nodeDid: info.nodeDid };
12770
+ }
12771
+ async function fetchRegistryLocalLinkCandidates(options2, fetchFn, existingCandidates) {
12772
+ if (!options2.subject || options2.registryUrl === null) {
12773
+ return [];
12774
+ }
12775
+ const registryUrl = options2.registryUrl ?? DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL;
12776
+ try {
12777
+ const record = await fetchLocationRecord(registryUrl, options2.subject, fetchFn);
12778
+ if (!record) {
12779
+ return [];
12780
+ }
12781
+ const shouldVerify = options2.verifyRecords ?? true;
12782
+ if (shouldVerify && !await verifyLocationRecord(record)) {
12783
+ debugLog(
12784
+ `registry record signature invalid for ${options2.subject}; skipping local-link lookup`
12785
+ );
12786
+ return [];
12787
+ }
12788
+ return extractLocalLinkUrls(record).filter((url) => !existingCandidates.some((c) => c.url === url)).map((url) => ({
12789
+ source: "local-link",
12790
+ url,
12791
+ timeoutMs: LOCAL_LINK_PROBE_TIMEOUT_MS
12792
+ }));
12793
+ } catch {
12794
+ return [];
12795
+ }
12796
+ }
12797
+ function extractLocalLinkUrls(record) {
12798
+ if (!record) {
12799
+ return [];
12800
+ }
12801
+ const urls = [];
12802
+ for (const addr of record.multiaddrs) {
12803
+ let url;
12804
+ try {
12805
+ url = multiaddrToHttpUrl(addr);
12806
+ } catch {
12807
+ continue;
12808
+ }
12809
+ if (isLocalLinkUrl(url)) {
12810
+ urls.push(url);
12811
+ }
12812
+ }
12813
+ return urls;
12814
+ }
12815
+ var DNS_LABEL_REGEX = /^[a-z0-9]([a-z0-9-]{1,30}[a-z0-9])?$/;
12816
+ function isValidDnsLabel(value) {
12817
+ return DNS_LABEL_REGEX.test(value);
12818
+ }
12819
+ function isLocalLinkUrl(url) {
12820
+ try {
12821
+ const parsed = new URL(url);
12822
+ return parsed.protocol === "https:" && parsed.hostname.endsWith(LOCAL_LINK_HOST_SUFFIX);
12823
+ } catch {
12824
+ return false;
12825
+ }
12826
+ }
12827
+ async function probeHealthz(url, timeoutMs, fetchFn) {
12828
+ try {
12829
+ const response = await fetchFn(`${url.replace(/\/$/, "")}/healthz`, {
12830
+ signal: AbortSignal.timeout(timeoutMs)
12831
+ });
12832
+ return response.ok;
12833
+ } catch {
12834
+ return false;
12835
+ }
12836
+ }
12837
+ async function fetchLocalNodeInfo(url, timeoutMs, fetchFn) {
12838
+ try {
12839
+ const response = await fetchFn(`${url.replace(/\/$/, "")}/info`, {
12840
+ signal: AbortSignal.timeout(timeoutMs)
12841
+ });
12842
+ if (!response.ok) {
12843
+ return null;
12844
+ }
12845
+ const body = await response.json();
12846
+ return { nodeDid: body.nodeId };
12847
+ } catch {
12848
+ return null;
12849
+ }
12850
+ }
12593
12851
  function multiaddrToHttpUrl(input) {
12594
12852
  const uri = multiaddrToUri(multiaddr(input));
12595
12853
  if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
@@ -12866,6 +13124,8 @@ function decodeBase64Url(value) {
12866
13124
  DEFAULT_EXPIRY,
12867
13125
  DEFAULT_KEY_VERSION,
12868
13126
  DEFAULT_KNOWLEDGE_ROOT,
13127
+ DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY,
13128
+ DEFAULT_LOCAL_NODE_URL,
12869
13129
  DEFAULT_MANIFEST_SPACE,
12870
13130
  DEFAULT_MANIFEST_VERSION,
12871
13131
  DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
@@ -12900,6 +13160,9 @@ function decodeBase64Url(value) {
12900
13160
  KV,
12901
13161
  KVService,
12902
13162
  LISTEN_SQL_STATEMENT_CATALOG,
13163
+ LOCAL_LINK_HOST_SUFFIX,
13164
+ LOCAL_LINK_PROBE_TIMEOUT_MS,
13165
+ LOCAL_LOOPBACK_PROBE_TIMEOUT_MS,
12903
13166
  LocationRecordValidationError,
12904
13167
  ManifestValidationError,
12905
13168
  NETWORK_NAME_PATTERN,
@@ -12995,6 +13258,8 @@ function decodeBase64Url(value) {
12995
13258
  createAndSignPolicyStatus,
12996
13259
  createAndSignSignedObject,
12997
13260
  createCapabilityKeyRegistry,
13261
+ createInMemoryLocalNodeIdentityStore,
13262
+ createLocalStorageLocalNodeIdentityStore,
12998
13263
  createOpenKeyCallbackSigningStrategy,
12999
13264
  createSharingService,
13000
13265
  createSpaceService,
@@ -13010,6 +13275,7 @@ function decodeBase64Url(value) {
13010
13275
  didCacheKey,
13011
13276
  didEquals,
13012
13277
  disableTinyCloudDebug,
13278
+ discoverLocalTinyCloudNode,
13013
13279
  discoverNetwork,
13014
13280
  enableTinyCloudDebug,
13015
13281
  encryptToNetwork,