@tinycloud/node-sdk 3.12.0-beta.2 → 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/README.md +39 -0
- package/dist/{NodeUserAuthorization-B05Wjjpb.d.cts → NodeUserAuthorization-B_TFUlua.d.cts} +18 -52
- package/dist/{NodeUserAuthorization-B05Wjjpb.d.ts → NodeUserAuthorization-B_TFUlua.d.ts} +18 -52
- package/dist/{TinyCloudNode-CEFGtSWn.d.ts → TinyCloudNode-Brcu8QoX.d.cts} +31 -59
- package/dist/{TinyCloudNode-6_IumWu0.d.cts → TinyCloudNode-DBJUnsMN.d.ts} +31 -59
- package/dist/{authority-DXpnuId1.d.ts → authority-BFc5lesl.d.ts} +1 -1
- package/dist/{authority-DODDXxoP.d.cts → authority-DQ6R1HEG.d.cts} +1 -1
- package/dist/core.cjs +500 -283
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +5 -5
- package/dist/core.d.ts +5 -5
- package/dist/core.js +526 -287
- package/dist/core.js.map +1 -1
- package/dist/credentials-core.d.cts +1 -1
- package/dist/credentials-core.d.ts +1 -1
- package/dist/credentials.d.cts +1 -1
- package/dist/credentials.d.ts +1 -1
- package/dist/{delegation-BQbAuKad.d.cts → delegation-CeOWDnBB.d.cts} +7 -0
- package/dist/{delegation-BQbAuKad.d.ts → delegation-CeOWDnBB.d.ts} +7 -0
- package/dist/encryption-keys.d.cts +3 -3
- package/dist/encryption-keys.d.ts +3 -3
- package/dist/index.cjs +517 -300
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +527 -288
- package/dist/index.js.map +1 -1
- package/dist/replication/authority.d.cts +4 -4
- package/dist/replication/authority.d.ts +4 -4
- package/dist/space-hosting.d.cts +1 -1
- package/dist/space-hosting.d.ts +1 -1
- package/package.json +4 -4
package/dist/core.cjs
CHANGED
|
@@ -54,7 +54,10 @@ __export(core_exports, {
|
|
|
54
54
|
KVService: () => import_sdk_core15.KVService,
|
|
55
55
|
ManifestValidationError: () => import_sdk_core14.ManifestValidationError,
|
|
56
56
|
MemorySessionStorage: () => MemorySessionStorage,
|
|
57
|
+
NodeIdentityMismatchError: () => import_sdk_services7.NodeIdentityMismatchError,
|
|
58
|
+
NodeNotRespondingError: () => import_sdk_services7.NodeNotRespondingError,
|
|
57
59
|
NodeUserAuthorization: () => NodeUserAuthorization,
|
|
60
|
+
PartialNodeWriteError: () => import_sdk_services7.PartialNodeWriteError,
|
|
58
61
|
PermissionNotInManifestError: () => import_sdk_core14.PermissionNotInManifestError,
|
|
59
62
|
PrefixedKVService: () => import_sdk_core15.PrefixedKVService,
|
|
60
63
|
ProtocolMismatchError: () => import_sdk_core22.ProtocolMismatchError,
|
|
@@ -138,6 +141,7 @@ __export(core_exports, {
|
|
|
138
141
|
});
|
|
139
142
|
module.exports = __toCommonJS(core_exports);
|
|
140
143
|
var import_sdk_core12 = require("@tinycloud/sdk-core");
|
|
144
|
+
var import_sdk_services7 = require("@tinycloud/sdk-services");
|
|
141
145
|
|
|
142
146
|
// src/replication/module-registry.ts
|
|
143
147
|
var nodeLoaders;
|
|
@@ -609,11 +613,12 @@ var NodeUserAuthorization = class {
|
|
|
609
613
|
this.tinycloudHosts = config.tinycloudHosts;
|
|
610
614
|
this.tinycloudRegistryUrl = config.tinycloudRegistryUrl;
|
|
611
615
|
this.tinycloudFallbackHosts = config.tinycloudFallbackHosts;
|
|
612
|
-
this.registryPolicy = config.registryPolicy;
|
|
613
616
|
this.newUserHost = config.newUserHost;
|
|
614
617
|
this.registryChainId = config.registryChainId;
|
|
615
618
|
this.registrySequenceStore = config.registrySequenceStore ?? (0, import_sdk_core5.createInMemoryRegistrySequenceStore)();
|
|
619
|
+
this.registryCacheStore = config.registryCacheStore ?? (0, import_sdk_core5.createInMemoryRegistryCacheStore)();
|
|
616
620
|
this.onNodeNotFound = config.onNodeNotFound;
|
|
621
|
+
this.onRegistryFallback = config.onRegistryFallback;
|
|
617
622
|
this.autoDiscoverLocalNode = config.autoDiscoverLocalNode;
|
|
618
623
|
this.localNodeUrl = config.localNodeUrl;
|
|
619
624
|
this.localLinkName = config.localLinkName;
|
|
@@ -681,13 +686,8 @@ var NodeUserAuthorization = class {
|
|
|
681
686
|
* undefined for missing SIWEs and the SDK then treats the session as
|
|
682
687
|
* expired-at-epoch-zero.
|
|
683
688
|
*
|
|
684
|
-
* @param hosts -
|
|
685
|
-
*
|
|
686
|
-
* present (and non-empty) they are adopted directly so the restored
|
|
687
|
-
* session resolves to the same node as the original sign-in without
|
|
688
|
-
* re-running registry/fallback resolution. When absent (old session)
|
|
689
|
-
* hosts are resolved lazily on the first host-needing call via
|
|
690
|
-
* {@link ensureTinyCloudHosts}.
|
|
689
|
+
* @param hosts - Persisted host hints for genuine no-entry onboarding.
|
|
690
|
+
* Current signed registry membership always takes precedence.
|
|
691
691
|
*/
|
|
692
692
|
setRestoredTinyCloudSession(session, hosts) {
|
|
693
693
|
const address = (0, import_sdk_core5.canonicalizeAddress)(session.address);
|
|
@@ -749,89 +749,84 @@ var NodeUserAuthorization = class {
|
|
|
749
749
|
signature: session.signature
|
|
750
750
|
};
|
|
751
751
|
}
|
|
752
|
-
/**
|
|
753
|
-
* Ensure `tinycloudHosts` are resolved before a host-needing call.
|
|
754
|
-
*
|
|
755
|
-
* Fresh sign-in resolves hosts up front; a restored session may not have
|
|
756
|
-
* (old persisted sessions predate {@link PersistedSessionData.tinycloudHosts}).
|
|
757
|
-
* This guard makes a restored session resolve the node exactly like a fresh
|
|
758
|
-
* sign-in — persisted hosts are preferred (already set by
|
|
759
|
-
* {@link setRestoredTinyCloudSession}), otherwise the registry/fallback
|
|
760
|
-
* resolution runs lazily here. Idempotent: {@link resolveTinyCloudHostsForSignIn}
|
|
761
|
-
* early-returns when hosts are already set.
|
|
762
|
-
*
|
|
763
|
-
* Throws if hosts are unset and the restored session has no address/chainId
|
|
764
|
-
* to resolve from — a real failure that must surface, not be masked.
|
|
765
|
-
*/
|
|
752
|
+
/** Refresh the primary space's registry destinations, including after restore. */
|
|
766
753
|
async ensureTinyCloudHosts() {
|
|
767
|
-
if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
|
|
768
|
-
return;
|
|
769
|
-
}
|
|
770
754
|
if (this._address === void 0 || this._chainId === void 0) {
|
|
771
|
-
throw new Error(
|
|
772
|
-
|
|
773
|
-
|
|
755
|
+
throw new Error("Cannot resolve TinyCloud hosts: sign in or restore a session first.");
|
|
756
|
+
}
|
|
757
|
+
await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId, this._tinyCloudSession?.spaceId);
|
|
758
|
+
}
|
|
759
|
+
async resolveTinyCloudHostsForSignIn(address, chainId, restoredSpaceId) {
|
|
760
|
+
const spaceId = restoredSpaceId ?? this.resolveSignInCapabilities(address, chainId).spaceId;
|
|
761
|
+
const destinations = await this.resolveResourceDestinations(spaceId, "read");
|
|
762
|
+
for (const destination of destinations) await (0, import_sdk_services2.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
|
|
763
|
+
this.tinycloudHosts = destinations.map(({ url }) => url);
|
|
764
|
+
}
|
|
765
|
+
/** Current full-resource registry routing; saved hosts are only no-entry hints. */
|
|
766
|
+
async resolveResourceDestinations(resource, access, noEntryHosts = this.tinycloudHosts ?? []) {
|
|
767
|
+
const owner = resource.startsWith("urn:tinycloud:encryption:") ? (0, import_sdk_services2.parseNetworkId)(resource.split("/")[0]).ownerDid : (0, import_sdk_core5.parseSpaceUri)(resource)?.owner ?? (() => {
|
|
768
|
+
const key = /^tinycloud:key:(z[^:]+):[^/]+$/.exec(resource);
|
|
769
|
+
return key ? `did:key:${key[1]}` : void 0;
|
|
770
|
+
})();
|
|
771
|
+
if (!owner) throw new Error(`Cannot determine registry owner for ${resource}`);
|
|
772
|
+
const subject = owner.startsWith("did:pkh:") ? (0, import_sdk_core5.registryOwnerSubjectV1)(owner.slice(owner.lastIndexOf(":") + 1), this.registryChainId) : owner;
|
|
773
|
+
for (let attempt = 1; ; attempt++) {
|
|
774
|
+
try {
|
|
775
|
+
const resolved = await (0, import_sdk_core5.resolveRegistryResource)(subject, resource, access, {
|
|
776
|
+
registryUrl: this.tinycloudRegistryUrl,
|
|
777
|
+
registryChainId: this.registryChainId,
|
|
778
|
+
sequenceStore: this.registrySequenceStore,
|
|
779
|
+
cacheStore: this.registryCacheStore,
|
|
780
|
+
onRegistryFallback: this.onRegistryFallback
|
|
781
|
+
});
|
|
782
|
+
this.registryVerdict = resolved.verdict;
|
|
783
|
+
if (resolved.verdict.status === "unregistered") {
|
|
784
|
+
if (noEntryHosts.length === 0 && (this.autoDiscoverLocalNode ?? true)) {
|
|
785
|
+
const local = await (0, import_sdk_core5.discoverLocalTinyCloudNode)({
|
|
786
|
+
localNodeUrl: this.localNodeUrl,
|
|
787
|
+
localLinkName: this.localLinkName,
|
|
788
|
+
expectedNodeDid: this.expectedNodeDid,
|
|
789
|
+
identityStore: this.localNodeIdentityStore
|
|
790
|
+
});
|
|
791
|
+
if (local) {
|
|
792
|
+
(0, import_sdk_core5.warnRegistryDefaultNode)(resolved.verdict, this.registryCacheStore, this.onRegistryFallback);
|
|
793
|
+
return [{ url: local.url }];
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const host = noEntryHosts[0] ?? this.newUserHost ?? (this.tinycloudFallbackHosts === void 0 ? "https://node.tinycloud.xyz" : this.tinycloudFallbackHosts?.[0]);
|
|
797
|
+
if (!host || this.newUserHost === null && noEntryHosts.length === 0) throw new import_sdk_core5.NodeNotFoundError(subject, resolved.verdict);
|
|
798
|
+
const url = (0, import_sdk_core5.exactHttpOrigin)(host, "new-user host");
|
|
799
|
+
(0, import_sdk_core5.warnRegistryDefaultNode)(resolved.verdict, this.registryCacheStore, this.onRegistryFallback);
|
|
800
|
+
return [{ url }];
|
|
801
|
+
}
|
|
802
|
+
const destinations = resolved.hosts.flatMap((host) => host.urls.map((url) => ({ url, nodeDid: host.nodeDid })));
|
|
803
|
+
return destinations;
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (!(error instanceof import_sdk_core5.NodeNotFoundError) && !(error instanceof import_sdk_core5.RegistryUnavailableError)) throw error;
|
|
806
|
+
if (!this.onNodeNotFound) throw error;
|
|
807
|
+
const decision = await this.onNodeNotFound({ error, attempt });
|
|
808
|
+
if (decision.action === "retry") continue;
|
|
809
|
+
if (decision.action === "use-host" && error.verdict?.status === "unregistered") {
|
|
810
|
+
const url = (0, import_sdk_core5.exactHttpOrigin)(decision.host, "new-user host");
|
|
811
|
+
(0, import_sdk_core5.warnRegistryDefaultNode)(error.verdict, this.registryCacheStore, this.onRegistryFallback);
|
|
812
|
+
return [{ url }];
|
|
813
|
+
}
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
774
816
|
}
|
|
775
|
-
await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId);
|
|
776
817
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
if (this.registryPolicy === "fail-closed") {
|
|
782
|
-
const resolved2 = await (0, import_sdk_core5.resolveFailClosedTinyCloudHosts)(address, {
|
|
783
|
-
registryUrl: this.tinycloudRegistryUrl,
|
|
784
|
-
registryChainId: this.registryChainId,
|
|
785
|
-
newUserHost: this.newUserHost,
|
|
786
|
-
sequenceStore: this.registrySequenceStore,
|
|
787
|
-
onNodeNotFound: this.onNodeNotFound,
|
|
788
|
-
autoDiscoverLocalNode: this.autoDiscoverLocalNode,
|
|
789
|
-
localNodeUrl: this.localNodeUrl,
|
|
790
|
-
localLinkName: this.localLinkName,
|
|
791
|
-
expectedNodeDid: this.expectedNodeDid,
|
|
792
|
-
localNodeIdentityStore: this.localNodeIdentityStore
|
|
793
|
-
});
|
|
794
|
-
this.tinycloudHosts = resolved2.hosts;
|
|
795
|
-
this.registryVerdict = resolved2.verdict;
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
const subject = (0, import_sdk_core5.pkhDid)(address, chainId);
|
|
799
|
-
const resolved = await (0, import_sdk_core5.resolveTinyCloudHosts)(subject, {
|
|
800
|
-
registryUrl: this.tinycloudRegistryUrl,
|
|
801
|
-
fallbackHosts: this.tinycloudFallbackHosts,
|
|
802
|
-
autoDiscoverLocalNode: this.autoDiscoverLocalNode,
|
|
803
|
-
localNodeUrl: this.localNodeUrl,
|
|
804
|
-
localLinkName: this.localLinkName,
|
|
805
|
-
expectedNodeDid: this.expectedNodeDid,
|
|
806
|
-
localNodeIdentityStore: this.localNodeIdentityStore
|
|
807
|
-
});
|
|
808
|
-
this.tinycloudHosts = resolved.hosts;
|
|
818
|
+
get registryRecordPresent() {
|
|
819
|
+
return this.registryVerdict?.status === "registered";
|
|
809
820
|
}
|
|
810
|
-
/**
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
*/
|
|
819
|
-
async assertMayCreateSpace() {
|
|
820
|
-
if (this.registryPolicy !== "fail-closed") return;
|
|
821
|
-
if (this._address === void 0) {
|
|
822
|
-
throw new Error("Must be signed in to create a space");
|
|
823
|
-
}
|
|
824
|
-
await this.ensureTinyCloudHosts();
|
|
825
|
-
const subject = (0, import_sdk_core5.registryOwnerSubjectV1)(this._address, this.registryChainId);
|
|
826
|
-
let verdict = this.registryVerdict;
|
|
827
|
-
if (verdict?.subject !== subject || verdict.status === "unavailable") {
|
|
828
|
-
verdict = await (0, import_sdk_core5.lookupOwnerRegistryVerdict)(subject, {
|
|
829
|
-
registryUrl: this.tinycloudRegistryUrl,
|
|
830
|
-
sequenceStore: this.registrySequenceStore
|
|
831
|
-
});
|
|
832
|
-
this.registryVerdict = verdict;
|
|
821
|
+
/** Refresh membership before hosting; a saved no-entry verdict never authorizes a later creation. */
|
|
822
|
+
async assertMayCreateSpace(spaceId = this._tinyCloudSession?.spaceId, host = this.primaryTinyCloudHost) {
|
|
823
|
+
if (!spaceId) throw new Error("Must be signed in to create a space");
|
|
824
|
+
const destinations = await this.resolveResourceDestinations(spaceId, "host");
|
|
825
|
+
if (!destinations.some((destination) => destination.url === host)) {
|
|
826
|
+
const subject = (0, import_sdk_core5.parseSpaceUri)(spaceId)?.owner;
|
|
827
|
+
if (!subject || !this.registryVerdict) throw new Error("Cannot resolve space owner");
|
|
828
|
+
(0, import_sdk_core5.assertSpaceCreationAllowed)(this.registryVerdict, host, spaceId);
|
|
833
829
|
}
|
|
834
|
-
(0, import_sdk_core5.assertSpaceCreationAllowed)(verdict, this.primaryTinyCloudHost);
|
|
835
830
|
}
|
|
836
831
|
requireTinyCloudHosts() {
|
|
837
832
|
if (!this.tinycloudHosts || this.tinycloudHosts.length === 0) {
|
|
@@ -1277,6 +1272,31 @@ var NodeUserAuthorization = class {
|
|
|
1277
1272
|
checkCurrent();
|
|
1278
1273
|
const host = context?.host ?? this.primaryTinyCloudHost;
|
|
1279
1274
|
const spaceId = context?.spaceId ?? (typeof targetSpaceId === "string" ? targetSpaceId : this._tinyCloudSession.spaceId);
|
|
1275
|
+
const destinations = await this.resolveResourceDestinations(spaceId, "host");
|
|
1276
|
+
if (this.registryVerdict?.status === "registered") {
|
|
1277
|
+
const results = [];
|
|
1278
|
+
for (const destination of destinations) {
|
|
1279
|
+
checkCurrent();
|
|
1280
|
+
try {
|
|
1281
|
+
await (0, import_sdk_services2.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
|
|
1282
|
+
const result = await (0, import_sdk_core5.activateSessionWithHost)(destination.url, this._tinyCloudSession.delegationHeader, signal);
|
|
1283
|
+
results.push(result.skipped?.includes(spaceId) ? { ...result, success: false, status: 403, error: "The session does not activate the requested space" } : result);
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
if ((0, import_sdk_services2.routingFailure)(error) || signal?.aborted) throw error;
|
|
1286
|
+
throw new import_sdk_services2.NodeNotRespondingError([destination.url], error);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
const failed = results.flatMap((result, index) => result.success ? [] : [{ url: destinations[index].url, status: result.status }]);
|
|
1290
|
+
if (failed.length) {
|
|
1291
|
+
if (destinations.length === 1) return results[0];
|
|
1292
|
+
throw new import_sdk_services2.PartialNodeWriteError(destinations.filter((_, index) => results[index].success).map(({ url }) => url), failed);
|
|
1293
|
+
}
|
|
1294
|
+
return {
|
|
1295
|
+
...results[0],
|
|
1296
|
+
activated: [...new Set(results.flatMap((result) => result.activated ?? []))],
|
|
1297
|
+
skipped: [...new Set(results.flatMap((result) => result.skipped ?? []))]
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1280
1300
|
const peerId = await (0, import_sdk_core5.fetchPeerId)(host, spaceId, signal);
|
|
1281
1301
|
checkCurrent();
|
|
1282
1302
|
const siwe = this.wasm.generateHostSIWEMessage(
|
|
@@ -1347,13 +1367,45 @@ var NodeUserAuthorization = class {
|
|
|
1347
1367
|
if (!this._tinyCloudSession) {
|
|
1348
1368
|
throw new Error("Must be signed in to ensure space exists");
|
|
1349
1369
|
}
|
|
1350
|
-
await this.ensureTinyCloudHosts();
|
|
1351
|
-
const host = this.primaryTinyCloudHost;
|
|
1352
1370
|
const primarySpaceId = this._tinyCloudSession.spaceId;
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
)
|
|
1371
|
+
const primary = await this.resolveResourceDestinations(primarySpaceId, "read");
|
|
1372
|
+
this.tinycloudHosts = primary.map(({ url }) => url);
|
|
1373
|
+
const targets = new Map(primary.map((destination) => [destination.url, destination]));
|
|
1374
|
+
if (this.registryVerdict?.status === "registered" && this.registryVerdict.record) {
|
|
1375
|
+
const record = this.registryVerdict.record;
|
|
1376
|
+
const resources = Object.keys((0, import_sdk_core5.extractRecapAttenuations)(this._tinyCloudSession.siwe ?? ""));
|
|
1377
|
+
for (const space of record.spaces) {
|
|
1378
|
+
const registeredSpace = (0, import_sdk_core5.parseSpaceUri)(space.space);
|
|
1379
|
+
if (!resources.some((resource) => {
|
|
1380
|
+
const fullSpace = resource.split("/")[0];
|
|
1381
|
+
const requestedSpace = (0, import_sdk_core5.parseSpaceUri)(fullSpace);
|
|
1382
|
+
return fullSpace === space.space || registeredSpace !== null && requestedSpace !== null && registeredSpace.owner === requestedSpace.owner && registeredSpace.name === requestedSpace.name;
|
|
1383
|
+
})) continue;
|
|
1384
|
+
for (const membership of space.nodes) {
|
|
1385
|
+
const node = record.nodes.find((candidate) => candidate.did === membership.did);
|
|
1386
|
+
targets.set(node.url, { url: node.url, nodeDid: node.did });
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (record.encryptionNode && resources.some((resource) => resource.startsWith("urn:tinycloud:encryption:"))) {
|
|
1390
|
+
const node = record.nodes.find((candidate) => candidate.did === record.encryptionNode);
|
|
1391
|
+
targets.set(node.url, { url: node.url, nodeDid: node.did });
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
let result;
|
|
1395
|
+
for (const destination of targets.values()) {
|
|
1396
|
+
await (0, import_sdk_services2.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
|
|
1397
|
+
let activated;
|
|
1398
|
+
try {
|
|
1399
|
+
activated = await (0, import_sdk_core5.activateSessionWithHost)(destination.url, this._tinyCloudSession.delegationHeader);
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
throw new import_sdk_services2.NodeNotRespondingError([destination.url], error);
|
|
1402
|
+
}
|
|
1403
|
+
if (!activated.success && activated.status !== 404) {
|
|
1404
|
+
throw Object.assign(new Error(`Failed to activate session: ${describeHostFailure(activated)}`), { cause: activated });
|
|
1405
|
+
}
|
|
1406
|
+
if (destination.url === primary[0].url) result = activated;
|
|
1407
|
+
}
|
|
1408
|
+
if (!result) throw new Error("Primary space activation did not return a result");
|
|
1357
1409
|
this.recordActivationSkippedSpaces(result, primarySpaceId);
|
|
1358
1410
|
if (!result.success && result.status !== 404) {
|
|
1359
1411
|
throw Object.assign(
|
|
@@ -1401,7 +1453,7 @@ var NodeUserAuthorization = class {
|
|
|
1401
1453
|
/** {@link assertMayCreateSpace}, reporting a refusal to the creation handler before rethrowing it. */
|
|
1402
1454
|
async guardSpaceCreation(handler, context) {
|
|
1403
1455
|
try {
|
|
1404
|
-
await this.assertMayCreateSpace();
|
|
1456
|
+
await this.assertMayCreateSpace(context.spaceId, context.host);
|
|
1405
1457
|
} catch (error) {
|
|
1406
1458
|
const failure = error instanceof Error ? error : new Error(String(error));
|
|
1407
1459
|
handler.onSpaceCreationFailed?.(context, failure);
|
|
@@ -2545,6 +2597,7 @@ function rememberInvokeAny(invokeAny, sink, pending) {
|
|
|
2545
2597
|
|
|
2546
2598
|
// src/delegation.ts
|
|
2547
2599
|
var import_sdk_core6 = require("@tinycloud/sdk-core");
|
|
2600
|
+
var import_sdk_services4 = require("@tinycloud/sdk-services");
|
|
2548
2601
|
var RuntimeDelegationRejectedError = class extends Error {
|
|
2549
2602
|
constructor(reason, message) {
|
|
2550
2603
|
super(message);
|
|
@@ -2571,7 +2624,8 @@ var SERVICE_BY_SHORT_NAME = {
|
|
|
2571
2624
|
kv: "tinycloud.kv",
|
|
2572
2625
|
sql: "tinycloud.sql",
|
|
2573
2626
|
duckdb: "tinycloud.duckdb",
|
|
2574
|
-
hooks: "tinycloud.hooks"
|
|
2627
|
+
hooks: "tinycloud.hooks",
|
|
2628
|
+
capabilities: "tinycloud.capabilities"
|
|
2575
2629
|
};
|
|
2576
2630
|
function authorizationWithoutBearer(authorization) {
|
|
2577
2631
|
return authorization.replace(/^Bearer /i, "");
|
|
@@ -2702,8 +2756,8 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
2702
2756
|
));
|
|
2703
2757
|
continue;
|
|
2704
2758
|
}
|
|
2705
|
-
const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/(
|
|
2706
|
-
if (!match) {
|
|
2759
|
+
const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/([^/]+)(?:\/|$)(.*)$/.exec(resource);
|
|
2760
|
+
if (!match || !Object.prototype.hasOwnProperty.call(SERVICE_BY_SHORT_NAME, match[3])) {
|
|
2707
2761
|
throw new Error(
|
|
2708
2762
|
`Validated runtime delegation has an unsupported signed resource '${resource}'.`
|
|
2709
2763
|
);
|
|
@@ -2848,13 +2902,8 @@ function resourcesMatch(actual, expected) {
|
|
|
2848
2902
|
return JSON.stringify(actual.map(comparable)) === JSON.stringify(expected.map(comparable));
|
|
2849
2903
|
}
|
|
2850
2904
|
async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
reject(
|
|
2854
|
-
"host_mismatch",
|
|
2855
|
-
`Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
|
|
2856
|
-
);
|
|
2857
|
-
}
|
|
2905
|
+
let host = normalizedHost(options.host);
|
|
2906
|
+
const differentHost = delegation.host !== void 0 && normalizedHost(delegation.host) !== host;
|
|
2858
2907
|
if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
|
|
2859
2908
|
reject("authority_invalid", "Runtime delegation has an invalid expiry.");
|
|
2860
2909
|
}
|
|
@@ -2898,6 +2947,13 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2898
2947
|
if (options.authorize !== void 0 && !options.authorize(signed.permissions)) {
|
|
2899
2948
|
throw new Error("Runtime delegation is not authorized for this runtime.");
|
|
2900
2949
|
}
|
|
2950
|
+
if (differentHost) {
|
|
2951
|
+
const registeredHost = await node.resolveRegisteredDelegationHost?.(signed.permissions, host);
|
|
2952
|
+
if (registeredHost === void 0) {
|
|
2953
|
+
reject("host_mismatch", `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`);
|
|
2954
|
+
}
|
|
2955
|
+
host = normalizedHost(registeredHost);
|
|
2956
|
+
}
|
|
2901
2957
|
const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
|
|
2902
2958
|
if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
|
|
2903
2959
|
reject(
|
|
@@ -2941,6 +2997,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2941
2997
|
await node.useRuntimeDelegation(installedCandidate);
|
|
2942
2998
|
}
|
|
2943
2999
|
} catch (error) {
|
|
3000
|
+
if ((0, import_sdk_services4.routingFailure)(error)) throw error;
|
|
2944
3001
|
reject(
|
|
2945
3002
|
activationRejectionReason(error),
|
|
2946
3003
|
error instanceof Error ? error.message : String(error)
|
|
@@ -2959,6 +3016,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2959
3016
|
"Runtime delegation activation installed authority that differs from the validated authority."
|
|
2960
3017
|
);
|
|
2961
3018
|
}
|
|
3019
|
+
node.refreshRegistryForCapabilities?.(signed.permissions);
|
|
2962
3020
|
return {
|
|
2963
3021
|
cid,
|
|
2964
3022
|
delegation: installed,
|
|
@@ -3022,10 +3080,11 @@ function deserializeDelegation(data) {
|
|
|
3022
3080
|
}
|
|
3023
3081
|
|
|
3024
3082
|
// src/TinyCloudNode.ts
|
|
3083
|
+
var import_sdk_services5 = require("@tinycloud/sdk-services");
|
|
3025
3084
|
var import_siwe2 = require("siwe");
|
|
3026
3085
|
var import_share_envelope = require("@tinycloud/share-envelope");
|
|
3027
3086
|
var import_sdk_core11 = require("@tinycloud/sdk-core");
|
|
3028
|
-
var
|
|
3087
|
+
var import_sdk_services6 = require("@tinycloud/sdk-services");
|
|
3029
3088
|
var import_requestLog2 = require("@tinycloud/sdk-services/telemetry/requestLog");
|
|
3030
3089
|
var import_resource2 = require("@tinycloud/sdk-services/encryption/resource");
|
|
3031
3090
|
|
|
@@ -4001,6 +4060,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4001
4060
|
* ```
|
|
4002
4061
|
*/
|
|
4003
4062
|
constructor(config = {}) {
|
|
4063
|
+
this.registryRefreshOwners = /* @__PURE__ */ new Set();
|
|
4004
4064
|
this.signer = null;
|
|
4005
4065
|
this.auth = null;
|
|
4006
4066
|
this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
|
|
@@ -4164,8 +4224,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4164
4224
|
this.config = {
|
|
4165
4225
|
...config,
|
|
4166
4226
|
host: config.host ?? DEFAULT_HOST,
|
|
4227
|
+
registryPolicy: "fail-closed",
|
|
4167
4228
|
// One store for every authorization this client creates.
|
|
4168
|
-
registrySequenceStore: config.registrySequenceStore ?? (0, import_sdk_core11.createInMemoryRegistrySequenceStore)()
|
|
4229
|
+
registrySequenceStore: config.registrySequenceStore ?? (0, import_sdk_core11.createInMemoryRegistrySequenceStore)(),
|
|
4230
|
+
registryCacheStore: config.registryCacheStore ?? (0, import_sdk_core11.createInMemoryRegistryCacheStore)()
|
|
4169
4231
|
};
|
|
4170
4232
|
if (this.config.replication?.enabled) {
|
|
4171
4233
|
const runtime = getNodeReplicationLoaders().runtime().then(({ ReplicationRuntime }) => {
|
|
@@ -4469,7 +4531,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4469
4531
|
// A session saved but never registered on this node is refused: it registers once.
|
|
4470
4532
|
activate: (signal) => {
|
|
4471
4533
|
assertCurrent();
|
|
4472
|
-
|
|
4534
|
+
const activationSignal = AbortSignal.any([signal, graph.signal]);
|
|
4535
|
+
if (primary && primary.delegationCid === session.delegationCid) {
|
|
4536
|
+
return this.activateSignedSession(primary, primary.siwe, host, activationSignal);
|
|
4537
|
+
}
|
|
4538
|
+
return this.activateResourceSession(session.delegationHeader, [spaceId], host, activationSignal);
|
|
4473
4539
|
}
|
|
4474
4540
|
});
|
|
4475
4541
|
assertCurrent();
|
|
@@ -4605,14 +4671,38 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4605
4671
|
get requests() {
|
|
4606
4672
|
return this._requestLog === void 0 ? void 0 : (0, import_requestLog2.readOnlyRequestLog)(this._requestLog);
|
|
4607
4673
|
}
|
|
4608
|
-
|
|
4674
|
+
throwRegistryRefusal(status, text) {
|
|
4675
|
+
if (status !== 403 && status !== 503 || !text.startsWith("registry_unavailable:") && !text.startsWith("registry_hosting_denied:")) return;
|
|
4676
|
+
const code = status === 503 ? "registry_unavailable" : "node_registry_entry_exists";
|
|
4677
|
+
throw Object.assign(new Error(status === 503 ? "The location registry is unavailable for this resource." : "The registry does not authorize this node for the requested resource."), { code, meta: { status } });
|
|
4678
|
+
}
|
|
4679
|
+
createServiceGraphLifetime(session) {
|
|
4680
|
+
const routedFetch = (0, import_sdk_services5.createResourceRoutingFetch)(
|
|
4681
|
+
async (url, init) => {
|
|
4682
|
+
try {
|
|
4683
|
+
const response = await globalThis.fetch(url, init);
|
|
4684
|
+
if (response.status === 403 || response.status === 503) {
|
|
4685
|
+
this.throwRegistryRefusal(response.status, await response.clone().text());
|
|
4686
|
+
}
|
|
4687
|
+
return response;
|
|
4688
|
+
} catch (error) {
|
|
4689
|
+
if ((0, import_sdk_services5.routingFailure)(error)) throw error;
|
|
4690
|
+
if (new URL(url).pathname.startsWith("/encryption/") && !init?.signal?.aborted) {
|
|
4691
|
+
throw new import_sdk_services5.NodeNotRespondingError([new URL(url).origin], error);
|
|
4692
|
+
}
|
|
4693
|
+
throw error;
|
|
4694
|
+
}
|
|
4695
|
+
},
|
|
4696
|
+
{
|
|
4697
|
+
resolveResource: (resource, access) => this.routingDestinations(resource, access),
|
|
4698
|
+
bindAudience: (authorization, nodeDid) => this.bindRoutedAudience(authorization, nodeDid, session),
|
|
4699
|
+
verifyDestination: (target) => (0, import_sdk_services5.verifyResourceDestination)(target, globalThis.fetch.bind(globalThis))
|
|
4700
|
+
}
|
|
4701
|
+
);
|
|
4609
4702
|
return new ServiceGraphLifetime(
|
|
4610
4703
|
this.invokeWithRuntimePermissions,
|
|
4611
4704
|
this.invokeAnyWithRuntimePermissions,
|
|
4612
|
-
|
|
4613
|
-
// signal while preserving the SDK's existing injectable global fetch
|
|
4614
|
-
// behavior (including adapters that install it after construction).
|
|
4615
|
-
(url, init) => globalThis.fetch(url, init),
|
|
4705
|
+
routedFetch,
|
|
4616
4706
|
(context) => {
|
|
4617
4707
|
this.storageFull.observe(context);
|
|
4618
4708
|
if (this._requestLog) (0, import_requestLog2.observeReplicaReads)(context, this._requestLog);
|
|
@@ -4620,6 +4710,75 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4620
4710
|
this._requestLog
|
|
4621
4711
|
);
|
|
4622
4712
|
}
|
|
4713
|
+
async routingDestinations(resource, access, noEntryHosts) {
|
|
4714
|
+
const session = this.currentTinyCloudSession();
|
|
4715
|
+
if (session) this.refreshRegistryOwner((0, import_sdk_core11.pkhDid)(session.address, session.chainId));
|
|
4716
|
+
if (this.auth) return this.auth.resolveResourceDestinations(resource, access, noEntryHosts);
|
|
4717
|
+
const owner = resource.startsWith("urn:tinycloud:encryption:") ? (0, import_sdk_core11.parseNetworkId)(resource.split("/")[0]).ownerDid : (0, import_sdk_core11.parseSpaceUri)(resource)?.owner ?? (() => {
|
|
4718
|
+
const key = /^tinycloud:key:(z[^:]+):[^/]+$/.exec(resource);
|
|
4719
|
+
return key ? `did:key:${key[1]}` : void 0;
|
|
4720
|
+
})();
|
|
4721
|
+
if (!owner) throw new Error(`Cannot determine registry owner for ${resource}`);
|
|
4722
|
+
const subject = owner.startsWith("did:pkh:") ? (0, import_sdk_core11.pkhDid)(owner.slice(owner.lastIndexOf(":") + 1), this.config.registryChainId ?? 1) : owner;
|
|
4723
|
+
const resolved = await (0, import_sdk_core11.resolveRegistryResource)(subject, resource, access, {
|
|
4724
|
+
registryUrl: this.config.tinycloudRegistryUrl,
|
|
4725
|
+
registryChainId: this.config.registryChainId,
|
|
4726
|
+
sequenceStore: this.config.registrySequenceStore,
|
|
4727
|
+
cacheStore: this.config.registryCacheStore,
|
|
4728
|
+
onRegistryFallback: this.config.onRegistryFallback
|
|
4729
|
+
});
|
|
4730
|
+
if (resolved.verdict.status === "unregistered") {
|
|
4731
|
+
const hint = noEntryHosts === void 0 ? this.explicitHost ?? this.config.host : noEntryHosts[0];
|
|
4732
|
+
const host = hint ?? this.config.newUserHost ?? (this.config.tinycloudFallbackHosts === void 0 ? DEFAULT_HOST : this.config.tinycloudFallbackHosts?.[0]);
|
|
4733
|
+
if (!host || !hint && this.config.newUserHost === null) throw new import_sdk_core11.NodeNotFoundError(subject, resolved.verdict);
|
|
4734
|
+
const url = (0, import_sdk_core11.exactHttpOrigin)(host, "new-user host");
|
|
4735
|
+
(0, import_sdk_core11.warnRegistryDefaultNode)(resolved.verdict, this.config.registryCacheStore, this.config.onRegistryFallback);
|
|
4736
|
+
return [{ url }];
|
|
4737
|
+
}
|
|
4738
|
+
const destinations = resolved.hosts.flatMap((host) => host.urls.map((url) => ({ url, nodeDid: host.nodeDid })));
|
|
4739
|
+
return destinations;
|
|
4740
|
+
}
|
|
4741
|
+
/** Transport hints cannot override verified full-resource registry routing. */
|
|
4742
|
+
async resolveRegisteredDelegationHost(permissions, noEntryHost) {
|
|
4743
|
+
const resources = /* @__PURE__ */ new Map();
|
|
4744
|
+
let hasData = false;
|
|
4745
|
+
for (const permission of permissions) {
|
|
4746
|
+
const encryption = this.isEncryptionNetworkOperation(this.invocationServiceName(permission.service), permission.path, permission.space);
|
|
4747
|
+
const resource = encryption ? permission.path.split("/")[0] : permission.space;
|
|
4748
|
+
if (!resource) throw new import_sdk_services5.ResourceNotRoutedError("delegation");
|
|
4749
|
+
resources.set(resource, encryption ? "encryption" : "host");
|
|
4750
|
+
hasData || (hasData = !encryption);
|
|
4751
|
+
}
|
|
4752
|
+
let dataHost;
|
|
4753
|
+
let encryptionHost;
|
|
4754
|
+
let unserved;
|
|
4755
|
+
for (const [resource, access] of resources) {
|
|
4756
|
+
try {
|
|
4757
|
+
const destinations = await this.routingDestinations(resource, access, [noEntryHost]);
|
|
4758
|
+
if (destinations.some(({ nodeDid }) => nodeDid === void 0)) return void 0;
|
|
4759
|
+
if (access === "host") dataHost ?? (dataHost = destinations[0]?.url);
|
|
4760
|
+
else encryptionHost ?? (encryptionHost = destinations[0]?.url);
|
|
4761
|
+
} catch (error) {
|
|
4762
|
+
if (!(error instanceof import_sdk_core11.NodeNotFoundError) || error.verdict.status !== "registered") throw error;
|
|
4763
|
+
unserved ?? (unserved = error);
|
|
4764
|
+
}
|
|
4765
|
+
}
|
|
4766
|
+
const host = hasData ? dataHost : encryptionHost;
|
|
4767
|
+
if (!host) throw unserved ?? new import_sdk_services5.ResourceNotRoutedError("delegation");
|
|
4768
|
+
return host;
|
|
4769
|
+
}
|
|
4770
|
+
async bindRoutedAudience(authorization, nodeDid, stagedSession) {
|
|
4771
|
+
const payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(authorization.split(".")[1])));
|
|
4772
|
+
const issuer = typeof payload === "object" && payload !== null && "iss" in payload ? payload.iss : void 0;
|
|
4773
|
+
const session = [
|
|
4774
|
+
stagedSession,
|
|
4775
|
+
this._serviceContext?.session,
|
|
4776
|
+
this.auth?.tinyCloudSession,
|
|
4777
|
+
...this.runtimePermissionGrants.map((grant) => grant.session)
|
|
4778
|
+
].find((candidate) => candidate?.verificationMethod === issuer);
|
|
4779
|
+
if (!session) throw new Error("Invocation signing session is not active");
|
|
4780
|
+
return rewriteInvocationAudience(authorization, nodeDid, session.jwk);
|
|
4781
|
+
}
|
|
4623
4782
|
syncResolvedHostFromAuth() {
|
|
4624
4783
|
const host = this.auth?.hosts[0];
|
|
4625
4784
|
if (host) {
|
|
@@ -4733,6 +4892,29 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4733
4892
|
const session = this.currentTinyCloudSession();
|
|
4734
4893
|
return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
|
|
4735
4894
|
}
|
|
4895
|
+
/** Start one background refresh for each owner derived from verified authority. */
|
|
4896
|
+
refreshRegistryForCapabilities(permissions) {
|
|
4897
|
+
for (const permission of permissions) {
|
|
4898
|
+
try {
|
|
4899
|
+
const owner = permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:") ? (0, import_sdk_core11.parseNetworkId)(permission.path.split("/")[0]).ownerDid : permission.space ? this.ownerDidFromSpaceId(permission.space) : void 0;
|
|
4900
|
+
if (owner) this.refreshRegistryOwner(owner);
|
|
4901
|
+
} catch {
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
refreshRegistryOwner(owner) {
|
|
4906
|
+
if (this.config.tinycloudRegistryUrl === null) return;
|
|
4907
|
+
const subject = owner.startsWith("did:pkh:") ? (0, import_sdk_core11.pkhDid)(owner.slice(owner.lastIndexOf(":") + 1), this.config.registryChainId ?? 1) : owner;
|
|
4908
|
+
const key = (0, import_sdk_core11.registryOwnerKeyV2)(subject);
|
|
4909
|
+
if (this.registryRefreshOwners.has(key)) return;
|
|
4910
|
+
this.registryRefreshOwners.add(key);
|
|
4911
|
+
void (0, import_sdk_core11.refreshOwnerRegistry)(subject, {
|
|
4912
|
+
registryUrl: this.config.tinycloudRegistryUrl,
|
|
4913
|
+
registryChainId: this.config.registryChainId,
|
|
4914
|
+
sequenceStore: this.config.registrySequenceStore,
|
|
4915
|
+
cacheStore: this.config.registryCacheStore
|
|
4916
|
+
}).catch(() => void 0);
|
|
4917
|
+
}
|
|
4736
4918
|
/** Normalize signed capabilities without collapsing nested encryption into raw authority. */
|
|
4737
4919
|
projectSignedRecapCapabilities(siwe) {
|
|
4738
4920
|
const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
|
|
@@ -5164,6 +5346,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5164
5346
|
const primarySession = this.currentTinyCloudSession();
|
|
5165
5347
|
if (primarySession) {
|
|
5166
5348
|
this.registerPrimarySessionGrant(primarySession);
|
|
5349
|
+
this.refreshRegistryOwner((0, import_sdk_core11.pkhDid)(primarySession.address, primarySession.chainId));
|
|
5350
|
+
this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
|
|
5167
5351
|
if (this.auth?.spaceHosting === "unknown") {
|
|
5168
5352
|
await this.checkSpaceHosting();
|
|
5169
5353
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
@@ -5386,8 +5570,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5386
5570
|
const primarySession = auth.tinyCloudSession;
|
|
5387
5571
|
const defaultSpaceId = this.ownedSpaceId("default");
|
|
5388
5572
|
const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === import_sdk_core11.BOOTSTRAP_SESSION_REQUESTS.default;
|
|
5389
|
-
|
|
5390
|
-
await this.assertMayCreateSpace();
|
|
5573
|
+
for (const step of steps) {
|
|
5574
|
+
if (step.kind === "host") await this.assertMayCreateSpace(step.spaceId);
|
|
5391
5575
|
}
|
|
5392
5576
|
for (const step of steps) {
|
|
5393
5577
|
if (step.kind !== "session") continue;
|
|
@@ -5436,10 +5620,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5436
5620
|
if (!session) {
|
|
5437
5621
|
throw new Error(`Missing bootstrap session for ${step.space}`);
|
|
5438
5622
|
}
|
|
5439
|
-
const
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5623
|
+
const destinations = await this.routingDestinations(step.spaceId, "host");
|
|
5624
|
+
let activated;
|
|
5625
|
+
for (const destination of destinations) {
|
|
5626
|
+
try {
|
|
5627
|
+
await (0, import_sdk_services5.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
|
|
5628
|
+
const result = await (0, import_sdk_core11.activateSessionWithHost)(destination.url, session.delegationHeader, graph.signal);
|
|
5629
|
+
if (!result.success) throw Object.assign(new Error("Bootstrap activation failed"), { cause: result });
|
|
5630
|
+
activated = result;
|
|
5631
|
+
} catch (error) {
|
|
5632
|
+
graph.assertActive();
|
|
5633
|
+
throw error;
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
if (!activated) throw new Error("Bootstrap space has no destination");
|
|
5443
5637
|
graph.assertActive();
|
|
5444
5638
|
if (!activated.success || activated.skipped?.includes(step.spaceId)) {
|
|
5445
5639
|
throw Object.assign(
|
|
@@ -5769,7 +5963,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5769
5963
|
if (!this.currentSessionCoversAccountRegistry()) {
|
|
5770
5964
|
if (!_TinyCloudNode.accountRegistrySkipLogged) {
|
|
5771
5965
|
_TinyCloudNode.accountRegistrySkipLogged = true;
|
|
5772
|
-
|
|
5966
|
+
import_sdk_services6.tinyCloudDebugLogger.log("registry.skip");
|
|
5773
5967
|
}
|
|
5774
5968
|
return;
|
|
5775
5969
|
}
|
|
@@ -6022,6 +6216,13 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6022
6216
|
if (!session) {
|
|
6023
6217
|
throw new Error("Owned space hosting requires an active session");
|
|
6024
6218
|
}
|
|
6219
|
+
await this.assertMayCreateSpace(spaceId);
|
|
6220
|
+
if (this.auth.registryRecordPresent) {
|
|
6221
|
+
const hosted = await this.hostOwnedSpaceOnce(spaceId, signal);
|
|
6222
|
+
if (!hosted.success) throw Object.assign(new Error(`Failed to activate listed space ${spaceId}: ${describeHostFailure2(hosted)}`), { cause: hosted });
|
|
6223
|
+
if (this.auth.tinyCloudSession === session) this.confirmedHostedSpaceIds.add(spaceId);
|
|
6224
|
+
return;
|
|
6225
|
+
}
|
|
6025
6226
|
if (this.confirmedHostedSpaceIds.has(spaceId) || this.hostedOwnedSpaceIds.has(spaceId)) {
|
|
6026
6227
|
return;
|
|
6027
6228
|
}
|
|
@@ -6040,7 +6241,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6040
6241
|
{ cause: activation }
|
|
6041
6242
|
);
|
|
6042
6243
|
}
|
|
6043
|
-
await this.assertMayCreateSpace();
|
|
6244
|
+
await this.assertMayCreateSpace(spaceId);
|
|
6044
6245
|
if (signal?.aborted) throw signal.reason;
|
|
6045
6246
|
const created = await this.hostOwnedSpaceOnce(spaceId, signal);
|
|
6046
6247
|
if (!created.success) {
|
|
@@ -6101,17 +6302,19 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6101
6302
|
{ cause: hosted }
|
|
6102
6303
|
);
|
|
6103
6304
|
}
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
if (!activation.success || activation.skipped?.includes(spaceId)) {
|
|
6109
|
-
throw Object.assign(
|
|
6110
|
-
new Error(
|
|
6111
|
-
`Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
|
|
6112
|
-
),
|
|
6113
|
-
{ cause: activation }
|
|
6305
|
+
if (!this.auth.registryRecordPresent) {
|
|
6306
|
+
const activation = await (0, import_sdk_core11.activateSessionWithHost)(
|
|
6307
|
+
host,
|
|
6308
|
+
session.delegationHeader
|
|
6114
6309
|
);
|
|
6310
|
+
if (!activation.success || activation.skipped?.includes(spaceId)) {
|
|
6311
|
+
throw Object.assign(
|
|
6312
|
+
new Error(
|
|
6313
|
+
`Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
|
|
6314
|
+
),
|
|
6315
|
+
{ cause: activation }
|
|
6316
|
+
);
|
|
6317
|
+
}
|
|
6115
6318
|
}
|
|
6116
6319
|
if (this.auth?.tinyCloudSession === session) this.hostedOwnedSpaceIds.add(spaceId);
|
|
6117
6320
|
try {
|
|
@@ -6155,11 +6358,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6155
6358
|
* session's recap doesn't cover it and the registry has no record; the host
|
|
6156
6359
|
* SIWE is idempotent server-side. Must be called after {@link signIn}.
|
|
6157
6360
|
*
|
|
6158
|
-
*
|
|
6159
|
-
*
|
|
6160
|
-
*
|
|
6161
|
-
* verdict. {@link hostOwnedSpace} itself is an explicit action and is not
|
|
6162
|
-
* guarded.
|
|
6361
|
+
* Current signed per-space membership is checked before both automatic and
|
|
6362
|
+
* explicit hosting. Registered replicas use the session grant; no-entry
|
|
6363
|
+
* onboarding retains the host-SIWE flow.
|
|
6163
6364
|
*
|
|
6164
6365
|
* @param name - The owned space name (e.g. `"secrets"`).
|
|
6165
6366
|
* @returns The hosted owned-space URI.
|
|
@@ -6169,27 +6370,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6169
6370
|
throw new Error("Not signed in. Call signIn() first.");
|
|
6170
6371
|
}
|
|
6171
6372
|
const spaceId = this.ownedSpaceId(name);
|
|
6373
|
+
await this.assertMayCreateSpace(spaceId);
|
|
6172
6374
|
if (this.hostedOwnedSpaceIds.has(spaceId) || await this.isOwnedSpaceRegistered(spaceId)) {
|
|
6173
6375
|
return spaceId;
|
|
6174
6376
|
}
|
|
6175
|
-
await this.assertMayCreateSpace();
|
|
6176
6377
|
return this.hostOwnedSpace(name);
|
|
6177
6378
|
}
|
|
6178
|
-
/**
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
* {@link ensurePublicSpace}. Only `registryPolicy: "fail-closed"` consults
|
|
6182
|
-
* the registry ({@link NodeUserAuthorization.assertMayCreateSpace}); the
|
|
6183
|
-
* default policy performs no lookup.
|
|
6184
|
-
*/
|
|
6185
|
-
async assertMayCreateSpace() {
|
|
6186
|
-
if (this.config.registryPolicy !== "fail-closed") return;
|
|
6187
|
-
await this.auth.assertMayCreateSpace();
|
|
6379
|
+
/** Refresh full-space membership before any automatic or explicit hosting. */
|
|
6380
|
+
async assertMayCreateSpace(spaceId) {
|
|
6381
|
+
await this.routingDestinations(spaceId, "host");
|
|
6188
6382
|
}
|
|
6189
6383
|
/** Registry-policy settings shared by every authorization this client creates. */
|
|
6190
6384
|
registryPolicyConfig() {
|
|
6191
|
-
const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound } = this.config;
|
|
6192
|
-
return { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound };
|
|
6385
|
+
const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, registryCacheStore, onNodeNotFound, onRegistryFallback } = this.config;
|
|
6386
|
+
return { registryPolicy, newUserHost, registryChainId, registrySequenceStore, registryCacheStore, onNodeNotFound, onRegistryFallback };
|
|
6193
6387
|
}
|
|
6194
6388
|
/**
|
|
6195
6389
|
* Submit the host delegation for an owned space, sharing one in-flight
|
|
@@ -6395,12 +6589,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6395
6589
|
}
|
|
6396
6590
|
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
6397
6591
|
}
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
sessionData.chainId
|
|
6402
|
-
);
|
|
6403
|
-
const stagedHost = resolvedHost ?? this.config.host;
|
|
6592
|
+
const restoreHints = this.explicitHost ? [this.explicitHost] : sessionData.tinycloudHosts ?? [];
|
|
6593
|
+
const resolvedHost = this.config.replication?.enabled ? (await this.routingDestinations(sessionData.spaceId, "read", restoreHints))[0]?.url : void 0;
|
|
6594
|
+
const stagedHost = resolvedHost ?? (0, import_sdk_core11.exactHttpOrigin)(restoreHints[0] ?? this.config.host, "restored host hint");
|
|
6404
6595
|
let restoredNodeFeatures;
|
|
6405
6596
|
let replicaProtocolMismatch = false;
|
|
6406
6597
|
if (this.config.replication?.enabled) {
|
|
@@ -6517,7 +6708,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6517
6708
|
this.sessionManager = stagedManager;
|
|
6518
6709
|
this.sessionKeyId = stagedKeyId;
|
|
6519
6710
|
this.sessionKeyJwk = stagedJwk;
|
|
6520
|
-
|
|
6711
|
+
this.config.host = stagedHost;
|
|
6521
6712
|
if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
|
|
6522
6713
|
this.storageFull.clear();
|
|
6523
6714
|
this._requestLog?.clear();
|
|
@@ -6584,6 +6775,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6584
6775
|
if (attempt === this.latestSessionAttempt) await this.endSession();
|
|
6585
6776
|
this.assertSessionEpoch(epoch, "Session restore");
|
|
6586
6777
|
}
|
|
6778
|
+
if (this.config.replication?.enabled) {
|
|
6779
|
+
if (stagedAddress !== void 0) this.refreshRegistryOwner((0, import_sdk_core11.pkhDid)(stagedAddress, stagedChainId));
|
|
6780
|
+
this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
|
|
6781
|
+
}
|
|
6587
6782
|
}
|
|
6588
6783
|
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
6589
6784
|
if (!session?.siwe) return { cache: void 0, grants: [] };
|
|
@@ -6632,7 +6827,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6632
6827
|
};
|
|
6633
6828
|
}
|
|
6634
6829
|
stageRestoredServiceGraph(input) {
|
|
6635
|
-
const graph = this.createServiceGraphLifetime();
|
|
6830
|
+
const graph = this.createServiceGraphLifetime(input.serviceSession);
|
|
6636
6831
|
const core = this.auth ? new import_sdk_core11.TinyCloud(this.auth, {
|
|
6637
6832
|
invokeAny: graph.invokeAny,
|
|
6638
6833
|
telemetry: this.config.telemetry,
|
|
@@ -6857,56 +7052,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6857
7052
|
spaceService.updateConfig({ sharingService });
|
|
6858
7053
|
return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
|
|
6859
7054
|
}
|
|
6860
|
-
/**
|
|
6861
|
-
* Resolve the host a restored session should target.
|
|
6862
|
-
*
|
|
6863
|
-
* Mirrors fresh sign-in host resolution but for the restore path:
|
|
6864
|
-
* an explicit/pinned host always wins, then the hosts the session was
|
|
6865
|
-
* persisted with, then a lazy registry/fallback resolution for sessions
|
|
6866
|
-
* that predate the persisted `tinycloudHosts` field. Returns `undefined`
|
|
6867
|
-
* only when there's nothing to resolve from (no explicit host, no
|
|
6868
|
-
* persisted hosts, and no address/chainId) — in which case the existing
|
|
6869
|
-
* `config.host` (default) is left in place.
|
|
6870
|
-
*
|
|
6871
|
-
* Resolution failures are surfaced, not swallowed: a genuinely broken
|
|
6872
|
-
* registry lookup throws rather than silently falling back to a wrong host.
|
|
6873
|
-
*/
|
|
6874
|
-
async resolveRestoredHost(persistedHosts, address, chainId) {
|
|
6875
|
-
if (this.explicitHost) {
|
|
6876
|
-
return this.explicitHost;
|
|
6877
|
-
}
|
|
6878
|
-
if (persistedHosts && persistedHosts.length > 0) {
|
|
6879
|
-
return persistedHosts[0];
|
|
6880
|
-
}
|
|
6881
|
-
if (address === void 0 || chainId === void 0) {
|
|
6882
|
-
return void 0;
|
|
6883
|
-
}
|
|
6884
|
-
if (this.config.registryPolicy === "fail-closed") {
|
|
6885
|
-
const resolved2 = await (0, import_sdk_core11.resolveFailClosedTinyCloudHosts)(address, {
|
|
6886
|
-
registryUrl: this.config.tinycloudRegistryUrl,
|
|
6887
|
-
registryChainId: this.config.registryChainId,
|
|
6888
|
-
newUserHost: this.config.newUserHost,
|
|
6889
|
-
sequenceStore: this.config.registrySequenceStore,
|
|
6890
|
-
onNodeNotFound: this.config.onNodeNotFound,
|
|
6891
|
-
autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
|
|
6892
|
-
localNodeUrl: this.config.localNodeUrl,
|
|
6893
|
-
localLinkName: this.config.localLinkName,
|
|
6894
|
-
expectedNodeDid: this.config.expectedNodeDid,
|
|
6895
|
-
localNodeIdentityStore: this.config.localNodeIdentityStore
|
|
6896
|
-
});
|
|
6897
|
-
return resolved2.hosts[0];
|
|
6898
|
-
}
|
|
6899
|
-
const resolved = await (0, import_sdk_core11.resolveTinyCloudHosts)((0, import_sdk_core11.pkhDid)(address, chainId), {
|
|
6900
|
-
registryUrl: this.config.tinycloudRegistryUrl,
|
|
6901
|
-
fallbackHosts: this.config.tinycloudFallbackHosts,
|
|
6902
|
-
autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
|
|
6903
|
-
localNodeUrl: this.config.localNodeUrl,
|
|
6904
|
-
localLinkName: this.config.localLinkName,
|
|
6905
|
-
expectedNodeDid: this.config.expectedNodeDid,
|
|
6906
|
-
localNodeIdentityStore: this.config.localNodeIdentityStore
|
|
6907
|
-
});
|
|
6908
|
-
return resolved.hosts[0];
|
|
6909
|
-
}
|
|
6910
7055
|
/**
|
|
6911
7056
|
* Resolve the currently-active TinyCloudSession, preferring the auth
|
|
6912
7057
|
* layer's value (wallet mode) and falling back to the node-level
|
|
@@ -7381,10 +7526,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7381
7526
|
* second time. Only a value recorded for this exact host qualifies; older
|
|
7382
7527
|
* nodes that omit `nodeId` fall through to the fetch.
|
|
7383
7528
|
*/
|
|
7384
|
-
async fetchNodeId() {
|
|
7385
|
-
const cached = this.auth?.nodeIdForHost(
|
|
7529
|
+
async fetchNodeId(host = this.config.host) {
|
|
7530
|
+
const cached = this.auth?.nodeIdForHost(host);
|
|
7386
7531
|
if (cached) return cached;
|
|
7387
|
-
const response = await fetch(`${
|
|
7532
|
+
const response = await fetch(`${host}/info`);
|
|
7388
7533
|
if (!response.ok) {
|
|
7389
7534
|
throw await (0, import_sdk_core11.httpResponseError)(response, "Failed to fetch node info");
|
|
7390
7535
|
}
|
|
@@ -7432,10 +7577,24 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7432
7577
|
)
|
|
7433
7578
|
};
|
|
7434
7579
|
}
|
|
7435
|
-
async
|
|
7436
|
-
const
|
|
7437
|
-
|
|
7438
|
-
);
|
|
7580
|
+
async encryptionDestination(networkId) {
|
|
7581
|
+
const destination = (await this.routingDestinations(networkId, "encryption"))[0];
|
|
7582
|
+
if (!destination) throw new Error("No encryption destination is registered");
|
|
7583
|
+
await (0, import_sdk_services5.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
|
|
7584
|
+
return { ...destination, nodeDid: destination.nodeDid ?? await this.fetchNodeId(destination.url) };
|
|
7585
|
+
}
|
|
7586
|
+
async fetchEncryptionNetworkAt(host, networkId, fetchFn, callerSignal) {
|
|
7587
|
+
let response;
|
|
7588
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
7589
|
+
try {
|
|
7590
|
+
response = await fetchFn(`${host}/encryption/networks/${encodeURIComponent(networkId)}`, {
|
|
7591
|
+
signal: callerSignal ? AbortSignal.any([callerSignal, timeout]) : timeout,
|
|
7592
|
+
redirect: "error"
|
|
7593
|
+
});
|
|
7594
|
+
} catch (error) {
|
|
7595
|
+
if ((0, import_sdk_services5.routingFailure)(error) || callerSignal?.aborted || !timeout.aborted && error instanceof Error && error.name === "AbortError") throw error;
|
|
7596
|
+
throw new import_sdk_services5.NodeNotRespondingError([host], error);
|
|
7597
|
+
}
|
|
7439
7598
|
if (response.status === 404) return null;
|
|
7440
7599
|
if (!response.ok) {
|
|
7441
7600
|
throw await (0, import_sdk_core11.httpResponseError)(response, `Failed to fetch encryption network ${networkId}`);
|
|
@@ -7445,18 +7604,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7445
7604
|
}
|
|
7446
7605
|
createEncryptionService(binding) {
|
|
7447
7606
|
const graph = binding?.graph ?? this._serviceGraph;
|
|
7448
|
-
const host = binding?.host ?? this.config.host;
|
|
7449
7607
|
const session = binding?.session ?? this.requireEncryptionSession();
|
|
7450
7608
|
const did = binding?.did ?? this.did;
|
|
7451
7609
|
const address = binding?.address ?? this._address;
|
|
7452
7610
|
const chainId = binding?.chainId ?? this._chainId;
|
|
7453
7611
|
const crypto2 = this.createEncryptionCrypto();
|
|
7454
|
-
const descriptorCache = /* @__PURE__ */ new Map();
|
|
7455
7612
|
const transport = {
|
|
7456
|
-
postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
|
|
7613
|
+
postDecrypt: async ({ targetNode, networkId, authorization, canonicalBody, signal }) => {
|
|
7457
7614
|
graph.assertActive();
|
|
7615
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7616
|
+
if (destination.nodeDid !== targetNode) throw new import_sdk_services5.NodeIdentityMismatchError(destination.url, destination.nodeDid, targetNode);
|
|
7458
7617
|
const response = await graph.fetch(
|
|
7459
|
-
`${
|
|
7618
|
+
`${destination.url}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
|
|
7460
7619
|
{
|
|
7461
7620
|
method: "POST",
|
|
7462
7621
|
headers: {
|
|
@@ -7469,7 +7628,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7469
7628
|
);
|
|
7470
7629
|
graph.assertActive();
|
|
7471
7630
|
if (!response.ok) {
|
|
7472
|
-
const permissionHint = response.status === 401 || response.status === 403 ? (0,
|
|
7631
|
+
const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services6.parsePermissionHint)({
|
|
7473
7632
|
service: "tinycloud.encryption",
|
|
7474
7633
|
path: networkId,
|
|
7475
7634
|
actions: ["tinycloud.encryption/decrypt"]
|
|
@@ -7484,6 +7643,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7484
7643
|
}
|
|
7485
7644
|
};
|
|
7486
7645
|
return new import_sdk_core11.EncryptionService({
|
|
7646
|
+
resolveTargetNode: async (networkId) => (await this.encryptionDestination(networkId)).nodeDid,
|
|
7487
7647
|
crypto: crypto2,
|
|
7488
7648
|
signer: {
|
|
7489
7649
|
signDecryptInvocation: async (input) => {
|
|
@@ -7507,18 +7667,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7507
7667
|
node: {
|
|
7508
7668
|
fetchByNetworkId: async (networkId) => {
|
|
7509
7669
|
graph.assertActive();
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
pending = this.fetchEncryptionNetworkAt(host, networkId, graph.fetch).then((descriptor2) => {
|
|
7513
|
-
if (descriptor2 === null) descriptorCache.delete(networkId);
|
|
7514
|
-
return descriptor2;
|
|
7515
|
-
}).catch((error) => {
|
|
7516
|
-
descriptorCache.delete(networkId);
|
|
7517
|
-
throw error;
|
|
7518
|
-
});
|
|
7519
|
-
descriptorCache.set(networkId, pending);
|
|
7520
|
-
}
|
|
7521
|
-
const descriptor = await pending;
|
|
7670
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7671
|
+
const descriptor = await this.fetchEncryptionNetworkAt(destination.url, networkId, graph.fetch, graph.signal);
|
|
7522
7672
|
graph.assertActive();
|
|
7523
7673
|
return descriptor;
|
|
7524
7674
|
}
|
|
@@ -7526,13 +7676,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7526
7676
|
wellKnown: {
|
|
7527
7677
|
fetchWellKnown: async (principal, discoveryKey) => {
|
|
7528
7678
|
graph.assertActive();
|
|
7679
|
+
const destination = await this.encryptionDestination(this.getDefaultEncryptionNetworkId());
|
|
7680
|
+
if (this.auth?.registryRecordPresent) return null;
|
|
7529
7681
|
if (!address || !didPrincipalMatches2(principal, did)) {
|
|
7530
7682
|
return null;
|
|
7531
7683
|
}
|
|
7532
7684
|
const publicSpaceId = (0, import_sdk_core11.makePublicSpaceId)(address, chainId);
|
|
7533
7685
|
const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
|
|
7534
7686
|
const response = await graph.fetch(
|
|
7535
|
-
`${
|
|
7687
|
+
`${destination.url}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
|
|
7536
7688
|
{ method: "GET" }
|
|
7537
7689
|
);
|
|
7538
7690
|
graph.assertActive();
|
|
@@ -7902,7 +8054,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7902
8054
|
const signature = await this.signer.signMessage(prepared.siwe);
|
|
7903
8055
|
assertOwnerGraphActive();
|
|
7904
8056
|
const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature });
|
|
7905
|
-
const activation = await (
|
|
8057
|
+
const activation = await this.activateSignedSession(delegationSession, prepared.siwe, host);
|
|
7906
8058
|
assertOwnerGraphActive();
|
|
7907
8059
|
if (!activation.success) {
|
|
7908
8060
|
throw Object.assign(
|
|
@@ -8221,15 +8373,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8221
8373
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
8222
8374
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
8223
8375
|
return this.fetchEncryptionNetworkAt(
|
|
8224
|
-
this.
|
|
8376
|
+
(await this.encryptionDestination(networkId)).url,
|
|
8225
8377
|
networkId,
|
|
8226
8378
|
globalThis.fetch.bind(globalThis)
|
|
8227
8379
|
);
|
|
8228
8380
|
}
|
|
8229
8381
|
async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
|
|
8230
|
-
const targetNode = await this.fetchNodeId();
|
|
8231
8382
|
const ownerDid = this.did;
|
|
8232
8383
|
const networkId = this.getDefaultEncryptionNetworkId(name);
|
|
8384
|
+
const destination = await this.encryptionDestination(networkId);
|
|
8385
|
+
const targetNode = destination.nodeDid;
|
|
8233
8386
|
const body = {
|
|
8234
8387
|
name,
|
|
8235
8388
|
ownerDid,
|
|
@@ -8252,7 +8405,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8252
8405
|
action: NETWORK_CREATE_ACTION2,
|
|
8253
8406
|
facts
|
|
8254
8407
|
});
|
|
8255
|
-
const response = await fetch(`${
|
|
8408
|
+
const response = await this._serviceGraph.fetch(`${destination.url}/encryption/networks`, {
|
|
8256
8409
|
method: "POST",
|
|
8257
8410
|
headers: {
|
|
8258
8411
|
Authorization: signed.authorization,
|
|
@@ -8345,7 +8498,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8345
8498
|
);
|
|
8346
8499
|
if (result.status !== "ok") {
|
|
8347
8500
|
if (result.status === "permission_required") {
|
|
8348
|
-
const hint = (0,
|
|
8501
|
+
const hint = (0, import_sdk_services6.parsePermissionHint)(result.hint);
|
|
8349
8502
|
if (hint === void 0) return { status: "read_failed" };
|
|
8350
8503
|
return {
|
|
8351
8504
|
status: "permission_required",
|
|
@@ -8592,6 +8745,97 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8592
8745
|
}
|
|
8593
8746
|
return { recap: exactVerifiedRecap(verified), expiresAt };
|
|
8594
8747
|
}
|
|
8748
|
+
async activateResourceSession(delegationHeader, resources, hostHint, signal) {
|
|
8749
|
+
const targets = /* @__PURE__ */ new Map();
|
|
8750
|
+
const routedSpaces = [];
|
|
8751
|
+
let unserved;
|
|
8752
|
+
let requestedResource;
|
|
8753
|
+
for (const resource of new Set(resources)) {
|
|
8754
|
+
requestedResource ?? (requestedResource = resource);
|
|
8755
|
+
signal?.throwIfAborted();
|
|
8756
|
+
try {
|
|
8757
|
+
const encryption = resource.startsWith("urn:tinycloud:encryption:");
|
|
8758
|
+
const destinations = await this.routingDestinations(
|
|
8759
|
+
resource,
|
|
8760
|
+
encryption ? "encryption" : "host",
|
|
8761
|
+
[hostHint ?? this.config.host]
|
|
8762
|
+
);
|
|
8763
|
+
if (!encryption && destinations.some(({ nodeDid }) => nodeDid !== void 0)) routedSpaces.push(resource);
|
|
8764
|
+
for (const destination of destinations) {
|
|
8765
|
+
let target = targets.get(destination.url);
|
|
8766
|
+
if (!target) {
|
|
8767
|
+
target = { destination, spaces: [] };
|
|
8768
|
+
targets.set(destination.url, target);
|
|
8769
|
+
}
|
|
8770
|
+
if (!encryption && destination.nodeDid !== void 0) target.spaces.push(resource);
|
|
8771
|
+
}
|
|
8772
|
+
} catch (error) {
|
|
8773
|
+
if (!(error instanceof import_sdk_core11.NodeNotFoundError)) throw error;
|
|
8774
|
+
unserved ?? (unserved = error);
|
|
8775
|
+
}
|
|
8776
|
+
}
|
|
8777
|
+
if (targets.size === 0) throw unserved ?? new import_sdk_services5.ResourceNotRoutedError(requestedResource ?? "session grant");
|
|
8778
|
+
const succeeded = [];
|
|
8779
|
+
const accepted = [];
|
|
8780
|
+
const failed = [];
|
|
8781
|
+
let refusal;
|
|
8782
|
+
for (const { destination: target, spaces } of targets.values()) {
|
|
8783
|
+
signal?.throwIfAborted();
|
|
8784
|
+
try {
|
|
8785
|
+
await (0, import_sdk_services5.verifyResourceDestination)(target, globalThis.fetch.bind(globalThis));
|
|
8786
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
8787
|
+
const result = await (0, import_sdk_core11.activateSessionWithHost)(
|
|
8788
|
+
target.url,
|
|
8789
|
+
delegationHeader,
|
|
8790
|
+
signal ? AbortSignal.any([signal, timeout]) : timeout
|
|
8791
|
+
);
|
|
8792
|
+
this.throwRegistryRefusal(result.status, result.error ?? "");
|
|
8793
|
+
if (result.success) {
|
|
8794
|
+
const skipped = result.skipped?.find((space) => spaces.some((expected) => this.spaceIdsEqual(space, expected)));
|
|
8795
|
+
if (skipped) {
|
|
8796
|
+
failed.push({ url: target.url, status: result.status, cause: new import_sdk_services5.ResourceNotRoutedError(skipped) });
|
|
8797
|
+
continue;
|
|
8798
|
+
}
|
|
8799
|
+
succeeded.push(target.url);
|
|
8800
|
+
accepted.push(result);
|
|
8801
|
+
} else {
|
|
8802
|
+
refusal = result;
|
|
8803
|
+
failed.push({ url: target.url, status: result.status, cause: result });
|
|
8804
|
+
}
|
|
8805
|
+
} catch (error) {
|
|
8806
|
+
if (signal?.aborted) throw error;
|
|
8807
|
+
failed.push({ url: target.url, cause: (0, import_sdk_services5.routingFailure)(error) ? error : new import_sdk_services5.NodeNotRespondingError([target.url], error) });
|
|
8808
|
+
}
|
|
8809
|
+
}
|
|
8810
|
+
if (failed.length > 0) {
|
|
8811
|
+
if (targets.size > 1) throw new import_sdk_services5.PartialNodeWriteError(succeeded, failed);
|
|
8812
|
+
if (refusal) return refusal;
|
|
8813
|
+
throw failed[0].cause;
|
|
8814
|
+
}
|
|
8815
|
+
const activated = new Set(accepted.flatMap((result) => result.activated ?? []));
|
|
8816
|
+
return {
|
|
8817
|
+
...accepted[0],
|
|
8818
|
+
activated: [...activated],
|
|
8819
|
+
skipped: [...new Set(accepted.flatMap((result) => result.skipped ?? []))].filter((space) => !activated.has(space) && !routedSpaces.some((expected) => this.spaceIdsEqual(space, expected)))
|
|
8820
|
+
};
|
|
8821
|
+
}
|
|
8822
|
+
activateRoutedDelegation(delegation, signal) {
|
|
8823
|
+
const resources = this.operationsFromDelegation(delegation).map(
|
|
8824
|
+
(operation) => operation.resource?.startsWith("urn:tinycloud:encryption:") ? operation.resource.split("/")[0] : operation.spaceId ?? delegation.spaceId
|
|
8825
|
+
);
|
|
8826
|
+
return this.activateResourceSession(delegation.delegationHeader, resources, delegation.host, signal);
|
|
8827
|
+
}
|
|
8828
|
+
activateSignedSession(session, siwe, hostHint, signal) {
|
|
8829
|
+
const resources = this.parseRecapWithCaveats(siwe).map(
|
|
8830
|
+
(entry) => this.isEncryptionNetworkOperation(this.invocationServiceName(entry.service), entry.path, entry.space) ? entry.path.split("/")[0] : entry.space.startsWith("tinycloud:") ? entry.space : session.spaceId
|
|
8831
|
+
);
|
|
8832
|
+
return this.activateResourceSession(
|
|
8833
|
+
session.delegationHeader,
|
|
8834
|
+
resources.length > 0 ? resources : [session.spaceId],
|
|
8835
|
+
hostHint,
|
|
8836
|
+
signal
|
|
8837
|
+
);
|
|
8838
|
+
}
|
|
8595
8839
|
/**
|
|
8596
8840
|
* Install a portable runtime permission delegation into this SDK instance so
|
|
8597
8841
|
* matching service calls and downstream `delegateTo()` calls can use it.
|
|
@@ -8611,16 +8855,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8611
8855
|
`Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
|
|
8612
8856
|
);
|
|
8613
8857
|
}
|
|
8614
|
-
const targetHost = delegation.host ?? this.config.host;
|
|
8615
8858
|
let activateResult;
|
|
8616
8859
|
try {
|
|
8617
|
-
activateResult = await (
|
|
8618
|
-
targetHost,
|
|
8619
|
-
delegation.delegationHeader,
|
|
8620
|
-
graph.signal
|
|
8621
|
-
);
|
|
8860
|
+
activateResult = await this.activateRoutedDelegation(delegation, graph.signal);
|
|
8622
8861
|
} catch (cause) {
|
|
8623
8862
|
graph.assertActive();
|
|
8863
|
+
if ((0, import_sdk_services5.routingFailure)(cause)) throw cause;
|
|
8624
8864
|
throw Object.assign(
|
|
8625
8865
|
new Error("The TinyCloud host could not be reached to activate the delegation."),
|
|
8626
8866
|
{ code: "ACTIVATION_TRANSPORT_FAILED", cause }
|
|
@@ -8776,11 +9016,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8776
9016
|
...prepared,
|
|
8777
9017
|
signature
|
|
8778
9018
|
});
|
|
8779
|
-
const
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
9019
|
+
const delegation = this.runtimeDelegationFromSession(
|
|
9020
|
+
delegatedSession,
|
|
9021
|
+
delegatedEntries,
|
|
9022
|
+
spaceId,
|
|
9023
|
+
captured,
|
|
9024
|
+
expiresAt,
|
|
9025
|
+
{ siwe: prepared.siwe, signature }
|
|
9026
|
+
);
|
|
9027
|
+
const activateResult = await this.activateRoutedDelegation(delegation, graph.signal).catch((error) => {
|
|
8784
9028
|
graph.assertActive();
|
|
8785
9029
|
throw error;
|
|
8786
9030
|
});
|
|
@@ -8796,14 +9040,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8796
9040
|
{ cause: activateResult }
|
|
8797
9041
|
);
|
|
8798
9042
|
}
|
|
8799
|
-
const delegation = this.runtimeDelegationFromSession(
|
|
8800
|
-
delegatedSession,
|
|
8801
|
-
delegatedEntries,
|
|
8802
|
-
spaceId,
|
|
8803
|
-
captured,
|
|
8804
|
-
expiresAt,
|
|
8805
|
-
{ siwe: prepared.siwe, signature }
|
|
8806
|
-
);
|
|
8807
9043
|
graph.assertActive();
|
|
8808
9044
|
assertCapturedKey?.();
|
|
8809
9045
|
this.runtimePermissionGrants.push({
|
|
@@ -8948,7 +9184,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8948
9184
|
if (!publicSpaceId) {
|
|
8949
9185
|
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
8950
9186
|
}
|
|
8951
|
-
await this.assertMayCreateSpace();
|
|
9187
|
+
await this.assertMayCreateSpace(publicSpaceId);
|
|
8952
9188
|
await this.auth.hostPublicSpace(publicSpaceId);
|
|
8953
9189
|
const kvActions = [import_sdk_core11.KV.PUT, import_sdk_core11.KV.GET, import_sdk_core11.KV.DEL, import_sdk_core11.KV.LIST, import_sdk_core11.KV.METADATA];
|
|
8954
9190
|
const abilities = { kv: { "": kvActions } };
|
|
@@ -8971,10 +9207,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8971
9207
|
...prepared,
|
|
8972
9208
|
signature
|
|
8973
9209
|
});
|
|
8974
|
-
const activateResult = await (
|
|
8975
|
-
this.config.host,
|
|
8976
|
-
delegationSession.delegationHeader
|
|
8977
|
-
);
|
|
9210
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
8978
9211
|
if (!activateResult.success) {
|
|
8979
9212
|
throw Object.assign(
|
|
8980
9213
|
new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -9524,10 +9757,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9524
9757
|
if (onPrepared) {
|
|
9525
9758
|
await onPrepared(delegation);
|
|
9526
9759
|
}
|
|
9527
|
-
const activateResult = await (
|
|
9528
|
-
this.config.host,
|
|
9529
|
-
delegationHeader
|
|
9530
|
-
);
|
|
9760
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9531
9761
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9532
9762
|
return delegation;
|
|
9533
9763
|
}
|
|
@@ -9561,10 +9791,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9561
9791
|
if (onPrepared) {
|
|
9562
9792
|
await onPrepared(delegation);
|
|
9563
9793
|
}
|
|
9564
|
-
const activateResult = await (
|
|
9565
|
-
targetHost,
|
|
9566
|
-
delegationHeader
|
|
9567
|
-
);
|
|
9794
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9568
9795
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9569
9796
|
return delegation;
|
|
9570
9797
|
}
|
|
@@ -10216,10 +10443,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10216
10443
|
...prepared,
|
|
10217
10444
|
signature
|
|
10218
10445
|
});
|
|
10219
|
-
const activateResult = await (
|
|
10220
|
-
this.config.host,
|
|
10221
|
-
delegationSession.delegationHeader
|
|
10222
|
-
);
|
|
10446
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
10223
10447
|
if (!activateResult.success) {
|
|
10224
10448
|
throw Object.assign(
|
|
10225
10449
|
new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10264,10 +10488,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10264
10488
|
...publicPrepared,
|
|
10265
10489
|
signature: publicSignature
|
|
10266
10490
|
});
|
|
10267
|
-
const publicActivateResult = await (
|
|
10268
|
-
this.config.host,
|
|
10269
|
-
publicSession.delegationHeader
|
|
10270
|
-
);
|
|
10491
|
+
const publicActivateResult = await this.activateSignedSession(publicSession, publicPrepared.siwe);
|
|
10271
10492
|
if (publicActivateResult.success) {
|
|
10272
10493
|
result.publicDelegation = {
|
|
10273
10494
|
cid: publicSession.delegationCid,
|
|
@@ -10373,11 +10594,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10373
10594
|
...prepared,
|
|
10374
10595
|
signature
|
|
10375
10596
|
});
|
|
10376
|
-
const activateResult = await (
|
|
10377
|
-
targetHost,
|
|
10378
|
-
invokerSession.delegationHeader,
|
|
10379
|
-
graph.signal
|
|
10380
|
-
).catch((error) => {
|
|
10597
|
+
const activateResult = await this.activateSignedSession(invokerSession, prepared.siwe, targetHost, graph.signal).catch((error) => {
|
|
10381
10598
|
graph.assertActive();
|
|
10382
10599
|
throw error;
|
|
10383
10600
|
});
|
|
@@ -10504,10 +10721,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10504
10721
|
...prepared,
|
|
10505
10722
|
signature
|
|
10506
10723
|
});
|
|
10507
|
-
const activateResult = await (
|
|
10508
|
-
targetHost,
|
|
10509
|
-
subDelegationSession.delegationHeader
|
|
10510
|
-
);
|
|
10724
|
+
const activateResult = await this.activateSignedSession(subDelegationSession, prepared.siwe, targetHost);
|
|
10511
10725
|
if (!activateResult.success) {
|
|
10512
10726
|
throw Object.assign(
|
|
10513
10727
|
new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10587,7 +10801,10 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
|
|
|
10587
10801
|
KVService,
|
|
10588
10802
|
ManifestValidationError,
|
|
10589
10803
|
MemorySessionStorage,
|
|
10804
|
+
NodeIdentityMismatchError,
|
|
10805
|
+
NodeNotRespondingError,
|
|
10590
10806
|
NodeUserAuthorization,
|
|
10807
|
+
PartialNodeWriteError,
|
|
10591
10808
|
PermissionNotInManifestError,
|
|
10592
10809
|
PrefixedKVService,
|
|
10593
10810
|
ProtocolMismatchError,
|