@tinycloud/sdk-core 2.2.0-beta.1 → 2.2.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  CapabilityKeyRegistry: () => CapabilityKeyRegistry,
37
37
  CapabilityKeyRegistryErrorCodes: () => CapabilityKeyRegistryErrorCodes,
38
38
  ClientSessionSchema: () => ClientSessionSchema,
39
+ CloudLocationResolutionError: () => CloudLocationResolutionError,
39
40
  DEFAULT_DEFAULTS: () => DEFAULT_DEFAULTS,
40
41
  DEFAULT_EXPIRY: () => DEFAULT_EXPIRY,
41
42
  DEFAULT_MANIFEST_SPACE: () => DEFAULT_MANIFEST_SPACE,
@@ -51,6 +52,7 @@ __export(index_exports, {
51
52
  ErrorCodes: () => import_sdk_services4.ErrorCodes,
52
53
  HooksService: () => import_sdk_services4.HooksService,
53
54
  KVService: () => import_sdk_services4.KVService,
55
+ LocationRecordValidationError: () => LocationRecordValidationError,
54
56
  ManifestValidationError: () => ManifestValidationError,
55
57
  PermissionNotInManifestError: () => PermissionNotInManifestError,
56
58
  PrefixedKVService: () => import_sdk_services4.PrefixedKVService,
@@ -76,6 +78,7 @@ __export(index_exports, {
76
78
  activateSessionWithHost: () => activateSessionWithHost,
77
79
  applyPrefix: () => applyPrefix,
78
80
  buildSpaceUri: () => buildSpaceUri,
81
+ canonicalLocationPayload: () => canonicalLocationPayload,
79
82
  checkNodeInfo: () => checkNodeInfo,
80
83
  composeManifestRequest: () => composeManifestRequest,
81
84
  createCapabilityKeyRegistry: () => createCapabilityKeyRegistry,
@@ -87,24 +90,33 @@ __export(index_exports, {
87
90
  defaultSpaceCreationHandler: () => defaultSpaceCreationHandler,
88
91
  err: () => import_sdk_services4.err,
89
92
  expandActionShortNames: () => expandActionShortNames,
93
+ fetchLocationRecord: () => fetchLocationRecord,
90
94
  fetchPeerId: () => fetchPeerId,
95
+ httpUrlToMultiaddr: () => httpUrlToMultiaddr,
91
96
  isCapabilitySubset: () => isCapabilitySubset,
92
97
  loadManifest: () => loadManifest,
98
+ locationPayloadForRecord: () => locationPayloadForRecord,
93
99
  makePublicSpaceId: () => makePublicSpaceId,
94
100
  manifestAbilitiesUnion: () => manifestAbilitiesUnion,
101
+ multiaddrToHttpUrl: () => multiaddrToHttpUrl,
95
102
  normalizeDefaults: () => normalizeDefaults,
96
103
  ok: () => import_sdk_services4.ok,
97
104
  parseExpiry: () => parseExpiry,
98
105
  parseRecapCapabilities: () => parseRecapCapabilities,
99
106
  parseSpaceUri: () => parseSpaceUri,
107
+ resolveCloudLocation: () => resolveCloudLocation,
100
108
  resolveManifest: () => resolveManifest,
101
109
  resourceCapabilitiesToAbilitiesMap: () => resourceCapabilitiesToAbilitiesMap,
102
110
  resourceCapabilitiesToSpaceAbilitiesMap: () => resourceCapabilitiesToSpaceAbilitiesMap,
103
111
  serviceError: () => import_sdk_services4.serviceError,
112
+ signLocationRecord: () => signLocationRecord,
104
113
  submitHostDelegation: () => submitHostDelegation,
105
114
  validateClientSession: () => validateClientSession,
115
+ validateLocationRecord: () => validateLocationRecord,
116
+ validateLocationRecordPayload: () => validateLocationRecordPayload,
106
117
  validateManifest: () => validateManifest,
107
- validatePersistedSessionData: () => validatePersistedSessionData
118
+ validatePersistedSessionData: () => validatePersistedSessionData,
119
+ verifyLocationRecord: () => verifyLocationRecord
108
120
  });
109
121
  module.exports = __toCommonJS(index_exports);
110
122
 
@@ -4330,6 +4342,339 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
4330
4342
  };
4331
4343
  }
4332
4344
 
4345
+ // src/location.ts
4346
+ var import_multiaddr = require("@multiformats/multiaddr");
4347
+ var import_multiaddr_to_uri = require("@multiformats/multiaddr-to-uri");
4348
+ var import_uri_to_multiaddr = require("@multiformats/uri-to-multiaddr");
4349
+ var import_ed25519 = require("@noble/curves/ed25519");
4350
+ var import_basics = require("multiformats/basics");
4351
+ var import_viem = require("viem");
4352
+ var LocationRecordValidationError = class extends Error {
4353
+ constructor(message) {
4354
+ super(`Location record validation failed: ${message}`);
4355
+ this.name = "LocationRecordValidationError";
4356
+ }
4357
+ };
4358
+ var CloudLocationResolutionError = class extends Error {
4359
+ constructor(subject, attempts) {
4360
+ super(`Unable to resolve TinyCloud location for ${subject}`);
4361
+ this.name = "CloudLocationResolutionError";
4362
+ this.attempts = attempts;
4363
+ }
4364
+ };
4365
+ function locationPayloadForRecord(record) {
4366
+ return {
4367
+ version: record.version,
4368
+ subject: record.subject,
4369
+ multiaddrs: [...record.multiaddrs],
4370
+ updated_at: record.updated_at,
4371
+ sequence: record.sequence
4372
+ };
4373
+ }
4374
+ function canonicalLocationPayload(payload) {
4375
+ return JSON.stringify({
4376
+ version: payload.version,
4377
+ subject: payload.subject,
4378
+ multiaddrs: payload.multiaddrs,
4379
+ updated_at: payload.updated_at,
4380
+ sequence: payload.sequence
4381
+ });
4382
+ }
4383
+ async function signLocationRecord(payload, signer) {
4384
+ validateLocationRecordPayload(payload);
4385
+ const message = canonicalLocationPayload(payload);
4386
+ const signature = signer.type === "did:pkh" ? await signer.signMessage(message) : base64UrlEncode2(await signer.signBytes(new TextEncoder().encode(message)));
4387
+ return { ...payload, signature };
4388
+ }
4389
+ function validateLocationRecordPayload(input) {
4390
+ if (input === null || typeof input !== "object") {
4391
+ throw new LocationRecordValidationError("payload must be an object");
4392
+ }
4393
+ const payload = input;
4394
+ if (payload.version !== 1) {
4395
+ throw new LocationRecordValidationError("version must be 1");
4396
+ }
4397
+ validateSubject(payload.subject);
4398
+ validateMultiaddrs(payload.multiaddrs);
4399
+ if (typeof payload.updated_at !== "string" || Number.isNaN(Date.parse(payload.updated_at))) {
4400
+ throw new LocationRecordValidationError("updated_at must be an ISO timestamp");
4401
+ }
4402
+ if (typeof payload.sequence !== "number" || !Number.isSafeInteger(payload.sequence) || payload.sequence < 0) {
4403
+ throw new LocationRecordValidationError(
4404
+ "sequence must be a non-negative safe integer"
4405
+ );
4406
+ }
4407
+ return {
4408
+ version: 1,
4409
+ subject: payload.subject,
4410
+ multiaddrs: [...payload.multiaddrs],
4411
+ updated_at: payload.updated_at,
4412
+ sequence: payload.sequence
4413
+ };
4414
+ }
4415
+ function validateLocationRecord(input) {
4416
+ const payload = validateLocationRecordPayload(input);
4417
+ const signature = input.signature;
4418
+ if (typeof signature !== "string" || signature.length === 0) {
4419
+ throw new LocationRecordValidationError("signature must be a non-empty string");
4420
+ }
4421
+ return { ...payload, signature };
4422
+ }
4423
+ async function verifyLocationRecord(input) {
4424
+ const record = validateLocationRecord(input);
4425
+ const payload = canonicalLocationPayload(locationPayloadForRecord(record));
4426
+ if (record.subject.startsWith("did:pkh:")) {
4427
+ return verifyPkhSignature(record.subject, payload, record.signature);
4428
+ }
4429
+ if (record.subject.startsWith("did:key:")) {
4430
+ return verifyDidKeySignature(record.subject, payload, record.signature);
4431
+ }
4432
+ return false;
4433
+ }
4434
+ async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
4435
+ const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
4436
+ const response = await fetchFn(url);
4437
+ if (response.status === 404) {
4438
+ return null;
4439
+ }
4440
+ if (!response.ok) {
4441
+ throw new Error(`location registry returned HTTP ${response.status}`);
4442
+ }
4443
+ const body = await response.json();
4444
+ if (body.record === void 0) {
4445
+ throw new LocationRecordValidationError("registry response missing record");
4446
+ }
4447
+ return validateLocationRecord(body.record);
4448
+ }
4449
+ async function resolveCloudLocation(subject, options = {}) {
4450
+ validateSubject(subject);
4451
+ const verifyRecords = options.verifyRecords ?? true;
4452
+ const attempts = await Promise.all([
4453
+ resolveExplicit(subject, options.explicitMultiaddrs),
4454
+ resolveBlockchain(subject, options.blockchain, verifyRecords),
4455
+ resolveCentralized(subject, options, verifyRecords),
4456
+ resolveFallback(subject, options.fallbackMultiaddrs)
4457
+ ]);
4458
+ const winner = attempts.find((attempt) => attempt.candidate)?.candidate;
4459
+ if (!winner) {
4460
+ throw new CloudLocationResolutionError(subject, attempts);
4461
+ }
4462
+ return {
4463
+ subject,
4464
+ source: winner.source,
4465
+ multiaddrs: [...winner.multiaddrs],
4466
+ ...winner.record ? { record: winner.record } : {},
4467
+ attempts,
4468
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
4469
+ };
4470
+ }
4471
+ function multiaddrToHttpUrl(input) {
4472
+ const uri = (0, import_multiaddr_to_uri.multiaddrToUri)((0, import_multiaddr.multiaddr)(input));
4473
+ if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
4474
+ throw new LocationRecordValidationError(
4475
+ `multiaddr does not resolve to http/https: ${input}`
4476
+ );
4477
+ }
4478
+ return uri;
4479
+ }
4480
+ function httpUrlToMultiaddr(input) {
4481
+ const url = new URL(input);
4482
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
4483
+ throw new LocationRecordValidationError("URL must use http or https");
4484
+ }
4485
+ return (0, import_uri_to_multiaddr.uriToMultiaddr)(url.toString()).toString();
4486
+ }
4487
+ async function resolveExplicit(subject, multiaddrs) {
4488
+ return resolveAttempt("explicit", async () => {
4489
+ if (multiaddrs === void 0 || multiaddrs.length === 0) {
4490
+ return null;
4491
+ }
4492
+ return toCandidate(subject, "explicit", multiaddrs, false);
4493
+ });
4494
+ }
4495
+ async function resolveBlockchain(subject, resolver, verifyRecords) {
4496
+ return resolveAttempt("blockchain", async () => {
4497
+ if (!resolver) {
4498
+ return null;
4499
+ }
4500
+ return toCandidate(subject, "blockchain", await resolver(subject), verifyRecords);
4501
+ });
4502
+ }
4503
+ async function resolveCentralized(subject, options, verifyRecords) {
4504
+ return resolveAttempt("centralized", async () => {
4505
+ if (!options.centralizedRegistryUrl) {
4506
+ return null;
4507
+ }
4508
+ const record = await fetchLocationRecord(
4509
+ options.centralizedRegistryUrl,
4510
+ subject,
4511
+ options.fetch
4512
+ );
4513
+ return toCandidate(subject, "centralized", record, verifyRecords);
4514
+ });
4515
+ }
4516
+ async function resolveFallback(subject, multiaddrs) {
4517
+ return resolveAttempt("fallback", async () => {
4518
+ if (multiaddrs === void 0 || multiaddrs.length === 0) {
4519
+ return null;
4520
+ }
4521
+ return toCandidate(subject, "fallback", multiaddrs, false);
4522
+ });
4523
+ }
4524
+ async function resolveAttempt(source, resolve) {
4525
+ try {
4526
+ const candidate = await resolve();
4527
+ return candidate ? { source, candidate } : { source };
4528
+ } catch (error) {
4529
+ return {
4530
+ source,
4531
+ error: error instanceof Error ? error : new Error(String(error))
4532
+ };
4533
+ }
4534
+ }
4535
+ async function toCandidate(subject, source, input, verifyRecord) {
4536
+ if (input === null || input === void 0) {
4537
+ return null;
4538
+ }
4539
+ if (Array.isArray(input)) {
4540
+ validateMultiaddrs(input);
4541
+ return { source, multiaddrs: [...input] };
4542
+ }
4543
+ const maybeRecord = input;
4544
+ if (maybeRecord.version === 1 && maybeRecord.signature !== void 0) {
4545
+ const record = validateLocationRecord(input);
4546
+ if (record.subject !== subject) {
4547
+ throw new LocationRecordValidationError(
4548
+ "location record subject does not match requested subject"
4549
+ );
4550
+ }
4551
+ if (verifyRecord && !await verifyLocationRecord(record)) {
4552
+ throw new LocationRecordValidationError("location record signature is invalid");
4553
+ }
4554
+ return { source, multiaddrs: [...record.multiaddrs], record };
4555
+ }
4556
+ const candidateInput = input;
4557
+ if (!Array.isArray(candidateInput.multiaddrs)) {
4558
+ throw new LocationRecordValidationError("candidate multiaddrs must be an array");
4559
+ }
4560
+ validateMultiaddrs(candidateInput.multiaddrs);
4561
+ if (candidateInput.record !== void 0) {
4562
+ const record = validateLocationRecord(candidateInput.record);
4563
+ if (record.subject !== subject) {
4564
+ throw new LocationRecordValidationError(
4565
+ "location record subject does not match requested subject"
4566
+ );
4567
+ }
4568
+ if (verifyRecord && !await verifyLocationRecord(record)) {
4569
+ throw new LocationRecordValidationError("location record signature is invalid");
4570
+ }
4571
+ return { source, multiaddrs: [...candidateInput.multiaddrs], record };
4572
+ }
4573
+ return { source, multiaddrs: [...candidateInput.multiaddrs] };
4574
+ }
4575
+ function validateSubject(subject) {
4576
+ if (typeof subject !== "string" || subject.length === 0) {
4577
+ throw new LocationRecordValidationError("subject must be a non-empty string");
4578
+ }
4579
+ if (!subject.startsWith("did:pkh:") && !subject.startsWith("did:key:")) {
4580
+ throw new LocationRecordValidationError("subject must be did:pkh or did:key");
4581
+ }
4582
+ }
4583
+ function validateMultiaddrs(input) {
4584
+ if (!Array.isArray(input)) {
4585
+ throw new LocationRecordValidationError("multiaddrs must be an array");
4586
+ }
4587
+ for (const addr of input) {
4588
+ if (typeof addr !== "string" || addr.length === 0) {
4589
+ throw new LocationRecordValidationError(
4590
+ "multiaddr entries must be non-empty strings"
4591
+ );
4592
+ }
4593
+ try {
4594
+ (0, import_multiaddr.multiaddr)(addr);
4595
+ } catch {
4596
+ throw new LocationRecordValidationError(`invalid multiaddr: ${addr}`);
4597
+ }
4598
+ }
4599
+ }
4600
+ async function verifyPkhSignature(did, payload, signature) {
4601
+ const address = did.split(":").at(-1);
4602
+ if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address)) {
4603
+ throw new LocationRecordValidationError(
4604
+ "did:pkh subject must end with an EVM address"
4605
+ );
4606
+ }
4607
+ if (!/^0x[0-9a-fA-F]+$/.test(signature)) {
4608
+ throw new LocationRecordValidationError("did:pkh signature must be hex");
4609
+ }
4610
+ return (0, import_viem.verifyMessage)({
4611
+ address,
4612
+ message: payload,
4613
+ signature
4614
+ });
4615
+ }
4616
+ function verifyDidKeySignature(did, payload, signature) {
4617
+ const publicKey = ed25519PublicKeyFromDidKey(did);
4618
+ const signatureBytes = decodeBase64Url(signature);
4619
+ if (signatureBytes.length !== 64) {
4620
+ throw new LocationRecordValidationError(
4621
+ "did:key signature must be a base64url Ed25519 signature"
4622
+ );
4623
+ }
4624
+ return import_ed25519.ed25519.verify(signatureBytes, new TextEncoder().encode(payload), publicKey);
4625
+ }
4626
+ function ed25519PublicKeyFromDidKey(did) {
4627
+ const identifier = did.slice("did:key:".length);
4628
+ if (!identifier.startsWith("z")) {
4629
+ throw new LocationRecordValidationError("did:key must use base58btc multibase");
4630
+ }
4631
+ const bytes = import_basics.bases.base58btc.decode(identifier);
4632
+ if (bytes.length !== 34 || bytes[0] !== 237 || bytes[1] !== 1) {
4633
+ throw new LocationRecordValidationError("did:key must be an Ed25519 public key");
4634
+ }
4635
+ return bytes.slice(2);
4636
+ }
4637
+ function base64UrlEncode2(bytes) {
4638
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4639
+ let output = "";
4640
+ for (let i = 0; i < bytes.length; i += 3) {
4641
+ const a = bytes[i];
4642
+ const b = bytes[i + 1];
4643
+ const c = bytes[i + 2];
4644
+ const triplet = a << 16 | (b ?? 0) << 8 | (c ?? 0);
4645
+ output += alphabet[triplet >> 18 & 63];
4646
+ output += alphabet[triplet >> 12 & 63];
4647
+ if (i + 1 < bytes.length) {
4648
+ output += alphabet[triplet >> 6 & 63];
4649
+ }
4650
+ if (i + 2 < bytes.length) {
4651
+ output += alphabet[triplet & 63];
4652
+ }
4653
+ }
4654
+ return output;
4655
+ }
4656
+ function decodeBase64Url(value) {
4657
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4658
+ const bytes = [];
4659
+ let buffer = 0;
4660
+ let bits = 0;
4661
+ for (const char of value) {
4662
+ const index = alphabet.indexOf(char);
4663
+ if (index < 0) {
4664
+ throw new LocationRecordValidationError(
4665
+ "did:key signature must be base64url"
4666
+ );
4667
+ }
4668
+ buffer = buffer << 6 | index;
4669
+ bits += 6;
4670
+ if (bits >= 8) {
4671
+ bits -= 8;
4672
+ bytes.push(buffer >> bits & 255);
4673
+ }
4674
+ }
4675
+ return Uint8Array.from(bytes);
4676
+ }
4677
+
4333
4678
  // src/capabilities.ts
4334
4679
  var PermissionNotInManifestError = class extends Error {
4335
4680
  constructor(missing, granted) {
@@ -4451,6 +4796,7 @@ function parseRecapCapabilities(parseWasm, siwe) {
4451
4796
  CapabilityKeyRegistry,
4452
4797
  CapabilityKeyRegistryErrorCodes,
4453
4798
  ClientSessionSchema,
4799
+ CloudLocationResolutionError,
4454
4800
  DEFAULT_DEFAULTS,
4455
4801
  DEFAULT_EXPIRY,
4456
4802
  DEFAULT_MANIFEST_SPACE,
@@ -4466,6 +4812,7 @@ function parseRecapCapabilities(parseWasm, siwe) {
4466
4812
  ErrorCodes,
4467
4813
  HooksService,
4468
4814
  KVService,
4815
+ LocationRecordValidationError,
4469
4816
  ManifestValidationError,
4470
4817
  PermissionNotInManifestError,
4471
4818
  PrefixedKVService,
@@ -4491,6 +4838,7 @@ function parseRecapCapabilities(parseWasm, siwe) {
4491
4838
  activateSessionWithHost,
4492
4839
  applyPrefix,
4493
4840
  buildSpaceUri,
4841
+ canonicalLocationPayload,
4494
4842
  checkNodeInfo,
4495
4843
  composeManifestRequest,
4496
4844
  createCapabilityKeyRegistry,
@@ -4502,23 +4850,32 @@ function parseRecapCapabilities(parseWasm, siwe) {
4502
4850
  defaultSpaceCreationHandler,
4503
4851
  err,
4504
4852
  expandActionShortNames,
4853
+ fetchLocationRecord,
4505
4854
  fetchPeerId,
4855
+ httpUrlToMultiaddr,
4506
4856
  isCapabilitySubset,
4507
4857
  loadManifest,
4858
+ locationPayloadForRecord,
4508
4859
  makePublicSpaceId,
4509
4860
  manifestAbilitiesUnion,
4861
+ multiaddrToHttpUrl,
4510
4862
  normalizeDefaults,
4511
4863
  ok,
4512
4864
  parseExpiry,
4513
4865
  parseRecapCapabilities,
4514
4866
  parseSpaceUri,
4867
+ resolveCloudLocation,
4515
4868
  resolveManifest,
4516
4869
  resourceCapabilitiesToAbilitiesMap,
4517
4870
  resourceCapabilitiesToSpaceAbilitiesMap,
4518
4871
  serviceError,
4872
+ signLocationRecord,
4519
4873
  submitHostDelegation,
4520
4874
  validateClientSession,
4875
+ validateLocationRecord,
4876
+ validateLocationRecordPayload,
4521
4877
  validateManifest,
4522
- validatePersistedSessionData
4878
+ validatePersistedSessionData,
4879
+ verifyLocationRecord
4523
4880
  });
4524
4881
  //# sourceMappingURL=index.cjs.map