@tinycloud/sdk-core 3.11.0 → 3.12.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 +866 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -69
- package/dist/index.d.ts +146 -69
- package/dist/index.js +866 -58
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -88,6 +88,7 @@ __export(index_exports, {
|
|
|
88
88
|
DEFAULT_MANIFEST_SPACE: () => DEFAULT_MANIFEST_SPACE,
|
|
89
89
|
DEFAULT_MANIFEST_VERSION: () => DEFAULT_MANIFEST_VERSION,
|
|
90
90
|
DEFAULT_NEW_USER_HOST: () => DEFAULT_NEW_USER_HOST,
|
|
91
|
+
DEFAULT_REGISTRY_CACHE_STORAGE_KEY: () => DEFAULT_REGISTRY_CACHE_STORAGE_KEY,
|
|
91
92
|
DEFAULT_REGISTRY_LOOKUP_TIMEOUT_MS: () => DEFAULT_REGISTRY_LOOKUP_TIMEOUT_MS,
|
|
92
93
|
DEFAULT_REGISTRY_OWNER_CHAIN_ID: () => DEFAULT_REGISTRY_OWNER_CHAIN_ID,
|
|
93
94
|
DEFAULT_REGISTRY_SEQUENCE_STORAGE_KEY: () => DEFAULT_REGISTRY_SEQUENCE_STORAGE_KEY,
|
|
@@ -179,6 +180,7 @@ __export(index_exports, {
|
|
|
179
180
|
PermissionNotInManifestError: () => PermissionNotInManifestError,
|
|
180
181
|
PrefixedKVService: () => import_sdk_services10.PrefixedKVService,
|
|
181
182
|
ProtocolMismatchError: () => ProtocolMismatchError,
|
|
183
|
+
REGISTRY_CACHE_MAX_AGE_MS: () => REGISTRY_CACHE_MAX_AGE_MS,
|
|
182
184
|
REGISTRY_LOOKUP_MAX_BODY_BYTES: () => REGISTRY_LOOKUP_MAX_BODY_BYTES,
|
|
183
185
|
REGISTRY_NOT_FOUND_BODY: () => REGISTRY_NOT_FOUND_BODY,
|
|
184
186
|
REQUESTER_ENGINE_RETRY_ATTEMPTS: () => REQUESTER_ENGINE_RETRY_ATTEMPTS,
|
|
@@ -279,6 +281,7 @@ __export(index_exports, {
|
|
|
279
281
|
canonicalEmailDomain: () => import_share_envelope.canonicalEmailDomain,
|
|
280
282
|
canonicalHashHex: () => import_sdk_services10.canonicalHashHex,
|
|
281
283
|
canonicalLocationPayload: () => canonicalLocationPayload,
|
|
284
|
+
canonicalLocationPayloadV2: () => canonicalLocationPayloadV2,
|
|
282
285
|
canonicalMailbox: () => import_share_envelope.canonicalMailbox,
|
|
283
286
|
canonicalOwnerSharePolicy: () => canonicalOwnerSharePolicy,
|
|
284
287
|
canonicalSignedResponse: () => import_sdk_services10.canonicalSignedResponse,
|
|
@@ -312,8 +315,10 @@ __export(index_exports, {
|
|
|
312
315
|
createEmailDomainCredentialRequirement: () => createEmailDomainCredentialRequirement,
|
|
313
316
|
createHolderBinding: () => createHolderBinding,
|
|
314
317
|
createInMemoryLocalNodeIdentityStore: () => createInMemoryLocalNodeIdentityStore,
|
|
318
|
+
createInMemoryRegistryCacheStore: () => createInMemoryRegistryCacheStore,
|
|
315
319
|
createInMemoryRegistrySequenceStore: () => createInMemoryRegistrySequenceStore,
|
|
316
320
|
createLocalStorageLocalNodeIdentityStore: () => createLocalStorageLocalNodeIdentityStore,
|
|
321
|
+
createLocalStorageRegistryCacheStore: () => createLocalStorageRegistryCacheStore,
|
|
317
322
|
createLocalStorageRegistrySequenceStore: () => createLocalStorageRegistrySequenceStore,
|
|
318
323
|
createOpenKeyCallbackSigningStrategy: () => createOpenKeyCallbackSigningStrategy,
|
|
319
324
|
createOpenKeyManageKeySigningStrategy: () => createOpenKeyManageKeySigningStrategy,
|
|
@@ -365,6 +370,7 @@ __export(index_exports, {
|
|
|
365
370
|
encryptionUtf8Encode: () => import_sdk_services10.utf8Encode,
|
|
366
371
|
ensureNetworkUsableForDecrypt: () => import_sdk_services10.ensureNetworkUsableForDecrypt,
|
|
367
372
|
err: () => import_sdk_services10.err,
|
|
373
|
+
exactHttpOrigin: () => exactHttpOrigin,
|
|
368
374
|
expandActionShortNames: () => expandActionShortNames,
|
|
369
375
|
expandDelegationPermissionEntries: () => expandDelegationPermissionEntries,
|
|
370
376
|
expandPermissionEntries: () => expandPermissionEntries,
|
|
@@ -448,7 +454,9 @@ __export(index_exports, {
|
|
|
448
454
|
projectUnifiedPolicyCapability: () => projectUnifiedPolicyCapability,
|
|
449
455
|
publishLocationRecord: () => publishLocationRecord,
|
|
450
456
|
recapCaveatsEqual: () => recapCaveatsEqual,
|
|
457
|
+
refreshOwnerRegistry: () => refreshOwnerRegistry,
|
|
451
458
|
registerPolicyV3: () => registerPolicyV3,
|
|
459
|
+
registryOwnerKeyV2: () => registryOwnerKeyV2,
|
|
452
460
|
registryOwnerSubjectV1: () => registryOwnerSubjectV1,
|
|
453
461
|
renewPolicyRootStatusV3: () => renewPolicyRootStatusV3,
|
|
454
462
|
requestTinyCloudManageKeyScope: () => requestTinyCloudManageKeyScope,
|
|
@@ -457,6 +465,7 @@ __export(index_exports, {
|
|
|
457
465
|
resolveManifest: () => resolveManifest,
|
|
458
466
|
resolveManifestKnowledgeRoot: () => resolveManifestKnowledgeRoot,
|
|
459
467
|
resolveOwnerHosts: () => resolveOwnerHosts,
|
|
468
|
+
resolveRegistryResource: () => resolveRegistryResource,
|
|
460
469
|
resolveSecretListPrefix: () => import_sdk_services10.resolveSecretListPrefix,
|
|
461
470
|
resolveSecretPath: () => import_sdk_services10.resolveSecretPath,
|
|
462
471
|
resolveTinyCloudHosts: () => resolveTinyCloudHosts,
|
|
@@ -474,6 +483,7 @@ __export(index_exports, {
|
|
|
474
483
|
signCompactUcanAuthorization: () => signCompactUcanAuthorization,
|
|
475
484
|
signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
|
|
476
485
|
signLocationRecord: () => signLocationRecord,
|
|
486
|
+
signLocationRecordV2: () => signLocationRecordV2,
|
|
477
487
|
signPolicyCredentialPresentationV4: () => signPolicyCredentialPresentationV4,
|
|
478
488
|
signShareEnvelope: () => signShareEnvelope,
|
|
479
489
|
signedObjectIdFor: () => signedObjectIdFor,
|
|
@@ -495,6 +505,8 @@ __export(index_exports, {
|
|
|
495
505
|
validateEnvelope: () => import_sdk_services10.validateEnvelope,
|
|
496
506
|
validateLocationRecord: () => validateLocationRecord,
|
|
497
507
|
validateLocationRecordPayload: () => validateLocationRecordPayload,
|
|
508
|
+
validateLocationRecordV2: () => validateLocationRecordV2,
|
|
509
|
+
validateLocationRecordV2Payload: () => validateLocationRecordV2Payload,
|
|
498
510
|
validateManifest: () => validateManifest,
|
|
499
511
|
validateOwnerSharePolicyRegistration: () => validateOwnerSharePolicyRegistration,
|
|
500
512
|
validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes,
|
|
@@ -516,17 +528,20 @@ __export(index_exports, {
|
|
|
516
528
|
verifyEip191MessageSignature: () => verifyEip191MessageSignature,
|
|
517
529
|
verifyIssuedCredential: () => verifyIssuedCredential,
|
|
518
530
|
verifyLocationRecord: () => verifyLocationRecord,
|
|
531
|
+
verifyLocationRecordV2: () => verifyLocationRecordV2,
|
|
519
532
|
verifyOwnerNodeBinding: () => verifyOwnerNodeBinding,
|
|
520
533
|
verifyPolicy: () => verifyPolicy,
|
|
521
534
|
verifyPolicyEngineRecord: () => verifyPolicyEngineRecord,
|
|
522
535
|
verifyPolicyRootRevocationV3: () => verifyPolicyRootRevocationV3,
|
|
523
536
|
verifyPolicyRootStatusCheckpointV3: () => verifyPolicyRootStatusCheckpointV3,
|
|
524
537
|
verifyPolicyStatus: () => verifyPolicyStatus,
|
|
538
|
+
verifyRegistryCacheEntry: () => verifyRegistryCacheEntry,
|
|
525
539
|
verifyShareCid: () => verifyShareCid,
|
|
526
540
|
verifyShareDeliveryAuthorizationV3Bytes: () => verifyShareDeliveryAuthorizationV3Bytes,
|
|
527
541
|
verifyShareEnvelope: () => verifyShareEnvelope,
|
|
528
542
|
verifySignedObject: () => verifySignedObject,
|
|
529
543
|
verifyStorageReceipt: () => verifyStorageReceipt,
|
|
544
|
+
warnRegistryDefaultNode: () => warnRegistryDefaultNode,
|
|
530
545
|
withoutShareLink: () => withoutShareLink
|
|
531
546
|
});
|
|
532
547
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -18748,6 +18763,298 @@ async function verifyLocationRecord(input) {
|
|
|
18748
18763
|
}
|
|
18749
18764
|
return false;
|
|
18750
18765
|
}
|
|
18766
|
+
function canonicalLocationPayloadV2(payload) {
|
|
18767
|
+
return JSON.stringify({
|
|
18768
|
+
version: payload.version,
|
|
18769
|
+
subject: payload.subject,
|
|
18770
|
+
nodes: payload.nodes.map(({ did, url }) => ({ did, url })).sort((left, right) => compareAscii(left.did, right.did)),
|
|
18771
|
+
spaces: payload.spaces.map(({ space, nodes }) => ({
|
|
18772
|
+
space,
|
|
18773
|
+
nodes: nodes.map(({ did, mode }) => ({ did, mode })).sort((left, right) => compareAscii(left.did, right.did))
|
|
18774
|
+
})).sort((left, right) => compareAscii(left.space, right.space)),
|
|
18775
|
+
encryptionNode: payload.encryptionNode,
|
|
18776
|
+
updated_at: payload.updated_at,
|
|
18777
|
+
sequence: payload.sequence
|
|
18778
|
+
});
|
|
18779
|
+
}
|
|
18780
|
+
function validateLocationRecordV2Payload(input) {
|
|
18781
|
+
const payload = exactV2Object(
|
|
18782
|
+
input,
|
|
18783
|
+
["version", "subject", "nodes", "spaces", "encryptionNode", "updated_at", "sequence"],
|
|
18784
|
+
"payload"
|
|
18785
|
+
);
|
|
18786
|
+
if (payload.version !== 2) {
|
|
18787
|
+
throw new LocationRecordValidationError("version must be 2");
|
|
18788
|
+
}
|
|
18789
|
+
validateV2OwnerSubject(payload.subject);
|
|
18790
|
+
if (!Array.isArray(payload.nodes)) {
|
|
18791
|
+
throw new LocationRecordValidationError("nodes must be an array");
|
|
18792
|
+
}
|
|
18793
|
+
const nodeDids = /* @__PURE__ */ new Set();
|
|
18794
|
+
const origins = /* @__PURE__ */ new Set();
|
|
18795
|
+
const nodes = [];
|
|
18796
|
+
for (const item of payload.nodes) {
|
|
18797
|
+
const node = exactV2Object(item, ["did", "url"], "node");
|
|
18798
|
+
if (typeof node.did !== "string") {
|
|
18799
|
+
throw new LocationRecordValidationError("node.did must be a string");
|
|
18800
|
+
}
|
|
18801
|
+
validateV2DidKey(node.did, "node.did");
|
|
18802
|
+
if (typeof node.url !== "string") {
|
|
18803
|
+
throw new LocationRecordValidationError("node.url must be a string");
|
|
18804
|
+
}
|
|
18805
|
+
const origin2 = validateV2Origin(node.url);
|
|
18806
|
+
if (nodeDids.has(node.did) || origins.has(origin2)) {
|
|
18807
|
+
throw new LocationRecordValidationError("node DIDs and origins must be unique");
|
|
18808
|
+
}
|
|
18809
|
+
nodeDids.add(node.did);
|
|
18810
|
+
origins.add(origin2);
|
|
18811
|
+
nodes.push({ did: node.did, url: origin2 });
|
|
18812
|
+
}
|
|
18813
|
+
if (!Array.isArray(payload.spaces)) {
|
|
18814
|
+
throw new LocationRecordValidationError("spaces must be an array");
|
|
18815
|
+
}
|
|
18816
|
+
const spaceKeys = /* @__PURE__ */ new Set();
|
|
18817
|
+
const spaces = [];
|
|
18818
|
+
for (const item of payload.spaces) {
|
|
18819
|
+
const space = exactV2Object(item, ["space", "nodes"], "space");
|
|
18820
|
+
if (typeof space.space !== "string" || !isAsciiNonEmpty(space.space)) {
|
|
18821
|
+
throw new LocationRecordValidationError("space.space must be a non-empty ASCII URI");
|
|
18822
|
+
}
|
|
18823
|
+
const identity3 = validateV2SpaceOwner(space.space, payload.subject);
|
|
18824
|
+
if (spaceKeys.has(identity3)) {
|
|
18825
|
+
throw new LocationRecordValidationError("spaces must be unique");
|
|
18826
|
+
}
|
|
18827
|
+
spaceKeys.add(identity3);
|
|
18828
|
+
if (!Array.isArray(space.nodes)) {
|
|
18829
|
+
throw new LocationRecordValidationError("space.nodes must be an array");
|
|
18830
|
+
}
|
|
18831
|
+
const membershipDids = /* @__PURE__ */ new Set();
|
|
18832
|
+
const memberships = [];
|
|
18833
|
+
for (const item2 of space.nodes) {
|
|
18834
|
+
const membership = exactV2Object(item2, ["did", "mode"], "space membership");
|
|
18835
|
+
if (typeof membership.did !== "string") {
|
|
18836
|
+
throw new LocationRecordValidationError("membership.did must be a string");
|
|
18837
|
+
}
|
|
18838
|
+
validateV2DidKey(membership.did, "membership.did");
|
|
18839
|
+
if (!nodeDids.has(membership.did)) {
|
|
18840
|
+
throw new LocationRecordValidationError("space membership must reference a listed node");
|
|
18841
|
+
}
|
|
18842
|
+
if (membershipDids.has(membership.did)) {
|
|
18843
|
+
throw new LocationRecordValidationError("space membership DIDs must be unique");
|
|
18844
|
+
}
|
|
18845
|
+
if (membership.mode !== "read-write" && membership.mode !== "read-only") {
|
|
18846
|
+
throw new LocationRecordValidationError("membership.mode must be read-write or read-only");
|
|
18847
|
+
}
|
|
18848
|
+
membershipDids.add(membership.did);
|
|
18849
|
+
memberships.push({ did: membership.did, mode: membership.mode });
|
|
18850
|
+
}
|
|
18851
|
+
spaces.push({ space: space.space, nodes: memberships });
|
|
18852
|
+
}
|
|
18853
|
+
if (payload.encryptionNode !== null) {
|
|
18854
|
+
if (typeof payload.encryptionNode !== "string") {
|
|
18855
|
+
throw new LocationRecordValidationError("encryptionNode must be a DID or null");
|
|
18856
|
+
}
|
|
18857
|
+
validateV2DidKey(payload.encryptionNode, "encryptionNode");
|
|
18858
|
+
if (!nodeDids.has(payload.encryptionNode)) {
|
|
18859
|
+
throw new LocationRecordValidationError("encryptionNode must reference a listed node");
|
|
18860
|
+
}
|
|
18861
|
+
}
|
|
18862
|
+
if (typeof payload.updated_at !== "string" || !isV2UtcTimestamp(payload.updated_at)) {
|
|
18863
|
+
throw new LocationRecordValidationError("updated_at must be a valid UTC RFC 3339 timestamp with milliseconds");
|
|
18864
|
+
}
|
|
18865
|
+
if (typeof payload.sequence !== "number" || !Number.isSafeInteger(payload.sequence) || payload.sequence < 0) {
|
|
18866
|
+
throw new LocationRecordValidationError("sequence must be a non-negative safe integer");
|
|
18867
|
+
}
|
|
18868
|
+
return {
|
|
18869
|
+
version: 2,
|
|
18870
|
+
subject: payload.subject,
|
|
18871
|
+
nodes,
|
|
18872
|
+
spaces,
|
|
18873
|
+
encryptionNode: payload.encryptionNode,
|
|
18874
|
+
updated_at: payload.updated_at,
|
|
18875
|
+
sequence: payload.sequence
|
|
18876
|
+
};
|
|
18877
|
+
}
|
|
18878
|
+
function validateLocationRecordV2(input) {
|
|
18879
|
+
const record2 = exactV2Object(
|
|
18880
|
+
input,
|
|
18881
|
+
["version", "subject", "nodes", "spaces", "encryptionNode", "updated_at", "sequence", "signature"],
|
|
18882
|
+
"record"
|
|
18883
|
+
);
|
|
18884
|
+
const payload = validateLocationRecordV2Payload({
|
|
18885
|
+
version: record2.version,
|
|
18886
|
+
subject: record2.subject,
|
|
18887
|
+
nodes: record2.nodes,
|
|
18888
|
+
spaces: record2.spaces,
|
|
18889
|
+
encryptionNode: record2.encryptionNode,
|
|
18890
|
+
updated_at: record2.updated_at,
|
|
18891
|
+
sequence: record2.sequence
|
|
18892
|
+
});
|
|
18893
|
+
if (typeof record2.signature !== "string" || !isCanonicalV2Signature(payload.subject, record2.signature)) {
|
|
18894
|
+
throw new LocationRecordValidationError("signature is not in canonical owner-signature format");
|
|
18895
|
+
}
|
|
18896
|
+
return { ...payload, signature: record2.signature };
|
|
18897
|
+
}
|
|
18898
|
+
async function verifyLocationRecordV2(input) {
|
|
18899
|
+
const record2 = validateLocationRecordV2(input);
|
|
18900
|
+
const payload = canonicalLocationPayloadV2(record2);
|
|
18901
|
+
if (record2.subject.startsWith("did:pkh:")) {
|
|
18902
|
+
const address = record2.subject.slice(record2.subject.lastIndexOf(":") + 1);
|
|
18903
|
+
try {
|
|
18904
|
+
return await (0, import_viem6.verifyMessage)({
|
|
18905
|
+
address,
|
|
18906
|
+
message: payload,
|
|
18907
|
+
signature: record2.signature
|
|
18908
|
+
});
|
|
18909
|
+
} catch {
|
|
18910
|
+
return false;
|
|
18911
|
+
}
|
|
18912
|
+
}
|
|
18913
|
+
try {
|
|
18914
|
+
return import_ed2551911.ed25519.verify(
|
|
18915
|
+
decodeBase64Url4(record2.signature),
|
|
18916
|
+
new TextEncoder().encode(payload),
|
|
18917
|
+
canonicalV2DidKey(record2.subject),
|
|
18918
|
+
{ zip215: false }
|
|
18919
|
+
);
|
|
18920
|
+
} catch {
|
|
18921
|
+
return false;
|
|
18922
|
+
}
|
|
18923
|
+
}
|
|
18924
|
+
async function signLocationRecordV2(payload, signer) {
|
|
18925
|
+
const validated = validateLocationRecordV2Payload(payload);
|
|
18926
|
+
const message = canonicalLocationPayloadV2(validated);
|
|
18927
|
+
const isPkh = validated.subject.startsWith("did:pkh:");
|
|
18928
|
+
if (signer.type === "did:pkh" !== isPkh) {
|
|
18929
|
+
throw new LocationRecordValidationError("signer type does not match owner subject");
|
|
18930
|
+
}
|
|
18931
|
+
const signature = signer.type === "did:pkh" ? await signer.signMessage(message) : base64UrlEncode3(await signer.signBytes(new TextEncoder().encode(message)));
|
|
18932
|
+
const record2 = validateLocationRecordV2({ ...validated, signature });
|
|
18933
|
+
if (!await verifyLocationRecordV2(record2)) {
|
|
18934
|
+
throw new LocationRecordValidationError("signature does not verify for owner subject");
|
|
18935
|
+
}
|
|
18936
|
+
return record2;
|
|
18937
|
+
}
|
|
18938
|
+
function exactV2Object(input, expected, label) {
|
|
18939
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
18940
|
+
throw new LocationRecordValidationError(`${label} must be an object`);
|
|
18941
|
+
}
|
|
18942
|
+
const keys = Reflect.ownKeys(input);
|
|
18943
|
+
if (keys.length !== expected.length || keys.some((key) => typeof key !== "string" || !expected.includes(key))) {
|
|
18944
|
+
throw new LocationRecordValidationError(`${label} has missing or unknown fields`);
|
|
18945
|
+
}
|
|
18946
|
+
return input;
|
|
18947
|
+
}
|
|
18948
|
+
function validateV2OwnerSubject(subject) {
|
|
18949
|
+
if (typeof subject !== "string" || !isAsciiNonEmpty(subject)) {
|
|
18950
|
+
throw new LocationRecordValidationError("subject must be a canonical owner DID");
|
|
18951
|
+
}
|
|
18952
|
+
const pkh = /^did:pkh:eip155:([1-9][0-9]*):(0x[a-fA-F0-9]{40})$/.exec(subject);
|
|
18953
|
+
if (pkh !== null) {
|
|
18954
|
+
const chainId = Number(pkh[1]);
|
|
18955
|
+
try {
|
|
18956
|
+
if (Number.isSafeInteger(chainId) && chainId > 0 && pkhDid(pkh[2], chainId) === subject) return;
|
|
18957
|
+
} catch {
|
|
18958
|
+
}
|
|
18959
|
+
throw new LocationRecordValidationError("subject must use a positive safe chain ID and EIP-55 address");
|
|
18960
|
+
}
|
|
18961
|
+
validateV2DidKey(subject, "subject");
|
|
18962
|
+
}
|
|
18963
|
+
function validateV2DidKey(value, label) {
|
|
18964
|
+
try {
|
|
18965
|
+
canonicalV2DidKey(value);
|
|
18966
|
+
} catch {
|
|
18967
|
+
throw new LocationRecordValidationError(`${label} must be a canonical Ed25519 did:key`);
|
|
18968
|
+
}
|
|
18969
|
+
}
|
|
18970
|
+
function canonicalV2DidKey(did) {
|
|
18971
|
+
if (!did.startsWith("did:key:z")) {
|
|
18972
|
+
throw new LocationRecordValidationError("DID must be an Ed25519 did:key");
|
|
18973
|
+
}
|
|
18974
|
+
const identifier = did.slice("did:key:".length);
|
|
18975
|
+
const bytes2 = bases.base58btc.decode(identifier);
|
|
18976
|
+
if (bytes2.length !== 34 || bytes2[0] !== 237 || bytes2[1] !== 1 || bases.base58btc.encode(bytes2) !== identifier) {
|
|
18977
|
+
throw new LocationRecordValidationError("DID must use the canonical Ed25519 multicodec");
|
|
18978
|
+
}
|
|
18979
|
+
return bytes2.slice(2);
|
|
18980
|
+
}
|
|
18981
|
+
function validateV2Origin(input) {
|
|
18982
|
+
if (!isAsciiNonEmpty(input)) {
|
|
18983
|
+
throw new LocationRecordValidationError("node.url must be an ASCII HTTP(S) origin");
|
|
18984
|
+
}
|
|
18985
|
+
let url;
|
|
18986
|
+
try {
|
|
18987
|
+
url = new URL(input);
|
|
18988
|
+
} catch {
|
|
18989
|
+
throw new LocationRecordValidationError("node.url must be an absolute HTTP(S) origin");
|
|
18990
|
+
}
|
|
18991
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" || url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "" || url.origin !== input) {
|
|
18992
|
+
throw new LocationRecordValidationError("node.url must be an exact origin without path, query, fragment, or credentials");
|
|
18993
|
+
}
|
|
18994
|
+
if (url.protocol === "http:" && url.hostname !== "127.0.0.1" && url.hostname !== "[::1]") {
|
|
18995
|
+
throw new LocationRecordValidationError("HTTP node URLs are allowed only for IPv4 or IPv6 loopback");
|
|
18996
|
+
}
|
|
18997
|
+
return url.origin;
|
|
18998
|
+
}
|
|
18999
|
+
function validateV2SpaceOwner(space, subject) {
|
|
19000
|
+
if (!/^[A-Za-z0-9._~!$&'()*+,;=:@%-]+$/.test(space)) {
|
|
19001
|
+
throw new LocationRecordValidationError("space must be a complete normalized TinyCloud space URI");
|
|
19002
|
+
}
|
|
19003
|
+
for (let index = space.indexOf("%"); index !== -1; index = space.indexOf("%", index + 3)) {
|
|
19004
|
+
const octet = space.slice(index + 1, index + 3);
|
|
19005
|
+
if (!/^[0-9A-F]{2}$/.test(octet) || /^[A-Za-z0-9._~-]$/.test(String.fromCharCode(Number.parseInt(octet, 16)))) {
|
|
19006
|
+
throw new LocationRecordValidationError("space URI percent encoding must be normalized");
|
|
19007
|
+
}
|
|
19008
|
+
}
|
|
19009
|
+
if (subject.startsWith("did:key:")) {
|
|
19010
|
+
const prefix = `tinycloud:${subject.slice("did:".length)}:`;
|
|
19011
|
+
if (!space.startsWith(prefix) || space.length === prefix.length || space.slice(prefix.length).includes(":")) {
|
|
19012
|
+
throw new LocationRecordValidationError("space owner must match the did:key subject");
|
|
19013
|
+
}
|
|
19014
|
+
return space;
|
|
19015
|
+
}
|
|
19016
|
+
const parts = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[a-fA-F0-9]{40}):([^:]+)$/.exec(space);
|
|
19017
|
+
const ownerAddress = subject.slice(subject.lastIndexOf(":") + 1);
|
|
19018
|
+
if (parts === null || !Number.isSafeInteger(Number(parts[1])) || Number(parts[1]) <= 0 || parts[2].toLowerCase() !== ownerAddress.toLowerCase()) {
|
|
19019
|
+
throw new LocationRecordValidationError("space owner address must match the did:pkh subject");
|
|
19020
|
+
}
|
|
19021
|
+
return `tinycloud:pkh:eip155:${parts[1]}:${parts[2].toLowerCase()}:${parts[3]}`;
|
|
19022
|
+
}
|
|
19023
|
+
function isV2UtcTimestamp(value) {
|
|
19024
|
+
const match = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})\.([0-9]{3})Z$/.exec(value);
|
|
19025
|
+
if (match === null) return false;
|
|
19026
|
+
const [year, month, day, hour, minute, second] = match.slice(1, 7).map(Number);
|
|
19027
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
19028
|
+
const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1];
|
|
19029
|
+
return year > 0 && daysInMonth !== void 0 && day >= 1 && day <= daysInMonth && hour <= 23 && minute <= 59 && second <= 59;
|
|
19030
|
+
}
|
|
19031
|
+
function isCanonicalV2Signature(subject, signature) {
|
|
19032
|
+
if (subject.startsWith("did:pkh:")) {
|
|
19033
|
+
if (!/^0x[0-9a-fA-F]{130}$/.test(signature)) return false;
|
|
19034
|
+
const r = BigInt(`0x${signature.slice(2, 66)}`);
|
|
19035
|
+
const s = BigInt(`0x${signature.slice(66, 130)}`);
|
|
19036
|
+
const v = Number.parseInt(signature.slice(130), 16);
|
|
19037
|
+
const curveOrder = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
|
19038
|
+
return r > 0n && r < curveOrder && s > 0n && s <= curveOrder / 2n && (v === 27 || v === 28);
|
|
19039
|
+
}
|
|
19040
|
+
if (!/^[A-Za-z0-9_-]{86}$/.test(signature)) return false;
|
|
19041
|
+
try {
|
|
19042
|
+
const bytes2 = decodeBase64Url4(signature);
|
|
19043
|
+
return bytes2.length === 64 && base64UrlEncode3(bytes2) === signature;
|
|
19044
|
+
} catch {
|
|
19045
|
+
return false;
|
|
19046
|
+
}
|
|
19047
|
+
}
|
|
19048
|
+
function isAsciiNonEmpty(value) {
|
|
19049
|
+
if (value.length === 0) return false;
|
|
19050
|
+
for (let index = 0; index < value.length; index++) {
|
|
19051
|
+
if (value.charCodeAt(index) > 127) return false;
|
|
19052
|
+
}
|
|
19053
|
+
return true;
|
|
19054
|
+
}
|
|
19055
|
+
function compareAscii(left, right) {
|
|
19056
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
19057
|
+
}
|
|
18751
19058
|
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
|
|
18752
19059
|
const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
|
|
18753
19060
|
const response = await fetchFn(url, {
|
|
@@ -19524,6 +19831,50 @@ async function classifyRegistryLookup(subject, response, options2 = {}) {
|
|
|
19524
19831
|
}
|
|
19525
19832
|
return { status: "unregistered", subject, source: "offchain", hosts: [] };
|
|
19526
19833
|
}
|
|
19834
|
+
if (options2.recordVersion === 2) {
|
|
19835
|
+
let record3;
|
|
19836
|
+
try {
|
|
19837
|
+
record3 = parseRecordResponseV2(response.body);
|
|
19838
|
+
} catch (error) {
|
|
19839
|
+
return unavailable(
|
|
19840
|
+
"malformed-response",
|
|
19841
|
+
error instanceof Error ? error.message : "location registry response is invalid"
|
|
19842
|
+
);
|
|
19843
|
+
}
|
|
19844
|
+
if (registryOwnerKeyV2(record3.subject) !== registryOwnerKeyV2(subject)) {
|
|
19845
|
+
return unavailable(
|
|
19846
|
+
"subject-mismatch",
|
|
19847
|
+
"location record owner does not match requested owner key"
|
|
19848
|
+
);
|
|
19849
|
+
}
|
|
19850
|
+
let signatureValid = false;
|
|
19851
|
+
try {
|
|
19852
|
+
signatureValid = await verifyLocationRecordV2(record3);
|
|
19853
|
+
} catch {
|
|
19854
|
+
signatureValid = false;
|
|
19855
|
+
}
|
|
19856
|
+
if (!signatureValid) {
|
|
19857
|
+
return unavailable("invalid-signature", "location record signature is invalid");
|
|
19858
|
+
}
|
|
19859
|
+
if (remembered !== null && record3.sequence < remembered) {
|
|
19860
|
+
return unavailable(
|
|
19861
|
+
"rollback",
|
|
19862
|
+
`location record sequence ${record3.sequence} is below remembered sequence ${remembered}`
|
|
19863
|
+
);
|
|
19864
|
+
}
|
|
19865
|
+
return {
|
|
19866
|
+
status: "registered",
|
|
19867
|
+
subject,
|
|
19868
|
+
source: "offchain",
|
|
19869
|
+
sequence: record3.sequence,
|
|
19870
|
+
record: record3,
|
|
19871
|
+
hosts: record3.nodes.map(({ did, url }) => ({
|
|
19872
|
+
nodeDid: did,
|
|
19873
|
+
urls: [url],
|
|
19874
|
+
source: "offchain"
|
|
19875
|
+
}))
|
|
19876
|
+
};
|
|
19877
|
+
}
|
|
19527
19878
|
let record2;
|
|
19528
19879
|
try {
|
|
19529
19880
|
record2 = parseRecordResponse(response.body);
|
|
@@ -19700,6 +20051,26 @@ function parseStrictJson(text3) {
|
|
|
19700
20051
|
if (i !== text3.length) fail2("trailing data");
|
|
19701
20052
|
return value;
|
|
19702
20053
|
}
|
|
20054
|
+
function registryOwnerKeyV2(subject) {
|
|
20055
|
+
return subject.startsWith("did:pkh:") ? subject.slice(subject.lastIndexOf(":") + 1) : subject;
|
|
20056
|
+
}
|
|
20057
|
+
function parseRecordResponseV2(body) {
|
|
20058
|
+
const root = parseStrictJson(body);
|
|
20059
|
+
if (!(root instanceof Map) || root.size !== 1 || !root.has("record")) {
|
|
20060
|
+
throw new SyntaxError("location registry v2 response must contain only a record field");
|
|
20061
|
+
}
|
|
20062
|
+
return validateLocationRecordV2(toNativeJson(root.get("record")));
|
|
20063
|
+
}
|
|
20064
|
+
function toNativeJson(value) {
|
|
20065
|
+
if (value instanceof JsonInteger) return Number(value.lexeme);
|
|
20066
|
+
if (Array.isArray(value)) return value.map(toNativeJson);
|
|
20067
|
+
if (value instanceof Map) {
|
|
20068
|
+
return Object.fromEntries(
|
|
20069
|
+
Array.from(value, ([key, entry]) => [key, toNativeJson(entry)])
|
|
20070
|
+
);
|
|
20071
|
+
}
|
|
20072
|
+
return value;
|
|
20073
|
+
}
|
|
19703
20074
|
function parseRecordResponse(body) {
|
|
19704
20075
|
const root = parseStrictJson(body);
|
|
19705
20076
|
const record2 = root instanceof Map ? root.get("record") : void 0;
|
|
@@ -19820,11 +20191,150 @@ function multiaddrOrigin(address) {
|
|
|
19820
20191
|
}
|
|
19821
20192
|
}
|
|
19822
20193
|
|
|
20194
|
+
// src/registry-cache.ts
|
|
20195
|
+
function createInMemoryRegistryCacheStore() {
|
|
20196
|
+
const entries = /* @__PURE__ */ new Map();
|
|
20197
|
+
let tail = Promise.resolve();
|
|
20198
|
+
return {
|
|
20199
|
+
async get(key) {
|
|
20200
|
+
await tail;
|
|
20201
|
+
const record2 = validateRegistryCacheRecord(entries.get(key), key);
|
|
20202
|
+
return record2 && "invalidated" in record2 ? void 0 : record2;
|
|
20203
|
+
},
|
|
20204
|
+
async getRevision(key) {
|
|
20205
|
+
await tail;
|
|
20206
|
+
return validateRegistryCacheRecord(entries.get(key), key)?.revision ?? 0;
|
|
20207
|
+
},
|
|
20208
|
+
update(key, update) {
|
|
20209
|
+
const next = tail.then(async () => {
|
|
20210
|
+
const record2 = validateRegistryCacheRecord(entries.get(key), key);
|
|
20211
|
+
const revision = record2?.revision ?? 0;
|
|
20212
|
+
const entry = { ...await update(record2 && "invalidated" in record2 ? void 0 : record2, revision), revision };
|
|
20213
|
+
entries.set(key, entry);
|
|
20214
|
+
return entry;
|
|
20215
|
+
});
|
|
20216
|
+
tail = next.catch(() => void 0);
|
|
20217
|
+
return next;
|
|
20218
|
+
},
|
|
20219
|
+
delete(key, expectedRevision, maximumSequence) {
|
|
20220
|
+
const next = tail.then(() => {
|
|
20221
|
+
const record2 = validateRegistryCacheRecord(entries.get(key), key);
|
|
20222
|
+
const revision = record2?.revision ?? 0;
|
|
20223
|
+
if (expectedRevision !== void 0 && (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0 || expectedRevision !== revision)) {
|
|
20224
|
+
throw new Error("Registry cache revision changed before invalidation");
|
|
20225
|
+
}
|
|
20226
|
+
if (maximumSequence !== void 0 && (!Number.isSafeInteger(maximumSequence) || maximumSequence < 0 || record2 !== void 0 && !("invalidated" in record2) && record2.sequence > maximumSequence)) {
|
|
20227
|
+
throw new Error("Registry cache sequence advanced before invalidation");
|
|
20228
|
+
}
|
|
20229
|
+
if (revision === Number.MAX_SAFE_INTEGER) throw new Error("Registry cache revision overflow");
|
|
20230
|
+
entries.set(key, { ownerKey: key, revision: revision + 1, invalidated: true });
|
|
20231
|
+
});
|
|
20232
|
+
tail = next.catch(() => void 0);
|
|
20233
|
+
return next;
|
|
20234
|
+
}
|
|
20235
|
+
};
|
|
20236
|
+
}
|
|
20237
|
+
function validateRegistryCacheRecord(value, key) {
|
|
20238
|
+
if (value === void 0) return void 0;
|
|
20239
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || !("ownerKey" in value) || value.ownerKey !== key) {
|
|
20240
|
+
throw new Error("Invalid registry cache metadata");
|
|
20241
|
+
}
|
|
20242
|
+
const revision = "revision" in value ? value.revision : 0;
|
|
20243
|
+
if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 0) {
|
|
20244
|
+
throw new Error("Invalid registry cache revision");
|
|
20245
|
+
}
|
|
20246
|
+
if ("invalidated" in value) {
|
|
20247
|
+
if (value.invalidated !== true || !("revision" in value) || revision === 0 || Object.keys(value).some((field) => field !== "ownerKey" && field !== "revision" && field !== "invalidated")) {
|
|
20248
|
+
throw new Error("Invalid registry cache tombstone");
|
|
20249
|
+
}
|
|
20250
|
+
return { ownerKey: key, revision, invalidated: true };
|
|
20251
|
+
}
|
|
20252
|
+
if (!("responseBody" in value) || typeof value.responseBody !== "string" || !("fetchedAt" in value) || typeof value.fetchedAt !== "number" || !Number.isSafeInteger(value.fetchedAt) || value.fetchedAt < 0 || !("sequence" in value) || typeof value.sequence !== "number" || !Number.isSafeInteger(value.sequence) || value.sequence < 0) {
|
|
20253
|
+
throw new Error("Invalid registry cache metadata");
|
|
20254
|
+
}
|
|
20255
|
+
return value;
|
|
20256
|
+
}
|
|
20257
|
+
var DEFAULT_REGISTRY_CACHE_STORAGE_KEY = "tinycloud.registryCache.v2";
|
|
20258
|
+
function createLocalStorageRegistryCacheStore(storage, storageKey = DEFAULT_REGISTRY_CACHE_STORAGE_KEY) {
|
|
20259
|
+
const browserGlobal = globalThis;
|
|
20260
|
+
const backing = storage ?? browserGlobal.localStorage;
|
|
20261
|
+
if (!backing) return createInMemoryRegistryCacheStore();
|
|
20262
|
+
const read2 = (key) => {
|
|
20263
|
+
const raw = backing.getItem(`${storageKey}:${key}`);
|
|
20264
|
+
if (raw === null) return void 0;
|
|
20265
|
+
const parsed = JSON.parse(raw);
|
|
20266
|
+
return validateRegistryCacheRecord(parsed, key);
|
|
20267
|
+
};
|
|
20268
|
+
let tail = Promise.resolve();
|
|
20269
|
+
return {
|
|
20270
|
+
async get(key) {
|
|
20271
|
+
await tail;
|
|
20272
|
+
const record2 = read2(key);
|
|
20273
|
+
return record2 && "invalidated" in record2 ? void 0 : record2;
|
|
20274
|
+
},
|
|
20275
|
+
async getRevision(key) {
|
|
20276
|
+
await tail;
|
|
20277
|
+
return read2(key)?.revision ?? 0;
|
|
20278
|
+
},
|
|
20279
|
+
update(key, update) {
|
|
20280
|
+
const commit = async () => {
|
|
20281
|
+
const record2 = read2(key);
|
|
20282
|
+
const revision = record2?.revision ?? 0;
|
|
20283
|
+
const entry = { ...await update(record2 && "invalidated" in record2 ? void 0 : record2, revision), revision };
|
|
20284
|
+
backing.setItem(`${storageKey}:${key}`, JSON.stringify(entry));
|
|
20285
|
+
return entry;
|
|
20286
|
+
};
|
|
20287
|
+
const next = tail.then(() => {
|
|
20288
|
+
const navigator = globalThis.navigator;
|
|
20289
|
+
const locks = navigator?.locks;
|
|
20290
|
+
if (!locks) throw new Error("Persistent registry cache requires Web Locks");
|
|
20291
|
+
return locks.request(`${storageKey}:${key}`, commit);
|
|
20292
|
+
});
|
|
20293
|
+
tail = next.catch(() => void 0);
|
|
20294
|
+
return next;
|
|
20295
|
+
},
|
|
20296
|
+
delete(key, expectedRevision, maximumSequence) {
|
|
20297
|
+
const next = tail.then(async () => {
|
|
20298
|
+
const navigator = globalThis.navigator;
|
|
20299
|
+
if (!navigator?.locks) throw new Error("Persistent registry cache requires Web Locks");
|
|
20300
|
+
await navigator.locks.request(`${storageKey}:${key}`, async () => {
|
|
20301
|
+
const record2 = read2(key);
|
|
20302
|
+
const revision = record2?.revision ?? 0;
|
|
20303
|
+
if (expectedRevision !== void 0 && (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0 || expectedRevision !== revision)) {
|
|
20304
|
+
throw new Error("Registry cache revision changed before invalidation");
|
|
20305
|
+
}
|
|
20306
|
+
if (maximumSequence !== void 0 && (!Number.isSafeInteger(maximumSequence) || maximumSequence < 0 || record2 !== void 0 && !("invalidated" in record2) && record2.sequence > maximumSequence)) {
|
|
20307
|
+
throw new Error("Registry cache sequence advanced before invalidation");
|
|
20308
|
+
}
|
|
20309
|
+
if (revision === Number.MAX_SAFE_INTEGER) throw new Error("Registry cache revision overflow");
|
|
20310
|
+
backing.setItem(`${storageKey}:${key}`, JSON.stringify({ ownerKey: key, revision: revision + 1, invalidated: true }));
|
|
20311
|
+
});
|
|
20312
|
+
});
|
|
20313
|
+
tail = next.catch(() => void 0);
|
|
20314
|
+
return next;
|
|
20315
|
+
}
|
|
20316
|
+
};
|
|
20317
|
+
}
|
|
20318
|
+
async function verifyRegistryCacheEntry(subject, ownerKey, entry, rememberedSequence) {
|
|
20319
|
+
if (!entry || ownerKey !== registryOwnerKeyV2(subject) || entry.ownerKey !== ownerKey || typeof entry.responseBody !== "string" || !Number.isSafeInteger(entry.fetchedAt) || entry.fetchedAt < 0 || !Number.isSafeInteger(entry.sequence) || entry.sequence < 0 || entry.revision !== void 0 && (!Number.isSafeInteger(entry.revision) || entry.revision < 0)) {
|
|
20320
|
+
throw new Error("Invalid registry cache metadata");
|
|
20321
|
+
}
|
|
20322
|
+
const verdict = await classifyRegistryLookup(subject, { kind: "http", status: 200, body: entry.responseBody }, {
|
|
20323
|
+
recordVersion: 2,
|
|
20324
|
+
rememberedSequence
|
|
20325
|
+
});
|
|
20326
|
+
if (verdict.status !== "registered" || verdict.sequence !== entry.sequence || !verdict.record) {
|
|
20327
|
+
throw new Error("Registry cache verification failed");
|
|
20328
|
+
}
|
|
20329
|
+
return verdict;
|
|
20330
|
+
}
|
|
20331
|
+
|
|
19823
20332
|
// src/registry-policy.ts
|
|
19824
20333
|
var DEFAULT_REGISTRY_OWNER_CHAIN_ID = 1;
|
|
19825
20334
|
var DEFAULT_NEW_USER_HOST = DEFAULT_TINYCLOUD_FALLBACK_HOST;
|
|
19826
20335
|
var DEFAULT_REGISTRY_LOOKUP_TIMEOUT_MS = 3e3;
|
|
19827
|
-
var DEFAULT_REGISTRY_SEQUENCE_STORAGE_KEY = "tinycloud.registrySequences.
|
|
20336
|
+
var DEFAULT_REGISTRY_SEQUENCE_STORAGE_KEY = "tinycloud.registrySequences.v2";
|
|
20337
|
+
var REGISTRY_CACHE_MAX_AGE_MS = 36e5;
|
|
19828
20338
|
var MAX_SUBJECT_LENGTH = 74;
|
|
19829
20339
|
function registryOwnerSubjectV1(address, registryChainId = DEFAULT_REGISTRY_OWNER_CHAIN_ID) {
|
|
19830
20340
|
return pkhDid(address, registryChainId);
|
|
@@ -19870,33 +20380,272 @@ function createLocalStorageRegistrySequenceStore(storage, storageKey = DEFAULT_R
|
|
|
19870
20380
|
},
|
|
19871
20381
|
set: async (subject, sequence) => {
|
|
19872
20382
|
await memory.set(subject, sequence);
|
|
19873
|
-
const
|
|
19874
|
-
|
|
19875
|
-
|
|
19876
|
-
|
|
20383
|
+
const navigator = globalThis.navigator;
|
|
20384
|
+
const locks = navigator?.locks;
|
|
20385
|
+
if (!locks) throw new Error("Persistent registry sequence store requires Web Locks");
|
|
20386
|
+
await locks.request(storageKey, async () => {
|
|
20387
|
+
const remembered = await memory.get(subject);
|
|
20388
|
+
const sequences = read2();
|
|
20389
|
+
const previous = sequences.get(subject);
|
|
20390
|
+
const highest = Math.max(previous ?? -1, remembered ?? -1, sequence);
|
|
20391
|
+
await memory.set(subject, highest);
|
|
20392
|
+
if (previous !== void 0 && previous >= highest) return;
|
|
20393
|
+
sequences.set(subject, highest);
|
|
20394
|
+
backing.setItem(storageKey, JSON.stringify(Object.fromEntries(sequences)));
|
|
20395
|
+
});
|
|
19877
20396
|
}
|
|
19878
20397
|
};
|
|
19879
20398
|
}
|
|
19880
20399
|
function isSequence(value) {
|
|
19881
20400
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
19882
20401
|
}
|
|
19883
|
-
|
|
19884
|
-
if (typeof subject !== "string" ||
|
|
19885
|
-
subject.length > MAX_SUBJECT_LENGTH || !isCanonicalSubject(subject)) {
|
|
20402
|
+
function assertRegistrySubject(subject) {
|
|
20403
|
+
if (typeof subject !== "string" || subject.length > MAX_SUBJECT_LENGTH || !isCanonicalSubject(subject)) {
|
|
19886
20404
|
throw new RegistryLookupInputError(
|
|
19887
20405
|
"invalid-subject",
|
|
19888
20406
|
"subject must be did:pkh:eip155:<chainId>:<EIP-55 address> or an Ed25519 did:key"
|
|
19889
20407
|
);
|
|
19890
20408
|
}
|
|
20409
|
+
}
|
|
20410
|
+
async function resolveOwnerHosts(subject, options2 = {}) {
|
|
20411
|
+
assertRegistrySubject(subject);
|
|
20412
|
+
const ownerKey = registryOwnerKeyV2(subject);
|
|
20413
|
+
const store = options2.sequenceStore;
|
|
20414
|
+
const remembered = store === void 0 ? void 0 : await store.get(ownerKey);
|
|
20415
|
+
const cache2 = options2.cacheStore;
|
|
20416
|
+
const authority = cache2 ? cacheAuthority(cache2, ownerKey) : void 0;
|
|
20417
|
+
const highWater = Math.max(remembered ?? -1, authority?.sequence ?? -1);
|
|
20418
|
+
if (cache2) {
|
|
20419
|
+
const cached = await cache2.get(ownerKey);
|
|
20420
|
+
if (cached !== void 0) {
|
|
20421
|
+
const verdict = await verifyRegistryCacheEntry(subject, ownerKey, cached, highWater < 0 ? void 0 : highWater);
|
|
20422
|
+
authority.sequence = Math.max(highWater, verdict.sequence);
|
|
20423
|
+
await rememberAcceptedSequence(store, ownerKey, remembered, verdict);
|
|
20424
|
+
}
|
|
20425
|
+
return refreshCachedOwnerRegistry(subject, options2, cache2, ownerKey, Math.max(highWater, authority.sequence) < 0 ? void 0 : Math.max(highWater, authority.sequence));
|
|
20426
|
+
}
|
|
20427
|
+
return (await fetchOwnerRegistry(subject, options2, remembered)).verdict;
|
|
20428
|
+
}
|
|
20429
|
+
var cacheAuthorities = /* @__PURE__ */ new WeakMap();
|
|
20430
|
+
var verdictRevisions = /* @__PURE__ */ new WeakMap();
|
|
20431
|
+
function cacheAuthority(cache2, key) {
|
|
20432
|
+
let owners = cacheAuthorities.get(cache2);
|
|
20433
|
+
if (!owners) cacheAuthorities.set(cache2, owners = /* @__PURE__ */ new Map());
|
|
20434
|
+
let authority = owners.get(key);
|
|
20435
|
+
if (!authority) owners.set(key, authority = { sequence: -1, invalidated: false, invalidationAttempt: 0 });
|
|
20436
|
+
return authority;
|
|
20437
|
+
}
|
|
20438
|
+
var refreshes = /* @__PURE__ */ new WeakMap();
|
|
20439
|
+
async function refreshOwnerRegistry(subject, options2) {
|
|
20440
|
+
return resolveOwnerHosts(subject, options2);
|
|
20441
|
+
}
|
|
20442
|
+
async function refreshCachedOwnerRegistry(subject, options2, cache2, key, remembered) {
|
|
20443
|
+
let active = refreshes.get(cache2);
|
|
20444
|
+
if (!active) refreshes.set(cache2, active = /* @__PURE__ */ new Map());
|
|
20445
|
+
let next = active.get(key);
|
|
20446
|
+
if (!next) {
|
|
20447
|
+
next = (async () => {
|
|
20448
|
+
const current2 = await cache2.get(key);
|
|
20449
|
+
if (current2 !== void 0) {
|
|
20450
|
+
const verified = await verifyRegistryCacheEntry(subject, key, current2, remembered);
|
|
20451
|
+
await rememberAcceptedSequence(options2.sequenceStore, key, remembered, verified);
|
|
20452
|
+
}
|
|
20453
|
+
const highWater2 = Math.max(remembered ?? -1, current2?.sequence ?? -1);
|
|
20454
|
+
return fetchOwnerRegistry(subject, options2, highWater2 < 0 ? void 0 : highWater2, current2);
|
|
20455
|
+
})();
|
|
20456
|
+
active.set(key, next);
|
|
20457
|
+
void next.finally(() => active.delete(key)).catch(() => void 0);
|
|
20458
|
+
}
|
|
20459
|
+
const { verdict, allowCached, revision, invalidationAttempt } = await next;
|
|
20460
|
+
if (verdict.status === "unavailable" && !allowCached) return verdict;
|
|
20461
|
+
const current = await cache2.get(key);
|
|
20462
|
+
const latest = await options2.sequenceStore?.get(key);
|
|
20463
|
+
const authority = cacheAuthority(cache2, key);
|
|
20464
|
+
const highWater = Math.max(remembered ?? -1, latest ?? -1, authority.sequence);
|
|
20465
|
+
assertRegistryRevision(subject, await cache2.getRevision(key), revision);
|
|
20466
|
+
assertInvalidationAttempt(subject, authority.invalidationAttempt, invalidationAttempt);
|
|
20467
|
+
if (current !== void 0) {
|
|
20468
|
+
const verified = await verifyRegistryCacheEntry(subject, key, current, highWater < 0 ? void 0 : highWater);
|
|
20469
|
+
authority.sequence = Math.max(highWater, verified.sequence);
|
|
20470
|
+
await rememberAcceptedSequence(options2.sequenceStore, key, latest, verified);
|
|
20471
|
+
assertRegistryRevision(subject, await cache2.getRevision(key), revision);
|
|
20472
|
+
assertInvalidationAttempt(subject, authority.invalidationAttempt, invalidationAttempt);
|
|
20473
|
+
if (verdict.status === "unavailable") {
|
|
20474
|
+
const age = Date.now() - current.fetchedAt;
|
|
20475
|
+
if (!allowCached || authority.invalidated || age < 0 || age >= REGISTRY_CACHE_MAX_AGE_MS) return verdict;
|
|
20476
|
+
const fallback = { ...verified, offline: true };
|
|
20477
|
+
verdictRevisions.set(fallback, revision);
|
|
20478
|
+
return fallback;
|
|
20479
|
+
}
|
|
20480
|
+
verdictRevisions.set(verified, revision);
|
|
20481
|
+
return verified;
|
|
20482
|
+
}
|
|
20483
|
+
if (verdict.status === "unavailable") return verdict;
|
|
20484
|
+
if (verdict.status === "registered") {
|
|
20485
|
+
if (verdict.sequence < highWater) return {
|
|
20486
|
+
status: "unavailable",
|
|
20487
|
+
subject,
|
|
20488
|
+
source: "offchain",
|
|
20489
|
+
reason: "rollback",
|
|
20490
|
+
message: `registry refresh sequence ${verdict.sequence} is below remembered sequence ${highWater}`,
|
|
20491
|
+
hosts: []
|
|
20492
|
+
};
|
|
20493
|
+
await rememberAcceptedSequence(options2.sequenceStore, key, latest, verdict);
|
|
20494
|
+
assertRegistryRevision(subject, await cache2.getRevision(key), revision);
|
|
20495
|
+
assertInvalidationAttempt(subject, authority.invalidationAttempt, invalidationAttempt);
|
|
20496
|
+
verdictRevisions.set(verdict, revision);
|
|
20497
|
+
return verdict;
|
|
20498
|
+
}
|
|
20499
|
+
if (highWater >= 0) return {
|
|
20500
|
+
status: "unavailable",
|
|
20501
|
+
subject,
|
|
20502
|
+
source: "offchain",
|
|
20503
|
+
reason: "not-found-after-registered",
|
|
20504
|
+
message: "owner registration was remembered before registry absence could authorize onboarding",
|
|
20505
|
+
hosts: []
|
|
20506
|
+
};
|
|
20507
|
+
return verdict;
|
|
20508
|
+
}
|
|
20509
|
+
function assertRegistryRevision(subject, actual, expected) {
|
|
20510
|
+
if (!isSequence(actual) || actual !== expected) {
|
|
20511
|
+
throw new RegistryUnavailableError(subject, void 0, {
|
|
20512
|
+
message: "owner routing was invalidated while the registry lookup was in flight"
|
|
20513
|
+
});
|
|
20514
|
+
}
|
|
20515
|
+
}
|
|
20516
|
+
function assertInvalidationAttempt(subject, actual, expected) {
|
|
20517
|
+
if (!isSequence(actual) || actual !== expected) {
|
|
20518
|
+
throw new RegistryUnavailableError(subject, void 0, {
|
|
20519
|
+
message: "owner routing invalidation was attempted while the registry lookup was in flight"
|
|
20520
|
+
});
|
|
20521
|
+
}
|
|
20522
|
+
}
|
|
20523
|
+
async function fetchOwnerRegistry(subject, options2, remembered, cached) {
|
|
20524
|
+
const ownerKey = registryOwnerKeyV2(subject);
|
|
19891
20525
|
const store = options2.sequenceStore;
|
|
19892
|
-
const remembered = store === void 0 ? void 0 : await store.get(subject);
|
|
19893
20526
|
const registryUrl = options2.registryUrl ?? DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL;
|
|
19894
|
-
const
|
|
19895
|
-
const
|
|
19896
|
-
|
|
19897
|
-
|
|
19898
|
-
|
|
19899
|
-
);
|
|
20527
|
+
const baseUrl = registryUrl.replace(/\/$/, "");
|
|
20528
|
+
const fetchFn = options2.fetch ?? globalThis.fetch;
|
|
20529
|
+
const timeoutMs = options2.timeoutMs ?? DEFAULT_REGISTRY_LOOKUP_TIMEOUT_MS;
|
|
20530
|
+
const authority = options2.cacheStore ? cacheAuthority(options2.cacheStore, ownerKey) : void 0;
|
|
20531
|
+
const invalidationAttempt = authority?.invalidationAttempt;
|
|
20532
|
+
const revision = options2.cacheStore ? await options2.cacheStore.getRevision(ownerKey) : void 0;
|
|
20533
|
+
if (options2.cacheStore && !isSequence(revision)) throw new RegistryUnavailableError(subject, void 0, {
|
|
20534
|
+
message: "invalid registry cache revision"
|
|
20535
|
+
});
|
|
20536
|
+
if (revision !== void 0 && revision > 0) remembered = Math.max(remembered ?? -1, 0);
|
|
20537
|
+
if (authority) assertInvalidationAttempt(subject, authority.invalidationAttempt, invalidationAttempt);
|
|
20538
|
+
const observation = await observeRegistry(`${baseUrl}/v2/locations/${encodeURIComponent(ownerKey)}`, fetchFn, timeoutMs);
|
|
20539
|
+
const v2 = await classifyObservation(subject, observation, 2, remembered ?? null);
|
|
20540
|
+
if (v2.status === "registered" && options2.cacheStore && observation.kind === "http") {
|
|
20541
|
+
const candidate = { ownerKey, responseBody: observation.body, fetchedAt: Date.now(), sequence: v2.sequence };
|
|
20542
|
+
let accepted;
|
|
20543
|
+
let equalSequenceConflict = false;
|
|
20544
|
+
try {
|
|
20545
|
+
accepted = await options2.cacheStore.update(ownerKey, async (current, currentRevision) => {
|
|
20546
|
+
assertRegistryRevision(subject, currentRevision, revision);
|
|
20547
|
+
assertInvalidationAttempt(subject, authority.invalidationAttempt, invalidationAttempt);
|
|
20548
|
+
const highest = Math.max(await store?.get(ownerKey) ?? -1, authority.sequence);
|
|
20549
|
+
const highWater = highest < 0 ? void 0 : highest;
|
|
20550
|
+
if (current !== void 0) {
|
|
20551
|
+
const previous = await verifyRegistryCacheEntry(subject, ownerKey, current, highWater);
|
|
20552
|
+
assertInvalidationAttempt(subject, authority.invalidationAttempt, invalidationAttempt);
|
|
20553
|
+
if (v2.sequence < previous.sequence) return current;
|
|
20554
|
+
if (v2.sequence === previous.sequence) {
|
|
20555
|
+
if (canonicalLocationPayloadV2(v2.record) !== canonicalLocationPayloadV2(previous.record)) {
|
|
20556
|
+
equalSequenceConflict = true;
|
|
20557
|
+
throw new RegistryUnavailableError(subject, void 0, {
|
|
20558
|
+
message: "equal-sequence registry response changes the accepted signed payload"
|
|
20559
|
+
});
|
|
20560
|
+
}
|
|
20561
|
+
return { ...current, fetchedAt: candidate.fetchedAt };
|
|
20562
|
+
}
|
|
20563
|
+
}
|
|
20564
|
+
await verifyRegistryCacheEntry(subject, ownerKey, candidate, highWater);
|
|
20565
|
+
assertInvalidationAttempt(subject, authority.invalidationAttempt, invalidationAttempt);
|
|
20566
|
+
return candidate;
|
|
20567
|
+
});
|
|
20568
|
+
} catch (error) {
|
|
20569
|
+
if (equalSequenceConflict) await dropOwnerRegistryCache(subject, options2.cacheStore, store, ownerKey, v2.sequence, revision);
|
|
20570
|
+
throw error;
|
|
20571
|
+
}
|
|
20572
|
+
const verdict = await verifyRegistryCacheEntry(subject, ownerKey, accepted);
|
|
20573
|
+
assertInvalidationAttempt(subject, authority.invalidationAttempt, invalidationAttempt);
|
|
20574
|
+
authority.sequence = Math.max(authority.sequence, verdict.sequence);
|
|
20575
|
+
authority.invalidated = false;
|
|
20576
|
+
await rememberAcceptedSequence(store, ownerKey, remembered, verdict);
|
|
20577
|
+
return { verdict, revision, invalidationAttempt };
|
|
20578
|
+
}
|
|
20579
|
+
if (options2.cacheStore && (cached !== void 0 || remembered !== void 0) && observation.kind === "http" && observation.status >= 400 && observation.status < 500) {
|
|
20580
|
+
await dropOwnerRegistryCache(subject, options2.cacheStore, store, ownerKey, Math.max(remembered ?? 0, cached?.sequence ?? 0), revision);
|
|
20581
|
+
}
|
|
20582
|
+
const fresh = cached === void 0 && remembered === void 0;
|
|
20583
|
+
const coldOffline = fresh && v2.status === "unavailable" && registryOutage(observation);
|
|
20584
|
+
if (v2.status !== "unregistered" && !coldOffline) {
|
|
20585
|
+
await rememberAcceptedSequence(store, ownerKey, remembered, v2);
|
|
20586
|
+
const allowCached = v2.status === "unavailable" && (observation.kind === "network-error" || observation.kind === "timeout" || observation.kind === "http" && [502, 503, 504].includes(observation.status));
|
|
20587
|
+
return { verdict: v2, allowCached, revision, invalidationAttempt };
|
|
20588
|
+
}
|
|
20589
|
+
const v1Subject = legacyRegistrySubject(subject, options2.registryChainId);
|
|
20590
|
+
const v1Observation = await observeRegistry(`${baseUrl}/v1/locations/${encodeURIComponent(v1Subject)}`, fetchFn, timeoutMs);
|
|
20591
|
+
const v1 = await classifyObservation(v1Subject, v1Observation, 1, null);
|
|
20592
|
+
if (v1.status === "registered") {
|
|
20593
|
+
await store?.set(ownerKey, Math.max(remembered ?? 0, 0));
|
|
20594
|
+
return { verdict: {
|
|
20595
|
+
status: "unavailable",
|
|
20596
|
+
subject,
|
|
20597
|
+
source: "offchain",
|
|
20598
|
+
reason: "unsupported-format",
|
|
20599
|
+
message: "owner has a v1 location record but no signed-v2 routing record",
|
|
20600
|
+
hosts: []
|
|
20601
|
+
}, revision, invalidationAttempt };
|
|
20602
|
+
}
|
|
20603
|
+
const legacyOffline = fresh && v1.status === "unavailable" && registryOutage(v1Observation);
|
|
20604
|
+
if (v1.status === "unavailable" && !legacyOffline) return { verdict: { ...v1, subject }, revision, invalidationAttempt };
|
|
20605
|
+
return { verdict: { status: "unregistered", subject, source: "offchain", hosts: [], ...coldOffline || legacyOffline ? { offline: true } : {} }, revision, invalidationAttempt };
|
|
20606
|
+
}
|
|
20607
|
+
async function dropOwnerRegistryCache(subject, cache2, store, key, sequence, expectedRevision) {
|
|
20608
|
+
if (!isSequence(expectedRevision)) throw new RegistryUnavailableError(subject, void 0, {
|
|
20609
|
+
message: "registry invalidation has no accepted origin revision"
|
|
20610
|
+
});
|
|
20611
|
+
const authority = cacheAuthority(cache2, key);
|
|
20612
|
+
authority.sequence = Math.max(authority.sequence, sequence);
|
|
20613
|
+
authority.invalidated = true;
|
|
20614
|
+
authority.invalidationAttempt++;
|
|
20615
|
+
try {
|
|
20616
|
+
if (store) await store.set(key, Math.max(authority.sequence, await store.get(key) ?? -1));
|
|
20617
|
+
await cache2.delete(key, expectedRevision, sequence);
|
|
20618
|
+
} catch (cause) {
|
|
20619
|
+
throw new RegistryUnavailableError(subject, void 0, { message: "cached storage settings could not be invalidated", cause });
|
|
20620
|
+
}
|
|
20621
|
+
}
|
|
20622
|
+
var sequenceWriteWarned = false;
|
|
20623
|
+
function registryOutage(observation) {
|
|
20624
|
+
return observation.kind === "network-error" || observation.kind === "timeout" || observation.kind === "http" && observation.status >= 500 && observation.status < 600;
|
|
20625
|
+
}
|
|
20626
|
+
var fallbackWarnings = /* @__PURE__ */ new WeakMap();
|
|
20627
|
+
function warnRegistryDefaultNode(verdict, cacheStore, onRegistryFallback) {
|
|
20628
|
+
if (verdict.status !== "unregistered" || !verdict.offline) return;
|
|
20629
|
+
warnRegistryFallback(verdict.subject, "using_default_node", cacheStore, onRegistryFallback);
|
|
20630
|
+
}
|
|
20631
|
+
function warnRegistryCachedSettings(verdict, options2) {
|
|
20632
|
+
if (verdict.status === "registered" && verdict.offline) {
|
|
20633
|
+
warnRegistryFallback(verdict.subject, "using_cached_settings", options2.cacheStore, options2.onRegistryFallback);
|
|
20634
|
+
}
|
|
20635
|
+
}
|
|
20636
|
+
function warnRegistryFallback(subject, code3, cacheStore, onRegistryFallback) {
|
|
20637
|
+
if (cacheStore) {
|
|
20638
|
+
const key = `${code3}:${registryOwnerKeyV2(subject)}`;
|
|
20639
|
+
let warned = fallbackWarnings.get(cacheStore);
|
|
20640
|
+
if (!warned) fallbackWarnings.set(cacheStore, warned = /* @__PURE__ */ new Set());
|
|
20641
|
+
if (warned.has(key)) return;
|
|
20642
|
+
warned.add(key);
|
|
20643
|
+
}
|
|
20644
|
+
const reason = code3 === "using_default_node" ? "registry unavailable; using the default node" : "Storage settings are unreachable; using cached settings checked less than 1 hour ago.";
|
|
20645
|
+
if (onRegistryFallback) onRegistryFallback(reason, code3);
|
|
20646
|
+
else console.warn(reason);
|
|
20647
|
+
}
|
|
20648
|
+
async function classifyObservation(subject, observed, recordVersion, rememberedSequence) {
|
|
19900
20649
|
if (observed.kind === "oversize" || observed.kind === "invalid-utf8") {
|
|
19901
20650
|
const oversize = observed.kind === "oversize";
|
|
19902
20651
|
return {
|
|
@@ -19908,22 +20657,23 @@ async function resolveOwnerHosts(subject, options2 = {}) {
|
|
|
19908
20657
|
hosts: []
|
|
19909
20658
|
};
|
|
19910
20659
|
}
|
|
19911
|
-
|
|
19912
|
-
|
|
19913
|
-
|
|
19914
|
-
if (store
|
|
19915
|
-
|
|
19916
|
-
|
|
19917
|
-
|
|
19918
|
-
|
|
19919
|
-
|
|
19920
|
-
|
|
19921
|
-
}
|
|
20660
|
+
return classifyRegistryLookup(subject, observed, { rememberedSequence, recordVersion });
|
|
20661
|
+
}
|
|
20662
|
+
async function rememberAcceptedSequence(store, ownerKey, remembered, verdict) {
|
|
20663
|
+
if (store === void 0 || verdict.status !== "registered" || remembered !== void 0 && verdict.sequence <= remembered) return;
|
|
20664
|
+
try {
|
|
20665
|
+
await store.set(ownerKey, verdict.sequence);
|
|
20666
|
+
} catch (error) {
|
|
20667
|
+
if (!sequenceWriteWarned) {
|
|
20668
|
+
sequenceWriteWarned = true;
|
|
20669
|
+
console.warn("[tinycloud:registry] could not remember the registry sequence:", error);
|
|
19922
20670
|
}
|
|
19923
20671
|
}
|
|
19924
|
-
return verdict;
|
|
19925
20672
|
}
|
|
19926
|
-
|
|
20673
|
+
function legacyRegistrySubject(subject, registryChainId) {
|
|
20674
|
+
if (subject.startsWith("did:key:")) return subject;
|
|
20675
|
+
return registryOwnerSubjectV1(subject.slice(subject.lastIndexOf(":") + 1), registryChainId);
|
|
20676
|
+
}
|
|
19927
20677
|
async function observeRegistry(url, fetchFn, timeoutMs) {
|
|
19928
20678
|
const controller = new AbortController();
|
|
19929
20679
|
let cancelTimer = () => void 0;
|
|
@@ -19945,6 +20695,8 @@ async function readRegistry(url, fetchFn, signal) {
|
|
|
19945
20695
|
try {
|
|
19946
20696
|
response = await fetchFn(url, {
|
|
19947
20697
|
redirect: "error",
|
|
20698
|
+
// A browser-cached HTTP 200 must not renew signed-cache freshness.
|
|
20699
|
+
cache: "no-store",
|
|
19948
20700
|
headers: { accept: "application/json" },
|
|
19949
20701
|
signal
|
|
19950
20702
|
});
|
|
@@ -19966,7 +20718,7 @@ async function readRegistry(url, fetchFn, signal) {
|
|
|
19966
20718
|
return {
|
|
19967
20719
|
kind: "http",
|
|
19968
20720
|
status: response.status,
|
|
19969
|
-
body: new TextDecoder("utf-8", { fatal: true }).decode(bytes2)
|
|
20721
|
+
body: new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2)
|
|
19970
20722
|
};
|
|
19971
20723
|
} catch {
|
|
19972
20724
|
return { kind: "invalid-utf8" };
|
|
@@ -20014,8 +20766,10 @@ async function lookupOwnerRegistryVerdict(subject, options2 = {}) {
|
|
|
20014
20766
|
return await resolveOwnerHosts(subject, {
|
|
20015
20767
|
registryUrl: options2.registryUrl,
|
|
20016
20768
|
sequenceStore: options2.sequenceStore,
|
|
20769
|
+
cacheStore: options2.cacheStore,
|
|
20017
20770
|
fetch: options2.fetch,
|
|
20018
|
-
timeoutMs: options2.timeoutMs
|
|
20771
|
+
timeoutMs: options2.timeoutMs,
|
|
20772
|
+
registryChainId: options2.registryChainId
|
|
20019
20773
|
});
|
|
20020
20774
|
} catch (error) {
|
|
20021
20775
|
throw new RegistryUnavailableError(subject, void 0, {
|
|
@@ -20024,35 +20778,81 @@ async function lookupOwnerRegistryVerdict(subject, options2 = {}) {
|
|
|
20024
20778
|
});
|
|
20025
20779
|
}
|
|
20026
20780
|
}
|
|
20781
|
+
async function resolveRegistryResource(subject, resource, access, options2) {
|
|
20782
|
+
const verdict = await lookupOwnerRegistryVerdict(subject, options2);
|
|
20783
|
+
if (verdict.status === "unregistered") return { verdict, hosts: [] };
|
|
20784
|
+
if (verdict.status === "unavailable") throw new RegistryUnavailableError(subject, verdict);
|
|
20785
|
+
const record2 = verdict.record;
|
|
20786
|
+
if (record2 === void 0) {
|
|
20787
|
+
throw new RegistryUnavailableError(subject, void 0, {
|
|
20788
|
+
message: "registered owner has no verified v2 routing record"
|
|
20789
|
+
});
|
|
20790
|
+
}
|
|
20791
|
+
const denyRoute = async () => {
|
|
20792
|
+
if (options2.cacheStore) {
|
|
20793
|
+
await dropOwnerRegistryCache(subject, options2.cacheStore, options2.sequenceStore, registryOwnerKeyV2(subject), verdict.sequence, verdictRevisions.get(verdict));
|
|
20794
|
+
}
|
|
20795
|
+
throw new NodeNotFoundError(subject, verdict);
|
|
20796
|
+
};
|
|
20797
|
+
let targetDids;
|
|
20798
|
+
if (access === "encryption") {
|
|
20799
|
+
if (record2.encryptionNode === null) return denyRoute();
|
|
20800
|
+
targetDids = [record2.encryptionNode];
|
|
20801
|
+
} else {
|
|
20802
|
+
const space = record2.spaces.find(({ space: signedSpace }) => sameSpaceIdentifier(signedSpace, resource));
|
|
20803
|
+
if (space === void 0) return denyRoute();
|
|
20804
|
+
targetDids = space.nodes.filter(({ mode }) => access !== "write" || mode === "read-write").map(({ did }) => did);
|
|
20805
|
+
}
|
|
20806
|
+
const nodeByDid = new Map(record2.nodes.map((node) => [node.did, node.url]));
|
|
20807
|
+
const hosts = targetDids.map((nodeDid) => {
|
|
20808
|
+
const url = nodeByDid.get(nodeDid);
|
|
20809
|
+
if (url === void 0) {
|
|
20810
|
+
throw new RegistryUnavailableError(subject, void 0, {
|
|
20811
|
+
message: "verified membership references a missing node"
|
|
20812
|
+
});
|
|
20813
|
+
}
|
|
20814
|
+
return { nodeDid, urls: [url], source: "offchain" };
|
|
20815
|
+
});
|
|
20816
|
+
if (hosts.length === 0) return denyRoute();
|
|
20817
|
+
warnRegistryCachedSettings(verdict, options2);
|
|
20818
|
+
return { verdict, hosts };
|
|
20819
|
+
}
|
|
20820
|
+
function sameSpaceIdentifier(signedSpace, requestedSpace) {
|
|
20821
|
+
const signed = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[a-fA-F0-9]{40}):([^/?#\s]+)$/.exec(signedSpace);
|
|
20822
|
+
const requested = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[a-fA-F0-9]{40}):([^/?#\s]+)$/.exec(requestedSpace);
|
|
20823
|
+
if (signed === null || requested === null) return signedSpace === requestedSpace;
|
|
20824
|
+
return signed[1] === requested[1] && signed[2].toLowerCase() === requested[2].toLowerCase() && signed[3] === requested[3];
|
|
20825
|
+
}
|
|
20027
20826
|
async function resolveFailClosedTinyCloudHosts(address, options2 = {}) {
|
|
20028
20827
|
const newUserHost = options2.newUserHost === null ? null : exactHttpOrigin(options2.newUserHost ?? DEFAULT_NEW_USER_HOST, "newUserHost");
|
|
20029
20828
|
const subject = registryOwnerSubjectV1(address, options2.registryChainId);
|
|
20030
20829
|
const resolve = async () => {
|
|
20031
|
-
|
|
20032
|
-
|
|
20830
|
+
const verdict = await lookupOwnerRegistryVerdict(subject, options2);
|
|
20831
|
+
if (verdict.status === "unregistered" && (options2.autoDiscoverLocalNode ?? true)) {
|
|
20832
|
+
const local = await discoverLocalTinyCloudNode({
|
|
20833
|
+
localNodeUrl: options2.localNodeUrl,
|
|
20834
|
+
localLinkName: options2.localLinkName,
|
|
20835
|
+
expectedNodeDid: options2.expectedNodeDid,
|
|
20836
|
+
identityStore: options2.localNodeIdentityStore,
|
|
20837
|
+
fetch: options2.fetch
|
|
20838
|
+
});
|
|
20839
|
+
if (local) {
|
|
20840
|
+
warnRegistryDefaultNode(verdict, options2.cacheStore, options2.onRegistryFallback);
|
|
20841
|
+
return { hosts: [local.url], verdict };
|
|
20842
|
+
}
|
|
20843
|
+
}
|
|
20844
|
+
let hosts;
|
|
20033
20845
|
try {
|
|
20034
|
-
|
|
20846
|
+
hosts = hostsForVerdict(verdict, newUserHost);
|
|
20035
20847
|
} catch (error) {
|
|
20036
|
-
if (
|
|
20037
|
-
|
|
20038
|
-
}
|
|
20039
|
-
if (options2.autoDiscoverLocalNode ?? true) {
|
|
20040
|
-
const linkNames = verdict?.status === "registered" ? secureRecordHosts(verdict).flatMap((host) => LOCAL_LINK_ORIGIN.exec(host)?.[1] ?? []) : [];
|
|
20041
|
-
for (const candidate of [
|
|
20042
|
-
{ localNodeUrl: options2.localNodeUrl, localLinkName: options2.localLinkName },
|
|
20043
|
-
...linkNames.map((localLinkName) => ({ localLinkName }))
|
|
20044
|
-
]) {
|
|
20045
|
-
const local = await discoverLocalTinyCloudNode({
|
|
20046
|
-
...candidate,
|
|
20047
|
-
expectedNodeDid: options2.expectedNodeDid,
|
|
20048
|
-
identityStore: options2.localNodeIdentityStore,
|
|
20049
|
-
fetch: options2.fetch
|
|
20050
|
-
});
|
|
20051
|
-
if (local) return { hosts: [local.url], ...verdict === void 0 ? {} : { verdict } };
|
|
20848
|
+
if (error instanceof NodeNotFoundError && error.verdict.status === "registered" && options2.cacheStore) {
|
|
20849
|
+
await dropOwnerRegistryCache(subject, options2.cacheStore, options2.sequenceStore, registryOwnerKeyV2(subject), error.verdict.sequence, verdictRevisions.get(error.verdict));
|
|
20052
20850
|
}
|
|
20851
|
+
throw error;
|
|
20053
20852
|
}
|
|
20054
|
-
|
|
20055
|
-
|
|
20853
|
+
warnRegistryCachedSettings(verdict, options2);
|
|
20854
|
+
warnRegistryDefaultNode(verdict, options2.cacheStore, options2.onRegistryFallback);
|
|
20855
|
+
return { hosts, verdict };
|
|
20056
20856
|
};
|
|
20057
20857
|
for (let attempt = 1; ; attempt++) {
|
|
20058
20858
|
let failure;
|
|
@@ -20070,10 +20870,12 @@ async function resolveFailClosedTinyCloudHosts(address, options2 = {}) {
|
|
|
20070
20870
|
case "retry":
|
|
20071
20871
|
continue;
|
|
20072
20872
|
case "use-host":
|
|
20073
|
-
|
|
20074
|
-
|
|
20075
|
-
|
|
20076
|
-
|
|
20873
|
+
if (!(failure instanceof NodeNotFoundError) || failure.verdict.status !== "unregistered") {
|
|
20874
|
+
throw failure;
|
|
20875
|
+
}
|
|
20876
|
+
const host = exactHttpOrigin(decision.host, "onNodeNotFound host");
|
|
20877
|
+
warnRegistryDefaultNode(failure.verdict, options2.cacheStore, options2.onRegistryFallback);
|
|
20878
|
+
return { hosts: [host], verdict: failure.verdict };
|
|
20077
20879
|
case "abort":
|
|
20078
20880
|
throw failure;
|
|
20079
20881
|
default:
|
|
@@ -20081,7 +20883,6 @@ async function resolveFailClosedTinyCloudHosts(address, options2 = {}) {
|
|
|
20081
20883
|
}
|
|
20082
20884
|
}
|
|
20083
20885
|
}
|
|
20084
|
-
var LOCAL_LINK_ORIGIN = /^https:\/\/([a-z0-9-]+)\.local\.tinycloud\.link$/;
|
|
20085
20886
|
function secureRecordHosts(verdict) {
|
|
20086
20887
|
const hosts = /* @__PURE__ */ new Set();
|
|
20087
20888
|
for (const host of verdict.hosts) {
|
|
@@ -20108,7 +20909,7 @@ function hostsForVerdict(verdict, newUserHost) {
|
|
|
20108
20909
|
throw new RegistryUnavailableError(verdict.subject, verdict);
|
|
20109
20910
|
}
|
|
20110
20911
|
}
|
|
20111
|
-
function assertSpaceCreationAllowed(verdict, host) {
|
|
20912
|
+
function assertSpaceCreationAllowed(verdict, host, spaceId) {
|
|
20112
20913
|
switch (verdict.status) {
|
|
20113
20914
|
case "unregistered":
|
|
20114
20915
|
return;
|
|
@@ -20116,7 +20917,14 @@ function assertSpaceCreationAllowed(verdict, host) {
|
|
|
20116
20917
|
throw new RegistryUnavailableError(verdict.subject, verdict);
|
|
20117
20918
|
case "registered": {
|
|
20118
20919
|
const origin2 = httpOrigin(host);
|
|
20119
|
-
if (origin2 !== void 0 &&
|
|
20920
|
+
if (origin2 !== void 0 && verdict.record !== void 0) {
|
|
20921
|
+
const space = spaceId === void 0 ? void 0 : verdict.record.spaces.find(({ space: signedSpace }) => sameSpaceIdentifier(signedSpace, spaceId));
|
|
20922
|
+
const memberNodes = new Set(
|
|
20923
|
+
space?.nodes.map(({ did }) => did) ?? []
|
|
20924
|
+
);
|
|
20925
|
+
const spaceHosts = verdict.record.nodes.filter(({ did }) => memberNodes.has(did)).map(({ url }) => httpOrigin(url));
|
|
20926
|
+
if (spaceHosts.includes(origin2)) return;
|
|
20927
|
+
} else if (origin2 !== void 0 && secureRecordHosts(verdict).includes(origin2)) {
|
|
20120
20928
|
return;
|
|
20121
20929
|
}
|
|
20122
20930
|
throw new NodeRegistryEntryExistsError(verdict.subject, host, verdict);
|