@tinycloud/node-sdk 3.12.0-beta.2 → 3.12.0-beta.4
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 +499 -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 +525 -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 +516 -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 +526 -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,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6395
6589
|
}
|
|
6396
6590
|
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
6397
6591
|
}
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6400
|
-
restoredAddress,
|
|
6401
|
-
sessionData.chainId
|
|
6402
|
-
);
|
|
6403
|
-
const stagedHost = resolvedHost ?? this.config.host;
|
|
6592
|
+
const restoreHints = this.explicitHost ? [this.explicitHost] : sessionData.tinycloudHosts ?? [];
|
|
6593
|
+
const stagedHost = (0, import_sdk_core11.exactHttpOrigin)(restoreHints[0] ?? this.config.host, "restored host hint");
|
|
6404
6594
|
let restoredNodeFeatures;
|
|
6405
6595
|
let replicaProtocolMismatch = false;
|
|
6406
6596
|
if (this.config.replication?.enabled) {
|
|
@@ -6517,7 +6707,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6517
6707
|
this.sessionManager = stagedManager;
|
|
6518
6708
|
this.sessionKeyId = stagedKeyId;
|
|
6519
6709
|
this.sessionKeyJwk = stagedJwk;
|
|
6520
|
-
|
|
6710
|
+
this.config.host = stagedHost;
|
|
6521
6711
|
if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
|
|
6522
6712
|
this.storageFull.clear();
|
|
6523
6713
|
this._requestLog?.clear();
|
|
@@ -6584,6 +6774,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6584
6774
|
if (attempt === this.latestSessionAttempt) await this.endSession();
|
|
6585
6775
|
this.assertSessionEpoch(epoch, "Session restore");
|
|
6586
6776
|
}
|
|
6777
|
+
if (this.config.replication?.enabled) {
|
|
6778
|
+
if (stagedAddress !== void 0) this.refreshRegistryOwner((0, import_sdk_core11.pkhDid)(stagedAddress, stagedChainId));
|
|
6779
|
+
this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
|
|
6780
|
+
}
|
|
6587
6781
|
}
|
|
6588
6782
|
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
6589
6783
|
if (!session?.siwe) return { cache: void 0, grants: [] };
|
|
@@ -6632,7 +6826,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6632
6826
|
};
|
|
6633
6827
|
}
|
|
6634
6828
|
stageRestoredServiceGraph(input) {
|
|
6635
|
-
const graph = this.createServiceGraphLifetime();
|
|
6829
|
+
const graph = this.createServiceGraphLifetime(input.serviceSession);
|
|
6636
6830
|
const core = this.auth ? new import_sdk_core11.TinyCloud(this.auth, {
|
|
6637
6831
|
invokeAny: graph.invokeAny,
|
|
6638
6832
|
telemetry: this.config.telemetry,
|
|
@@ -6857,56 +7051,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6857
7051
|
spaceService.updateConfig({ sharingService });
|
|
6858
7052
|
return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
|
|
6859
7053
|
}
|
|
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
7054
|
/**
|
|
6911
7055
|
* Resolve the currently-active TinyCloudSession, preferring the auth
|
|
6912
7056
|
* layer's value (wallet mode) and falling back to the node-level
|
|
@@ -7381,10 +7525,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7381
7525
|
* second time. Only a value recorded for this exact host qualifies; older
|
|
7382
7526
|
* nodes that omit `nodeId` fall through to the fetch.
|
|
7383
7527
|
*/
|
|
7384
|
-
async fetchNodeId() {
|
|
7385
|
-
const cached = this.auth?.nodeIdForHost(
|
|
7528
|
+
async fetchNodeId(host = this.config.host) {
|
|
7529
|
+
const cached = this.auth?.nodeIdForHost(host);
|
|
7386
7530
|
if (cached) return cached;
|
|
7387
|
-
const response = await fetch(`${
|
|
7531
|
+
const response = await fetch(`${host}/info`);
|
|
7388
7532
|
if (!response.ok) {
|
|
7389
7533
|
throw await (0, import_sdk_core11.httpResponseError)(response, "Failed to fetch node info");
|
|
7390
7534
|
}
|
|
@@ -7432,10 +7576,24 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7432
7576
|
)
|
|
7433
7577
|
};
|
|
7434
7578
|
}
|
|
7435
|
-
async
|
|
7436
|
-
const
|
|
7437
|
-
|
|
7438
|
-
);
|
|
7579
|
+
async encryptionDestination(networkId) {
|
|
7580
|
+
const destination = (await this.routingDestinations(networkId, "encryption"))[0];
|
|
7581
|
+
if (!destination) throw new Error("No encryption destination is registered");
|
|
7582
|
+
await (0, import_sdk_services5.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
|
|
7583
|
+
return { ...destination, nodeDid: destination.nodeDid ?? await this.fetchNodeId(destination.url) };
|
|
7584
|
+
}
|
|
7585
|
+
async fetchEncryptionNetworkAt(host, networkId, fetchFn, callerSignal) {
|
|
7586
|
+
let response;
|
|
7587
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
7588
|
+
try {
|
|
7589
|
+
response = await fetchFn(`${host}/encryption/networks/${encodeURIComponent(networkId)}`, {
|
|
7590
|
+
signal: callerSignal ? AbortSignal.any([callerSignal, timeout]) : timeout,
|
|
7591
|
+
redirect: "error"
|
|
7592
|
+
});
|
|
7593
|
+
} catch (error) {
|
|
7594
|
+
if ((0, import_sdk_services5.routingFailure)(error) || callerSignal?.aborted || !timeout.aborted && error instanceof Error && error.name === "AbortError") throw error;
|
|
7595
|
+
throw new import_sdk_services5.NodeNotRespondingError([host], error);
|
|
7596
|
+
}
|
|
7439
7597
|
if (response.status === 404) return null;
|
|
7440
7598
|
if (!response.ok) {
|
|
7441
7599
|
throw await (0, import_sdk_core11.httpResponseError)(response, `Failed to fetch encryption network ${networkId}`);
|
|
@@ -7445,18 +7603,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7445
7603
|
}
|
|
7446
7604
|
createEncryptionService(binding) {
|
|
7447
7605
|
const graph = binding?.graph ?? this._serviceGraph;
|
|
7448
|
-
const host = binding?.host ?? this.config.host;
|
|
7449
7606
|
const session = binding?.session ?? this.requireEncryptionSession();
|
|
7450
7607
|
const did = binding?.did ?? this.did;
|
|
7451
7608
|
const address = binding?.address ?? this._address;
|
|
7452
7609
|
const chainId = binding?.chainId ?? this._chainId;
|
|
7453
7610
|
const crypto2 = this.createEncryptionCrypto();
|
|
7454
|
-
const descriptorCache = /* @__PURE__ */ new Map();
|
|
7455
7611
|
const transport = {
|
|
7456
|
-
postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
|
|
7612
|
+
postDecrypt: async ({ targetNode, networkId, authorization, canonicalBody, signal }) => {
|
|
7457
7613
|
graph.assertActive();
|
|
7614
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7615
|
+
if (destination.nodeDid !== targetNode) throw new import_sdk_services5.NodeIdentityMismatchError(destination.url, destination.nodeDid, targetNode);
|
|
7458
7616
|
const response = await graph.fetch(
|
|
7459
|
-
`${
|
|
7617
|
+
`${destination.url}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
|
|
7460
7618
|
{
|
|
7461
7619
|
method: "POST",
|
|
7462
7620
|
headers: {
|
|
@@ -7469,7 +7627,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7469
7627
|
);
|
|
7470
7628
|
graph.assertActive();
|
|
7471
7629
|
if (!response.ok) {
|
|
7472
|
-
const permissionHint = response.status === 401 || response.status === 403 ? (0,
|
|
7630
|
+
const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services6.parsePermissionHint)({
|
|
7473
7631
|
service: "tinycloud.encryption",
|
|
7474
7632
|
path: networkId,
|
|
7475
7633
|
actions: ["tinycloud.encryption/decrypt"]
|
|
@@ -7484,6 +7642,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7484
7642
|
}
|
|
7485
7643
|
};
|
|
7486
7644
|
return new import_sdk_core11.EncryptionService({
|
|
7645
|
+
resolveTargetNode: async (networkId) => (await this.encryptionDestination(networkId)).nodeDid,
|
|
7487
7646
|
crypto: crypto2,
|
|
7488
7647
|
signer: {
|
|
7489
7648
|
signDecryptInvocation: async (input) => {
|
|
@@ -7507,18 +7666,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7507
7666
|
node: {
|
|
7508
7667
|
fetchByNetworkId: async (networkId) => {
|
|
7509
7668
|
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;
|
|
7669
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7670
|
+
const descriptor = await this.fetchEncryptionNetworkAt(destination.url, networkId, graph.fetch, graph.signal);
|
|
7522
7671
|
graph.assertActive();
|
|
7523
7672
|
return descriptor;
|
|
7524
7673
|
}
|
|
@@ -7526,13 +7675,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7526
7675
|
wellKnown: {
|
|
7527
7676
|
fetchWellKnown: async (principal, discoveryKey) => {
|
|
7528
7677
|
graph.assertActive();
|
|
7678
|
+
const destination = await this.encryptionDestination(this.getDefaultEncryptionNetworkId());
|
|
7679
|
+
if (this.auth?.registryRecordPresent) return null;
|
|
7529
7680
|
if (!address || !didPrincipalMatches2(principal, did)) {
|
|
7530
7681
|
return null;
|
|
7531
7682
|
}
|
|
7532
7683
|
const publicSpaceId = (0, import_sdk_core11.makePublicSpaceId)(address, chainId);
|
|
7533
7684
|
const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
|
|
7534
7685
|
const response = await graph.fetch(
|
|
7535
|
-
`${
|
|
7686
|
+
`${destination.url}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
|
|
7536
7687
|
{ method: "GET" }
|
|
7537
7688
|
);
|
|
7538
7689
|
graph.assertActive();
|
|
@@ -7902,7 +8053,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7902
8053
|
const signature = await this.signer.signMessage(prepared.siwe);
|
|
7903
8054
|
assertOwnerGraphActive();
|
|
7904
8055
|
const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature });
|
|
7905
|
-
const activation = await (
|
|
8056
|
+
const activation = await this.activateSignedSession(delegationSession, prepared.siwe, host);
|
|
7906
8057
|
assertOwnerGraphActive();
|
|
7907
8058
|
if (!activation.success) {
|
|
7908
8059
|
throw Object.assign(
|
|
@@ -8221,15 +8372,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8221
8372
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
8222
8373
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
8223
8374
|
return this.fetchEncryptionNetworkAt(
|
|
8224
|
-
this.
|
|
8375
|
+
(await this.encryptionDestination(networkId)).url,
|
|
8225
8376
|
networkId,
|
|
8226
8377
|
globalThis.fetch.bind(globalThis)
|
|
8227
8378
|
);
|
|
8228
8379
|
}
|
|
8229
8380
|
async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
|
|
8230
|
-
const targetNode = await this.fetchNodeId();
|
|
8231
8381
|
const ownerDid = this.did;
|
|
8232
8382
|
const networkId = this.getDefaultEncryptionNetworkId(name);
|
|
8383
|
+
const destination = await this.encryptionDestination(networkId);
|
|
8384
|
+
const targetNode = destination.nodeDid;
|
|
8233
8385
|
const body = {
|
|
8234
8386
|
name,
|
|
8235
8387
|
ownerDid,
|
|
@@ -8252,7 +8404,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8252
8404
|
action: NETWORK_CREATE_ACTION2,
|
|
8253
8405
|
facts
|
|
8254
8406
|
});
|
|
8255
|
-
const response = await fetch(`${
|
|
8407
|
+
const response = await this._serviceGraph.fetch(`${destination.url}/encryption/networks`, {
|
|
8256
8408
|
method: "POST",
|
|
8257
8409
|
headers: {
|
|
8258
8410
|
Authorization: signed.authorization,
|
|
@@ -8345,7 +8497,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8345
8497
|
);
|
|
8346
8498
|
if (result.status !== "ok") {
|
|
8347
8499
|
if (result.status === "permission_required") {
|
|
8348
|
-
const hint = (0,
|
|
8500
|
+
const hint = (0, import_sdk_services6.parsePermissionHint)(result.hint);
|
|
8349
8501
|
if (hint === void 0) return { status: "read_failed" };
|
|
8350
8502
|
return {
|
|
8351
8503
|
status: "permission_required",
|
|
@@ -8592,6 +8744,97 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8592
8744
|
}
|
|
8593
8745
|
return { recap: exactVerifiedRecap(verified), expiresAt };
|
|
8594
8746
|
}
|
|
8747
|
+
async activateResourceSession(delegationHeader, resources, hostHint, signal) {
|
|
8748
|
+
const targets = /* @__PURE__ */ new Map();
|
|
8749
|
+
const routedSpaces = [];
|
|
8750
|
+
let unserved;
|
|
8751
|
+
let requestedResource;
|
|
8752
|
+
for (const resource of new Set(resources)) {
|
|
8753
|
+
requestedResource ?? (requestedResource = resource);
|
|
8754
|
+
signal?.throwIfAborted();
|
|
8755
|
+
try {
|
|
8756
|
+
const encryption = resource.startsWith("urn:tinycloud:encryption:");
|
|
8757
|
+
const destinations = await this.routingDestinations(
|
|
8758
|
+
resource,
|
|
8759
|
+
encryption ? "encryption" : "host",
|
|
8760
|
+
[hostHint ?? this.config.host]
|
|
8761
|
+
);
|
|
8762
|
+
if (!encryption && destinations.some(({ nodeDid }) => nodeDid !== void 0)) routedSpaces.push(resource);
|
|
8763
|
+
for (const destination of destinations) {
|
|
8764
|
+
let target = targets.get(destination.url);
|
|
8765
|
+
if (!target) {
|
|
8766
|
+
target = { destination, spaces: [] };
|
|
8767
|
+
targets.set(destination.url, target);
|
|
8768
|
+
}
|
|
8769
|
+
if (!encryption && destination.nodeDid !== void 0) target.spaces.push(resource);
|
|
8770
|
+
}
|
|
8771
|
+
} catch (error) {
|
|
8772
|
+
if (!(error instanceof import_sdk_core11.NodeNotFoundError)) throw error;
|
|
8773
|
+
unserved ?? (unserved = error);
|
|
8774
|
+
}
|
|
8775
|
+
}
|
|
8776
|
+
if (targets.size === 0) throw unserved ?? new import_sdk_services5.ResourceNotRoutedError(requestedResource ?? "session grant");
|
|
8777
|
+
const succeeded = [];
|
|
8778
|
+
const accepted = [];
|
|
8779
|
+
const failed = [];
|
|
8780
|
+
let refusal;
|
|
8781
|
+
for (const { destination: target, spaces } of targets.values()) {
|
|
8782
|
+
signal?.throwIfAborted();
|
|
8783
|
+
try {
|
|
8784
|
+
await (0, import_sdk_services5.verifyResourceDestination)(target, globalThis.fetch.bind(globalThis));
|
|
8785
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
8786
|
+
const result = await (0, import_sdk_core11.activateSessionWithHost)(
|
|
8787
|
+
target.url,
|
|
8788
|
+
delegationHeader,
|
|
8789
|
+
signal ? AbortSignal.any([signal, timeout]) : timeout
|
|
8790
|
+
);
|
|
8791
|
+
this.throwRegistryRefusal(result.status, result.error ?? "");
|
|
8792
|
+
if (result.success) {
|
|
8793
|
+
const skipped = result.skipped?.find((space) => spaces.some((expected) => this.spaceIdsEqual(space, expected)));
|
|
8794
|
+
if (skipped) {
|
|
8795
|
+
failed.push({ url: target.url, status: result.status, cause: new import_sdk_services5.ResourceNotRoutedError(skipped) });
|
|
8796
|
+
continue;
|
|
8797
|
+
}
|
|
8798
|
+
succeeded.push(target.url);
|
|
8799
|
+
accepted.push(result);
|
|
8800
|
+
} else {
|
|
8801
|
+
refusal = result;
|
|
8802
|
+
failed.push({ url: target.url, status: result.status, cause: result });
|
|
8803
|
+
}
|
|
8804
|
+
} catch (error) {
|
|
8805
|
+
if (signal?.aborted) throw error;
|
|
8806
|
+
failed.push({ url: target.url, cause: (0, import_sdk_services5.routingFailure)(error) ? error : new import_sdk_services5.NodeNotRespondingError([target.url], error) });
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
if (failed.length > 0) {
|
|
8810
|
+
if (targets.size > 1) throw new import_sdk_services5.PartialNodeWriteError(succeeded, failed);
|
|
8811
|
+
if (refusal) return refusal;
|
|
8812
|
+
throw failed[0].cause;
|
|
8813
|
+
}
|
|
8814
|
+
const activated = new Set(accepted.flatMap((result) => result.activated ?? []));
|
|
8815
|
+
return {
|
|
8816
|
+
...accepted[0],
|
|
8817
|
+
activated: [...activated],
|
|
8818
|
+
skipped: [...new Set(accepted.flatMap((result) => result.skipped ?? []))].filter((space) => !activated.has(space) && !routedSpaces.some((expected) => this.spaceIdsEqual(space, expected)))
|
|
8819
|
+
};
|
|
8820
|
+
}
|
|
8821
|
+
activateRoutedDelegation(delegation, signal) {
|
|
8822
|
+
const resources = this.operationsFromDelegation(delegation).map(
|
|
8823
|
+
(operation) => operation.resource?.startsWith("urn:tinycloud:encryption:") ? operation.resource.split("/")[0] : operation.spaceId ?? delegation.spaceId
|
|
8824
|
+
);
|
|
8825
|
+
return this.activateResourceSession(delegation.delegationHeader, resources, delegation.host, signal);
|
|
8826
|
+
}
|
|
8827
|
+
activateSignedSession(session, siwe, hostHint, signal) {
|
|
8828
|
+
const resources = this.parseRecapWithCaveats(siwe).map(
|
|
8829
|
+
(entry) => this.isEncryptionNetworkOperation(this.invocationServiceName(entry.service), entry.path, entry.space) ? entry.path.split("/")[0] : entry.space.startsWith("tinycloud:") ? entry.space : session.spaceId
|
|
8830
|
+
);
|
|
8831
|
+
return this.activateResourceSession(
|
|
8832
|
+
session.delegationHeader,
|
|
8833
|
+
resources.length > 0 ? resources : [session.spaceId],
|
|
8834
|
+
hostHint,
|
|
8835
|
+
signal
|
|
8836
|
+
);
|
|
8837
|
+
}
|
|
8595
8838
|
/**
|
|
8596
8839
|
* Install a portable runtime permission delegation into this SDK instance so
|
|
8597
8840
|
* matching service calls and downstream `delegateTo()` calls can use it.
|
|
@@ -8611,16 +8854,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8611
8854
|
`Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
|
|
8612
8855
|
);
|
|
8613
8856
|
}
|
|
8614
|
-
const targetHost = delegation.host ?? this.config.host;
|
|
8615
8857
|
let activateResult;
|
|
8616
8858
|
try {
|
|
8617
|
-
activateResult = await (
|
|
8618
|
-
targetHost,
|
|
8619
|
-
delegation.delegationHeader,
|
|
8620
|
-
graph.signal
|
|
8621
|
-
);
|
|
8859
|
+
activateResult = await this.activateRoutedDelegation(delegation, graph.signal);
|
|
8622
8860
|
} catch (cause) {
|
|
8623
8861
|
graph.assertActive();
|
|
8862
|
+
if ((0, import_sdk_services5.routingFailure)(cause)) throw cause;
|
|
8624
8863
|
throw Object.assign(
|
|
8625
8864
|
new Error("The TinyCloud host could not be reached to activate the delegation."),
|
|
8626
8865
|
{ code: "ACTIVATION_TRANSPORT_FAILED", cause }
|
|
@@ -8776,11 +9015,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8776
9015
|
...prepared,
|
|
8777
9016
|
signature
|
|
8778
9017
|
});
|
|
8779
|
-
const
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
9018
|
+
const delegation = this.runtimeDelegationFromSession(
|
|
9019
|
+
delegatedSession,
|
|
9020
|
+
delegatedEntries,
|
|
9021
|
+
spaceId,
|
|
9022
|
+
captured,
|
|
9023
|
+
expiresAt,
|
|
9024
|
+
{ siwe: prepared.siwe, signature }
|
|
9025
|
+
);
|
|
9026
|
+
const activateResult = await this.activateRoutedDelegation(delegation, graph.signal).catch((error) => {
|
|
8784
9027
|
graph.assertActive();
|
|
8785
9028
|
throw error;
|
|
8786
9029
|
});
|
|
@@ -8796,14 +9039,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8796
9039
|
{ cause: activateResult }
|
|
8797
9040
|
);
|
|
8798
9041
|
}
|
|
8799
|
-
const delegation = this.runtimeDelegationFromSession(
|
|
8800
|
-
delegatedSession,
|
|
8801
|
-
delegatedEntries,
|
|
8802
|
-
spaceId,
|
|
8803
|
-
captured,
|
|
8804
|
-
expiresAt,
|
|
8805
|
-
{ siwe: prepared.siwe, signature }
|
|
8806
|
-
);
|
|
8807
9042
|
graph.assertActive();
|
|
8808
9043
|
assertCapturedKey?.();
|
|
8809
9044
|
this.runtimePermissionGrants.push({
|
|
@@ -8948,7 +9183,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8948
9183
|
if (!publicSpaceId) {
|
|
8949
9184
|
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
8950
9185
|
}
|
|
8951
|
-
await this.assertMayCreateSpace();
|
|
9186
|
+
await this.assertMayCreateSpace(publicSpaceId);
|
|
8952
9187
|
await this.auth.hostPublicSpace(publicSpaceId);
|
|
8953
9188
|
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
9189
|
const abilities = { kv: { "": kvActions } };
|
|
@@ -8971,10 +9206,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8971
9206
|
...prepared,
|
|
8972
9207
|
signature
|
|
8973
9208
|
});
|
|
8974
|
-
const activateResult = await (
|
|
8975
|
-
this.config.host,
|
|
8976
|
-
delegationSession.delegationHeader
|
|
8977
|
-
);
|
|
9209
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
8978
9210
|
if (!activateResult.success) {
|
|
8979
9211
|
throw Object.assign(
|
|
8980
9212
|
new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -9524,10 +9756,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9524
9756
|
if (onPrepared) {
|
|
9525
9757
|
await onPrepared(delegation);
|
|
9526
9758
|
}
|
|
9527
|
-
const activateResult = await (
|
|
9528
|
-
this.config.host,
|
|
9529
|
-
delegationHeader
|
|
9530
|
-
);
|
|
9759
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9531
9760
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9532
9761
|
return delegation;
|
|
9533
9762
|
}
|
|
@@ -9561,10 +9790,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9561
9790
|
if (onPrepared) {
|
|
9562
9791
|
await onPrepared(delegation);
|
|
9563
9792
|
}
|
|
9564
|
-
const activateResult = await (
|
|
9565
|
-
targetHost,
|
|
9566
|
-
delegationHeader
|
|
9567
|
-
);
|
|
9793
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9568
9794
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9569
9795
|
return delegation;
|
|
9570
9796
|
}
|
|
@@ -10216,10 +10442,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10216
10442
|
...prepared,
|
|
10217
10443
|
signature
|
|
10218
10444
|
});
|
|
10219
|
-
const activateResult = await (
|
|
10220
|
-
this.config.host,
|
|
10221
|
-
delegationSession.delegationHeader
|
|
10222
|
-
);
|
|
10445
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
10223
10446
|
if (!activateResult.success) {
|
|
10224
10447
|
throw Object.assign(
|
|
10225
10448
|
new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10264,10 +10487,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10264
10487
|
...publicPrepared,
|
|
10265
10488
|
signature: publicSignature
|
|
10266
10489
|
});
|
|
10267
|
-
const publicActivateResult = await (
|
|
10268
|
-
this.config.host,
|
|
10269
|
-
publicSession.delegationHeader
|
|
10270
|
-
);
|
|
10490
|
+
const publicActivateResult = await this.activateSignedSession(publicSession, publicPrepared.siwe);
|
|
10271
10491
|
if (publicActivateResult.success) {
|
|
10272
10492
|
result.publicDelegation = {
|
|
10273
10493
|
cid: publicSession.delegationCid,
|
|
@@ -10373,11 +10593,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10373
10593
|
...prepared,
|
|
10374
10594
|
signature
|
|
10375
10595
|
});
|
|
10376
|
-
const activateResult = await (
|
|
10377
|
-
targetHost,
|
|
10378
|
-
invokerSession.delegationHeader,
|
|
10379
|
-
graph.signal
|
|
10380
|
-
).catch((error) => {
|
|
10596
|
+
const activateResult = await this.activateSignedSession(invokerSession, prepared.siwe, targetHost, graph.signal).catch((error) => {
|
|
10381
10597
|
graph.assertActive();
|
|
10382
10598
|
throw error;
|
|
10383
10599
|
});
|
|
@@ -10504,10 +10720,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10504
10720
|
...prepared,
|
|
10505
10721
|
signature
|
|
10506
10722
|
});
|
|
10507
|
-
const activateResult = await (
|
|
10508
|
-
targetHost,
|
|
10509
|
-
subDelegationSession.delegationHeader
|
|
10510
|
-
);
|
|
10723
|
+
const activateResult = await this.activateSignedSession(subDelegationSession, prepared.siwe, targetHost);
|
|
10511
10724
|
if (!activateResult.success) {
|
|
10512
10725
|
throw Object.assign(
|
|
10513
10726
|
new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10587,7 +10800,10 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
|
|
|
10587
10800
|
KVService,
|
|
10588
10801
|
ManifestValidationError,
|
|
10589
10802
|
MemorySessionStorage,
|
|
10803
|
+
NodeIdentityMismatchError,
|
|
10804
|
+
NodeNotRespondingError,
|
|
10590
10805
|
NodeUserAuthorization,
|
|
10806
|
+
PartialNodeWriteError,
|
|
10591
10807
|
PermissionNotInManifestError,
|
|
10592
10808
|
PrefixedKVService,
|
|
10593
10809
|
ProtocolMismatchError,
|