@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.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
2
|
import { TinyCloud as TinyCloud2 } from "@tinycloud/sdk-core";
|
|
3
|
+
import { NodeNotRespondingError as NodeNotRespondingError3, NodeIdentityMismatchError as NodeIdentityMismatchError2, PartialNodeWriteError as PartialNodeWriteError3 } from "@tinycloud/sdk-services";
|
|
3
4
|
|
|
4
5
|
// src/replication/module-registry.ts
|
|
5
6
|
var nodeLoaders;
|
|
@@ -259,11 +260,16 @@ import {
|
|
|
259
260
|
principalDidEquals,
|
|
260
261
|
resourceCapabilitiesToAbilitiesMap,
|
|
261
262
|
resourceCapabilitiesToSpaceAbilitiesMap,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
resolveRegistryResource,
|
|
264
|
+
warnRegistryDefaultNode,
|
|
265
|
+
parseSpaceUri,
|
|
266
|
+
NodeNotFoundError,
|
|
267
|
+
RegistryUnavailableError,
|
|
268
|
+
discoverLocalTinyCloudNode,
|
|
269
|
+
exactHttpOrigin,
|
|
265
270
|
assertSpaceCreationAllowed,
|
|
266
271
|
createInMemoryRegistrySequenceStore,
|
|
272
|
+
createInMemoryRegistryCacheStore,
|
|
267
273
|
registryOwnerSubjectV1,
|
|
268
274
|
EXPIRY,
|
|
269
275
|
assertExpiryWithinTechnicalLimit,
|
|
@@ -286,7 +292,13 @@ import {
|
|
|
286
292
|
ENCRYPTION
|
|
287
293
|
} from "@tinycloud/sdk-core";
|
|
288
294
|
import { parseEncryptionResource } from "@tinycloud/sdk-services/encryption/resource";
|
|
289
|
-
import {
|
|
295
|
+
import {
|
|
296
|
+
parseNetworkId,
|
|
297
|
+
verifyResourceDestination,
|
|
298
|
+
NodeNotRespondingError,
|
|
299
|
+
PartialNodeWriteError,
|
|
300
|
+
routingFailure
|
|
301
|
+
} from "@tinycloud/sdk-services";
|
|
290
302
|
|
|
291
303
|
// src/authorization/appKeyHostBinding.ts
|
|
292
304
|
var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -541,11 +553,12 @@ var NodeUserAuthorization = class {
|
|
|
541
553
|
this.tinycloudHosts = config.tinycloudHosts;
|
|
542
554
|
this.tinycloudRegistryUrl = config.tinycloudRegistryUrl;
|
|
543
555
|
this.tinycloudFallbackHosts = config.tinycloudFallbackHosts;
|
|
544
|
-
this.registryPolicy = config.registryPolicy;
|
|
545
556
|
this.newUserHost = config.newUserHost;
|
|
546
557
|
this.registryChainId = config.registryChainId;
|
|
547
558
|
this.registrySequenceStore = config.registrySequenceStore ?? createInMemoryRegistrySequenceStore();
|
|
559
|
+
this.registryCacheStore = config.registryCacheStore ?? createInMemoryRegistryCacheStore();
|
|
548
560
|
this.onNodeNotFound = config.onNodeNotFound;
|
|
561
|
+
this.onRegistryFallback = config.onRegistryFallback;
|
|
549
562
|
this.autoDiscoverLocalNode = config.autoDiscoverLocalNode;
|
|
550
563
|
this.localNodeUrl = config.localNodeUrl;
|
|
551
564
|
this.localLinkName = config.localLinkName;
|
|
@@ -613,13 +626,8 @@ var NodeUserAuthorization = class {
|
|
|
613
626
|
* undefined for missing SIWEs and the SDK then treats the session as
|
|
614
627
|
* expired-at-epoch-zero.
|
|
615
628
|
*
|
|
616
|
-
* @param hosts -
|
|
617
|
-
*
|
|
618
|
-
* present (and non-empty) they are adopted directly so the restored
|
|
619
|
-
* session resolves to the same node as the original sign-in without
|
|
620
|
-
* re-running registry/fallback resolution. When absent (old session)
|
|
621
|
-
* hosts are resolved lazily on the first host-needing call via
|
|
622
|
-
* {@link ensureTinyCloudHosts}.
|
|
629
|
+
* @param hosts - Persisted host hints for genuine no-entry onboarding.
|
|
630
|
+
* Current signed registry membership always takes precedence.
|
|
623
631
|
*/
|
|
624
632
|
setRestoredTinyCloudSession(session, hosts) {
|
|
625
633
|
const address = canonicalizeAddress(session.address);
|
|
@@ -681,89 +689,84 @@ var NodeUserAuthorization = class {
|
|
|
681
689
|
signature: session.signature
|
|
682
690
|
};
|
|
683
691
|
}
|
|
684
|
-
/**
|
|
685
|
-
* Ensure `tinycloudHosts` are resolved before a host-needing call.
|
|
686
|
-
*
|
|
687
|
-
* Fresh sign-in resolves hosts up front; a restored session may not have
|
|
688
|
-
* (old persisted sessions predate {@link PersistedSessionData.tinycloudHosts}).
|
|
689
|
-
* This guard makes a restored session resolve the node exactly like a fresh
|
|
690
|
-
* sign-in — persisted hosts are preferred (already set by
|
|
691
|
-
* {@link setRestoredTinyCloudSession}), otherwise the registry/fallback
|
|
692
|
-
* resolution runs lazily here. Idempotent: {@link resolveTinyCloudHostsForSignIn}
|
|
693
|
-
* early-returns when hosts are already set.
|
|
694
|
-
*
|
|
695
|
-
* Throws if hosts are unset and the restored session has no address/chainId
|
|
696
|
-
* to resolve from — a real failure that must surface, not be masked.
|
|
697
|
-
*/
|
|
692
|
+
/** Refresh the primary space's registry destinations, including after restore. */
|
|
698
693
|
async ensureTinyCloudHosts() {
|
|
699
|
-
if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
694
|
if (this._address === void 0 || this._chainId === void 0) {
|
|
703
|
-
throw new Error(
|
|
704
|
-
|
|
705
|
-
|
|
695
|
+
throw new Error("Cannot resolve TinyCloud hosts: sign in or restore a session first.");
|
|
696
|
+
}
|
|
697
|
+
await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId, this._tinyCloudSession?.spaceId);
|
|
698
|
+
}
|
|
699
|
+
async resolveTinyCloudHostsForSignIn(address, chainId, restoredSpaceId) {
|
|
700
|
+
const spaceId = restoredSpaceId ?? this.resolveSignInCapabilities(address, chainId).spaceId;
|
|
701
|
+
const destinations = await this.resolveResourceDestinations(spaceId, "read");
|
|
702
|
+
for (const destination of destinations) await verifyResourceDestination(destination, globalThis.fetch.bind(globalThis));
|
|
703
|
+
this.tinycloudHosts = destinations.map(({ url }) => url);
|
|
704
|
+
}
|
|
705
|
+
/** Current full-resource registry routing; saved hosts are only no-entry hints. */
|
|
706
|
+
async resolveResourceDestinations(resource, access, noEntryHosts = this.tinycloudHosts ?? []) {
|
|
707
|
+
const owner = resource.startsWith("urn:tinycloud:encryption:") ? parseNetworkId(resource.split("/")[0]).ownerDid : parseSpaceUri(resource)?.owner ?? (() => {
|
|
708
|
+
const key = /^tinycloud:key:(z[^:]+):[^/]+$/.exec(resource);
|
|
709
|
+
return key ? `did:key:${key[1]}` : void 0;
|
|
710
|
+
})();
|
|
711
|
+
if (!owner) throw new Error(`Cannot determine registry owner for ${resource}`);
|
|
712
|
+
const subject = owner.startsWith("did:pkh:") ? registryOwnerSubjectV1(owner.slice(owner.lastIndexOf(":") + 1), this.registryChainId) : owner;
|
|
713
|
+
for (let attempt = 1; ; attempt++) {
|
|
714
|
+
try {
|
|
715
|
+
const resolved = await resolveRegistryResource(subject, resource, access, {
|
|
716
|
+
registryUrl: this.tinycloudRegistryUrl,
|
|
717
|
+
registryChainId: this.registryChainId,
|
|
718
|
+
sequenceStore: this.registrySequenceStore,
|
|
719
|
+
cacheStore: this.registryCacheStore,
|
|
720
|
+
onRegistryFallback: this.onRegistryFallback
|
|
721
|
+
});
|
|
722
|
+
this.registryVerdict = resolved.verdict;
|
|
723
|
+
if (resolved.verdict.status === "unregistered") {
|
|
724
|
+
if (noEntryHosts.length === 0 && (this.autoDiscoverLocalNode ?? true)) {
|
|
725
|
+
const local = await discoverLocalTinyCloudNode({
|
|
726
|
+
localNodeUrl: this.localNodeUrl,
|
|
727
|
+
localLinkName: this.localLinkName,
|
|
728
|
+
expectedNodeDid: this.expectedNodeDid,
|
|
729
|
+
identityStore: this.localNodeIdentityStore
|
|
730
|
+
});
|
|
731
|
+
if (local) {
|
|
732
|
+
warnRegistryDefaultNode(resolved.verdict, this.registryCacheStore, this.onRegistryFallback);
|
|
733
|
+
return [{ url: local.url }];
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const host = noEntryHosts[0] ?? this.newUserHost ?? (this.tinycloudFallbackHosts === void 0 ? "https://node.tinycloud.xyz" : this.tinycloudFallbackHosts?.[0]);
|
|
737
|
+
if (!host || this.newUserHost === null && noEntryHosts.length === 0) throw new NodeNotFoundError(subject, resolved.verdict);
|
|
738
|
+
const url = exactHttpOrigin(host, "new-user host");
|
|
739
|
+
warnRegistryDefaultNode(resolved.verdict, this.registryCacheStore, this.onRegistryFallback);
|
|
740
|
+
return [{ url }];
|
|
741
|
+
}
|
|
742
|
+
const destinations = resolved.hosts.flatMap((host) => host.urls.map((url) => ({ url, nodeDid: host.nodeDid })));
|
|
743
|
+
return destinations;
|
|
744
|
+
} catch (error) {
|
|
745
|
+
if (!(error instanceof NodeNotFoundError) && !(error instanceof RegistryUnavailableError)) throw error;
|
|
746
|
+
if (!this.onNodeNotFound) throw error;
|
|
747
|
+
const decision = await this.onNodeNotFound({ error, attempt });
|
|
748
|
+
if (decision.action === "retry") continue;
|
|
749
|
+
if (decision.action === "use-host" && error.verdict?.status === "unregistered") {
|
|
750
|
+
const url = exactHttpOrigin(decision.host, "new-user host");
|
|
751
|
+
warnRegistryDefaultNode(error.verdict, this.registryCacheStore, this.onRegistryFallback);
|
|
752
|
+
return [{ url }];
|
|
753
|
+
}
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
706
756
|
}
|
|
707
|
-
await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId);
|
|
708
757
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
if (this.registryPolicy === "fail-closed") {
|
|
714
|
-
const resolved2 = await resolveFailClosedTinyCloudHosts(address, {
|
|
715
|
-
registryUrl: this.tinycloudRegistryUrl,
|
|
716
|
-
registryChainId: this.registryChainId,
|
|
717
|
-
newUserHost: this.newUserHost,
|
|
718
|
-
sequenceStore: this.registrySequenceStore,
|
|
719
|
-
onNodeNotFound: this.onNodeNotFound,
|
|
720
|
-
autoDiscoverLocalNode: this.autoDiscoverLocalNode,
|
|
721
|
-
localNodeUrl: this.localNodeUrl,
|
|
722
|
-
localLinkName: this.localLinkName,
|
|
723
|
-
expectedNodeDid: this.expectedNodeDid,
|
|
724
|
-
localNodeIdentityStore: this.localNodeIdentityStore
|
|
725
|
-
});
|
|
726
|
-
this.tinycloudHosts = resolved2.hosts;
|
|
727
|
-
this.registryVerdict = resolved2.verdict;
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
const subject = pkhDid(address, chainId);
|
|
731
|
-
const resolved = await resolveTinyCloudHosts(subject, {
|
|
732
|
-
registryUrl: this.tinycloudRegistryUrl,
|
|
733
|
-
fallbackHosts: this.tinycloudFallbackHosts,
|
|
734
|
-
autoDiscoverLocalNode: this.autoDiscoverLocalNode,
|
|
735
|
-
localNodeUrl: this.localNodeUrl,
|
|
736
|
-
localLinkName: this.localLinkName,
|
|
737
|
-
expectedNodeDid: this.expectedNodeDid,
|
|
738
|
-
localNodeIdentityStore: this.localNodeIdentityStore
|
|
739
|
-
});
|
|
740
|
-
this.tinycloudHosts = resolved.hosts;
|
|
758
|
+
get registryRecordPresent() {
|
|
759
|
+
return this.registryVerdict?.status === "registered";
|
|
741
760
|
}
|
|
742
|
-
/**
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
*/
|
|
751
|
-
async assertMayCreateSpace() {
|
|
752
|
-
if (this.registryPolicy !== "fail-closed") return;
|
|
753
|
-
if (this._address === void 0) {
|
|
754
|
-
throw new Error("Must be signed in to create a space");
|
|
761
|
+
/** Refresh membership before hosting; a saved no-entry verdict never authorizes a later creation. */
|
|
762
|
+
async assertMayCreateSpace(spaceId = this._tinyCloudSession?.spaceId, host = this.primaryTinyCloudHost) {
|
|
763
|
+
if (!spaceId) throw new Error("Must be signed in to create a space");
|
|
764
|
+
const destinations = await this.resolveResourceDestinations(spaceId, "host");
|
|
765
|
+
if (!destinations.some((destination) => destination.url === host)) {
|
|
766
|
+
const subject = parseSpaceUri(spaceId)?.owner;
|
|
767
|
+
if (!subject || !this.registryVerdict) throw new Error("Cannot resolve space owner");
|
|
768
|
+
assertSpaceCreationAllowed(this.registryVerdict, host, spaceId);
|
|
755
769
|
}
|
|
756
|
-
await this.ensureTinyCloudHosts();
|
|
757
|
-
const subject = registryOwnerSubjectV1(this._address, this.registryChainId);
|
|
758
|
-
let verdict = this.registryVerdict;
|
|
759
|
-
if (verdict?.subject !== subject || verdict.status === "unavailable") {
|
|
760
|
-
verdict = await lookupOwnerRegistryVerdict(subject, {
|
|
761
|
-
registryUrl: this.tinycloudRegistryUrl,
|
|
762
|
-
sequenceStore: this.registrySequenceStore
|
|
763
|
-
});
|
|
764
|
-
this.registryVerdict = verdict;
|
|
765
|
-
}
|
|
766
|
-
assertSpaceCreationAllowed(verdict, this.primaryTinyCloudHost);
|
|
767
770
|
}
|
|
768
771
|
requireTinyCloudHosts() {
|
|
769
772
|
if (!this.tinycloudHosts || this.tinycloudHosts.length === 0) {
|
|
@@ -1209,6 +1212,31 @@ var NodeUserAuthorization = class {
|
|
|
1209
1212
|
checkCurrent();
|
|
1210
1213
|
const host = context?.host ?? this.primaryTinyCloudHost;
|
|
1211
1214
|
const spaceId = context?.spaceId ?? (typeof targetSpaceId === "string" ? targetSpaceId : this._tinyCloudSession.spaceId);
|
|
1215
|
+
const destinations = await this.resolveResourceDestinations(spaceId, "host");
|
|
1216
|
+
if (this.registryVerdict?.status === "registered") {
|
|
1217
|
+
const results = [];
|
|
1218
|
+
for (const destination of destinations) {
|
|
1219
|
+
checkCurrent();
|
|
1220
|
+
try {
|
|
1221
|
+
await verifyResourceDestination(destination, globalThis.fetch.bind(globalThis));
|
|
1222
|
+
const result = await activateSessionWithHost(destination.url, this._tinyCloudSession.delegationHeader, signal);
|
|
1223
|
+
results.push(result.skipped?.includes(spaceId) ? { ...result, success: false, status: 403, error: "The session does not activate the requested space" } : result);
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
if (routingFailure(error) || signal?.aborted) throw error;
|
|
1226
|
+
throw new NodeNotRespondingError([destination.url], error);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const failed = results.flatMap((result, index) => result.success ? [] : [{ url: destinations[index].url, status: result.status }]);
|
|
1230
|
+
if (failed.length) {
|
|
1231
|
+
if (destinations.length === 1) return results[0];
|
|
1232
|
+
throw new PartialNodeWriteError(destinations.filter((_, index) => results[index].success).map(({ url }) => url), failed);
|
|
1233
|
+
}
|
|
1234
|
+
return {
|
|
1235
|
+
...results[0],
|
|
1236
|
+
activated: [...new Set(results.flatMap((result) => result.activated ?? []))],
|
|
1237
|
+
skipped: [...new Set(results.flatMap((result) => result.skipped ?? []))]
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1212
1240
|
const peerId = await fetchPeerId(host, spaceId, signal);
|
|
1213
1241
|
checkCurrent();
|
|
1214
1242
|
const siwe = this.wasm.generateHostSIWEMessage(
|
|
@@ -1279,13 +1307,45 @@ var NodeUserAuthorization = class {
|
|
|
1279
1307
|
if (!this._tinyCloudSession) {
|
|
1280
1308
|
throw new Error("Must be signed in to ensure space exists");
|
|
1281
1309
|
}
|
|
1282
|
-
await this.ensureTinyCloudHosts();
|
|
1283
|
-
const host = this.primaryTinyCloudHost;
|
|
1284
1310
|
const primarySpaceId = this._tinyCloudSession.spaceId;
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
)
|
|
1311
|
+
const primary = await this.resolveResourceDestinations(primarySpaceId, "read");
|
|
1312
|
+
this.tinycloudHosts = primary.map(({ url }) => url);
|
|
1313
|
+
const targets = new Map(primary.map((destination) => [destination.url, destination]));
|
|
1314
|
+
if (this.registryVerdict?.status === "registered" && this.registryVerdict.record) {
|
|
1315
|
+
const record = this.registryVerdict.record;
|
|
1316
|
+
const resources = Object.keys(extractRecapAttenuations(this._tinyCloudSession.siwe ?? ""));
|
|
1317
|
+
for (const space of record.spaces) {
|
|
1318
|
+
const registeredSpace = parseSpaceUri(space.space);
|
|
1319
|
+
if (!resources.some((resource) => {
|
|
1320
|
+
const fullSpace = resource.split("/")[0];
|
|
1321
|
+
const requestedSpace = parseSpaceUri(fullSpace);
|
|
1322
|
+
return fullSpace === space.space || registeredSpace !== null && requestedSpace !== null && registeredSpace.owner === requestedSpace.owner && registeredSpace.name === requestedSpace.name;
|
|
1323
|
+
})) continue;
|
|
1324
|
+
for (const membership of space.nodes) {
|
|
1325
|
+
const node = record.nodes.find((candidate) => candidate.did === membership.did);
|
|
1326
|
+
targets.set(node.url, { url: node.url, nodeDid: node.did });
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (record.encryptionNode && resources.some((resource) => resource.startsWith("urn:tinycloud:encryption:"))) {
|
|
1330
|
+
const node = record.nodes.find((candidate) => candidate.did === record.encryptionNode);
|
|
1331
|
+
targets.set(node.url, { url: node.url, nodeDid: node.did });
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
let result;
|
|
1335
|
+
for (const destination of targets.values()) {
|
|
1336
|
+
await verifyResourceDestination(destination, globalThis.fetch.bind(globalThis));
|
|
1337
|
+
let activated;
|
|
1338
|
+
try {
|
|
1339
|
+
activated = await activateSessionWithHost(destination.url, this._tinyCloudSession.delegationHeader);
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
throw new NodeNotRespondingError([destination.url], error);
|
|
1342
|
+
}
|
|
1343
|
+
if (!activated.success && activated.status !== 404) {
|
|
1344
|
+
throw Object.assign(new Error(`Failed to activate session: ${describeHostFailure(activated)}`), { cause: activated });
|
|
1345
|
+
}
|
|
1346
|
+
if (destination.url === primary[0].url) result = activated;
|
|
1347
|
+
}
|
|
1348
|
+
if (!result) throw new Error("Primary space activation did not return a result");
|
|
1289
1349
|
this.recordActivationSkippedSpaces(result, primarySpaceId);
|
|
1290
1350
|
if (!result.success && result.status !== 404) {
|
|
1291
1351
|
throw Object.assign(
|
|
@@ -1333,7 +1393,7 @@ var NodeUserAuthorization = class {
|
|
|
1333
1393
|
/** {@link assertMayCreateSpace}, reporting a refusal to the creation handler before rethrowing it. */
|
|
1334
1394
|
async guardSpaceCreation(handler, context) {
|
|
1335
1395
|
try {
|
|
1336
|
-
await this.assertMayCreateSpace();
|
|
1396
|
+
await this.assertMayCreateSpace(context.spaceId, context.host);
|
|
1337
1397
|
} catch (error) {
|
|
1338
1398
|
const failure = error instanceof Error ? error : new Error(String(error));
|
|
1339
1399
|
handler.onSpaceCreationFailed?.(context, failure);
|
|
@@ -2487,6 +2547,7 @@ import {
|
|
|
2487
2547
|
decodeBase64Url,
|
|
2488
2548
|
principalDidEquals as principalDidEquals2
|
|
2489
2549
|
} from "@tinycloud/sdk-core";
|
|
2550
|
+
import { routingFailure as routingFailure2 } from "@tinycloud/sdk-services";
|
|
2490
2551
|
var RuntimeDelegationRejectedError = class extends Error {
|
|
2491
2552
|
constructor(reason, message) {
|
|
2492
2553
|
super(message);
|
|
@@ -2513,7 +2574,8 @@ var SERVICE_BY_SHORT_NAME = {
|
|
|
2513
2574
|
kv: "tinycloud.kv",
|
|
2514
2575
|
sql: "tinycloud.sql",
|
|
2515
2576
|
duckdb: "tinycloud.duckdb",
|
|
2516
|
-
hooks: "tinycloud.hooks"
|
|
2577
|
+
hooks: "tinycloud.hooks",
|
|
2578
|
+
capabilities: "tinycloud.capabilities"
|
|
2517
2579
|
};
|
|
2518
2580
|
function authorizationWithoutBearer(authorization) {
|
|
2519
2581
|
return authorization.replace(/^Bearer /i, "");
|
|
@@ -2644,8 +2706,8 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
2644
2706
|
));
|
|
2645
2707
|
continue;
|
|
2646
2708
|
}
|
|
2647
|
-
const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/(
|
|
2648
|
-
if (!match) {
|
|
2709
|
+
const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/([^/]+)(?:\/|$)(.*)$/.exec(resource);
|
|
2710
|
+
if (!match || !Object.prototype.hasOwnProperty.call(SERVICE_BY_SHORT_NAME, match[3])) {
|
|
2649
2711
|
throw new Error(
|
|
2650
2712
|
`Validated runtime delegation has an unsupported signed resource '${resource}'.`
|
|
2651
2713
|
);
|
|
@@ -2790,13 +2852,8 @@ function resourcesMatch(actual, expected) {
|
|
|
2790
2852
|
return JSON.stringify(actual.map(comparable)) === JSON.stringify(expected.map(comparable));
|
|
2791
2853
|
}
|
|
2792
2854
|
async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
reject(
|
|
2796
|
-
"host_mismatch",
|
|
2797
|
-
`Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
|
|
2798
|
-
);
|
|
2799
|
-
}
|
|
2855
|
+
let host = normalizedHost(options.host);
|
|
2856
|
+
const differentHost = delegation.host !== void 0 && normalizedHost(delegation.host) !== host;
|
|
2800
2857
|
if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
|
|
2801
2858
|
reject("authority_invalid", "Runtime delegation has an invalid expiry.");
|
|
2802
2859
|
}
|
|
@@ -2840,6 +2897,13 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2840
2897
|
if (options.authorize !== void 0 && !options.authorize(signed.permissions)) {
|
|
2841
2898
|
throw new Error("Runtime delegation is not authorized for this runtime.");
|
|
2842
2899
|
}
|
|
2900
|
+
if (differentHost) {
|
|
2901
|
+
const registeredHost = await node.resolveRegisteredDelegationHost?.(signed.permissions, host);
|
|
2902
|
+
if (registeredHost === void 0) {
|
|
2903
|
+
reject("host_mismatch", `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`);
|
|
2904
|
+
}
|
|
2905
|
+
host = normalizedHost(registeredHost);
|
|
2906
|
+
}
|
|
2843
2907
|
const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
|
|
2844
2908
|
if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
|
|
2845
2909
|
reject(
|
|
@@ -2883,6 +2947,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2883
2947
|
await node.useRuntimeDelegation(installedCandidate);
|
|
2884
2948
|
}
|
|
2885
2949
|
} catch (error) {
|
|
2950
|
+
if (routingFailure2(error)) throw error;
|
|
2886
2951
|
reject(
|
|
2887
2952
|
activationRejectionReason(error),
|
|
2888
2953
|
error instanceof Error ? error.message : String(error)
|
|
@@ -2901,6 +2966,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2901
2966
|
"Runtime delegation activation installed authority that differs from the validated authority."
|
|
2902
2967
|
);
|
|
2903
2968
|
}
|
|
2969
|
+
node.refreshRegistryForCapabilities?.(signed.permissions);
|
|
2904
2970
|
return {
|
|
2905
2971
|
cid,
|
|
2906
2972
|
delegation: installed,
|
|
@@ -2964,6 +3030,15 @@ function deserializeDelegation(data) {
|
|
|
2964
3030
|
}
|
|
2965
3031
|
|
|
2966
3032
|
// src/TinyCloudNode.ts
|
|
3033
|
+
import {
|
|
3034
|
+
createResourceRoutingFetch,
|
|
3035
|
+
verifyResourceDestination as verifyResourceDestination2,
|
|
3036
|
+
NodeNotRespondingError as NodeNotRespondingError2,
|
|
3037
|
+
NodeIdentityMismatchError,
|
|
3038
|
+
PartialNodeWriteError as PartialNodeWriteError2,
|
|
3039
|
+
ResourceNotRoutedError,
|
|
3040
|
+
routingFailure as routingFailure3
|
|
3041
|
+
} from "@tinycloud/sdk-services";
|
|
2967
3042
|
import { generateNonce, SiweMessage as SiweMessage3 } from "siwe";
|
|
2968
3043
|
import { didKeyFromEd25519PublicKey } from "@tinycloud/share-envelope";
|
|
2969
3044
|
import {
|
|
@@ -3023,9 +3098,15 @@ import {
|
|
|
3023
3098
|
verifyDidKeyEd25519Signature,
|
|
3024
3099
|
canonicalizeAddress as canonicalizeAddress2,
|
|
3025
3100
|
pkhDid as pkhDid2,
|
|
3026
|
-
|
|
3027
|
-
|
|
3101
|
+
parseSpaceUri as parseSpaceUri2,
|
|
3102
|
+
resolveRegistryResource as resolveRegistryResource2,
|
|
3103
|
+
warnRegistryDefaultNode as warnRegistryDefaultNode2,
|
|
3104
|
+
NodeNotFoundError as NodeNotFoundError2,
|
|
3105
|
+
exactHttpOrigin as exactHttpOrigin2,
|
|
3028
3106
|
createInMemoryRegistrySequenceStore as createInMemoryRegistrySequenceStore2,
|
|
3107
|
+
createInMemoryRegistryCacheStore as createInMemoryRegistryCacheStore2,
|
|
3108
|
+
refreshOwnerRegistry,
|
|
3109
|
+
registryOwnerKeyV2,
|
|
3029
3110
|
publishLocationRecord,
|
|
3030
3111
|
principalDidEquals as principalDidEquals3,
|
|
3031
3112
|
parseNetworkId as parseNetworkId2,
|
|
@@ -4043,6 +4124,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4043
4124
|
* ```
|
|
4044
4125
|
*/
|
|
4045
4126
|
constructor(config = {}) {
|
|
4127
|
+
this.registryRefreshOwners = /* @__PURE__ */ new Set();
|
|
4046
4128
|
this.signer = null;
|
|
4047
4129
|
this.auth = null;
|
|
4048
4130
|
this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
|
|
@@ -4206,8 +4288,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4206
4288
|
this.config = {
|
|
4207
4289
|
...config,
|
|
4208
4290
|
host: config.host ?? DEFAULT_HOST,
|
|
4291
|
+
registryPolicy: "fail-closed",
|
|
4209
4292
|
// One store for every authorization this client creates.
|
|
4210
|
-
registrySequenceStore: config.registrySequenceStore ?? createInMemoryRegistrySequenceStore2()
|
|
4293
|
+
registrySequenceStore: config.registrySequenceStore ?? createInMemoryRegistrySequenceStore2(),
|
|
4294
|
+
registryCacheStore: config.registryCacheStore ?? createInMemoryRegistryCacheStore2()
|
|
4211
4295
|
};
|
|
4212
4296
|
if (this.config.replication?.enabled) {
|
|
4213
4297
|
const runtime = getNodeReplicationLoaders().runtime().then(({ ReplicationRuntime }) => {
|
|
@@ -4511,7 +4595,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4511
4595
|
// A session saved but never registered on this node is refused: it registers once.
|
|
4512
4596
|
activate: (signal) => {
|
|
4513
4597
|
assertCurrent();
|
|
4514
|
-
|
|
4598
|
+
const activationSignal = AbortSignal.any([signal, graph.signal]);
|
|
4599
|
+
if (primary && primary.delegationCid === session.delegationCid) {
|
|
4600
|
+
return this.activateSignedSession(primary, primary.siwe, host, activationSignal);
|
|
4601
|
+
}
|
|
4602
|
+
return this.activateResourceSession(session.delegationHeader, [spaceId], host, activationSignal);
|
|
4515
4603
|
}
|
|
4516
4604
|
});
|
|
4517
4605
|
assertCurrent();
|
|
@@ -4647,14 +4735,38 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4647
4735
|
get requests() {
|
|
4648
4736
|
return this._requestLog === void 0 ? void 0 : readOnlyRequestLog(this._requestLog);
|
|
4649
4737
|
}
|
|
4650
|
-
|
|
4738
|
+
throwRegistryRefusal(status, text) {
|
|
4739
|
+
if (status !== 403 && status !== 503 || !text.startsWith("registry_unavailable:") && !text.startsWith("registry_hosting_denied:")) return;
|
|
4740
|
+
const code = status === 503 ? "registry_unavailable" : "node_registry_entry_exists";
|
|
4741
|
+
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 } });
|
|
4742
|
+
}
|
|
4743
|
+
createServiceGraphLifetime(session) {
|
|
4744
|
+
const routedFetch = createResourceRoutingFetch(
|
|
4745
|
+
async (url, init) => {
|
|
4746
|
+
try {
|
|
4747
|
+
const response = await globalThis.fetch(url, init);
|
|
4748
|
+
if (response.status === 403 || response.status === 503) {
|
|
4749
|
+
this.throwRegistryRefusal(response.status, await response.clone().text());
|
|
4750
|
+
}
|
|
4751
|
+
return response;
|
|
4752
|
+
} catch (error) {
|
|
4753
|
+
if (routingFailure3(error)) throw error;
|
|
4754
|
+
if (new URL(url).pathname.startsWith("/encryption/") && !init?.signal?.aborted) {
|
|
4755
|
+
throw new NodeNotRespondingError2([new URL(url).origin], error);
|
|
4756
|
+
}
|
|
4757
|
+
throw error;
|
|
4758
|
+
}
|
|
4759
|
+
},
|
|
4760
|
+
{
|
|
4761
|
+
resolveResource: (resource, access) => this.routingDestinations(resource, access),
|
|
4762
|
+
bindAudience: (authorization, nodeDid) => this.bindRoutedAudience(authorization, nodeDid, session),
|
|
4763
|
+
verifyDestination: (target) => verifyResourceDestination2(target, globalThis.fetch.bind(globalThis))
|
|
4764
|
+
}
|
|
4765
|
+
);
|
|
4651
4766
|
return new ServiceGraphLifetime(
|
|
4652
4767
|
this.invokeWithRuntimePermissions,
|
|
4653
4768
|
this.invokeAnyWithRuntimePermissions,
|
|
4654
|
-
|
|
4655
|
-
// signal while preserving the SDK's existing injectable global fetch
|
|
4656
|
-
// behavior (including adapters that install it after construction).
|
|
4657
|
-
(url, init) => globalThis.fetch(url, init),
|
|
4769
|
+
routedFetch,
|
|
4658
4770
|
(context) => {
|
|
4659
4771
|
this.storageFull.observe(context);
|
|
4660
4772
|
if (this._requestLog) observeReplicaReads(context, this._requestLog);
|
|
@@ -4662,6 +4774,75 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4662
4774
|
this._requestLog
|
|
4663
4775
|
);
|
|
4664
4776
|
}
|
|
4777
|
+
async routingDestinations(resource, access, noEntryHosts) {
|
|
4778
|
+
const session = this.currentTinyCloudSession();
|
|
4779
|
+
if (session) this.refreshRegistryOwner(pkhDid2(session.address, session.chainId));
|
|
4780
|
+
if (this.auth) return this.auth.resolveResourceDestinations(resource, access, noEntryHosts);
|
|
4781
|
+
const owner = resource.startsWith("urn:tinycloud:encryption:") ? parseNetworkId2(resource.split("/")[0]).ownerDid : parseSpaceUri2(resource)?.owner ?? (() => {
|
|
4782
|
+
const key = /^tinycloud:key:(z[^:]+):[^/]+$/.exec(resource);
|
|
4783
|
+
return key ? `did:key:${key[1]}` : void 0;
|
|
4784
|
+
})();
|
|
4785
|
+
if (!owner) throw new Error(`Cannot determine registry owner for ${resource}`);
|
|
4786
|
+
const subject = owner.startsWith("did:pkh:") ? pkhDid2(owner.slice(owner.lastIndexOf(":") + 1), this.config.registryChainId ?? 1) : owner;
|
|
4787
|
+
const resolved = await resolveRegistryResource2(subject, resource, access, {
|
|
4788
|
+
registryUrl: this.config.tinycloudRegistryUrl,
|
|
4789
|
+
registryChainId: this.config.registryChainId,
|
|
4790
|
+
sequenceStore: this.config.registrySequenceStore,
|
|
4791
|
+
cacheStore: this.config.registryCacheStore,
|
|
4792
|
+
onRegistryFallback: this.config.onRegistryFallback
|
|
4793
|
+
});
|
|
4794
|
+
if (resolved.verdict.status === "unregistered") {
|
|
4795
|
+
const hint = noEntryHosts === void 0 ? this.explicitHost ?? this.config.host : noEntryHosts[0];
|
|
4796
|
+
const host = hint ?? this.config.newUserHost ?? (this.config.tinycloudFallbackHosts === void 0 ? DEFAULT_HOST : this.config.tinycloudFallbackHosts?.[0]);
|
|
4797
|
+
if (!host || !hint && this.config.newUserHost === null) throw new NodeNotFoundError2(subject, resolved.verdict);
|
|
4798
|
+
const url = exactHttpOrigin2(host, "new-user host");
|
|
4799
|
+
warnRegistryDefaultNode2(resolved.verdict, this.config.registryCacheStore, this.config.onRegistryFallback);
|
|
4800
|
+
return [{ url }];
|
|
4801
|
+
}
|
|
4802
|
+
const destinations = resolved.hosts.flatMap((host) => host.urls.map((url) => ({ url, nodeDid: host.nodeDid })));
|
|
4803
|
+
return destinations;
|
|
4804
|
+
}
|
|
4805
|
+
/** Transport hints cannot override verified full-resource registry routing. */
|
|
4806
|
+
async resolveRegisteredDelegationHost(permissions, noEntryHost) {
|
|
4807
|
+
const resources = /* @__PURE__ */ new Map();
|
|
4808
|
+
let hasData = false;
|
|
4809
|
+
for (const permission of permissions) {
|
|
4810
|
+
const encryption = this.isEncryptionNetworkOperation(this.invocationServiceName(permission.service), permission.path, permission.space);
|
|
4811
|
+
const resource = encryption ? permission.path.split("/")[0] : permission.space;
|
|
4812
|
+
if (!resource) throw new ResourceNotRoutedError("delegation");
|
|
4813
|
+
resources.set(resource, encryption ? "encryption" : "host");
|
|
4814
|
+
hasData || (hasData = !encryption);
|
|
4815
|
+
}
|
|
4816
|
+
let dataHost;
|
|
4817
|
+
let encryptionHost;
|
|
4818
|
+
let unserved;
|
|
4819
|
+
for (const [resource, access] of resources) {
|
|
4820
|
+
try {
|
|
4821
|
+
const destinations = await this.routingDestinations(resource, access, [noEntryHost]);
|
|
4822
|
+
if (destinations.some(({ nodeDid }) => nodeDid === void 0)) return void 0;
|
|
4823
|
+
if (access === "host") dataHost ?? (dataHost = destinations[0]?.url);
|
|
4824
|
+
else encryptionHost ?? (encryptionHost = destinations[0]?.url);
|
|
4825
|
+
} catch (error) {
|
|
4826
|
+
if (!(error instanceof NodeNotFoundError2) || error.verdict.status !== "registered") throw error;
|
|
4827
|
+
unserved ?? (unserved = error);
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
const host = hasData ? dataHost : encryptionHost;
|
|
4831
|
+
if (!host) throw unserved ?? new ResourceNotRoutedError("delegation");
|
|
4832
|
+
return host;
|
|
4833
|
+
}
|
|
4834
|
+
async bindRoutedAudience(authorization, nodeDid, stagedSession) {
|
|
4835
|
+
const payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(authorization.split(".")[1])));
|
|
4836
|
+
const issuer = typeof payload === "object" && payload !== null && "iss" in payload ? payload.iss : void 0;
|
|
4837
|
+
const session = [
|
|
4838
|
+
stagedSession,
|
|
4839
|
+
this._serviceContext?.session,
|
|
4840
|
+
this.auth?.tinyCloudSession,
|
|
4841
|
+
...this.runtimePermissionGrants.map((grant) => grant.session)
|
|
4842
|
+
].find((candidate) => candidate?.verificationMethod === issuer);
|
|
4843
|
+
if (!session) throw new Error("Invocation signing session is not active");
|
|
4844
|
+
return rewriteInvocationAudience(authorization, nodeDid, session.jwk);
|
|
4845
|
+
}
|
|
4665
4846
|
syncResolvedHostFromAuth() {
|
|
4666
4847
|
const host = this.auth?.hosts[0];
|
|
4667
4848
|
if (host) {
|
|
@@ -4775,6 +4956,29 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4775
4956
|
const session = this.currentTinyCloudSession();
|
|
4776
4957
|
return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
|
|
4777
4958
|
}
|
|
4959
|
+
/** Start one background refresh for each owner derived from verified authority. */
|
|
4960
|
+
refreshRegistryForCapabilities(permissions) {
|
|
4961
|
+
for (const permission of permissions) {
|
|
4962
|
+
try {
|
|
4963
|
+
const owner = permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:") ? parseNetworkId2(permission.path.split("/")[0]).ownerDid : permission.space ? this.ownerDidFromSpaceId(permission.space) : void 0;
|
|
4964
|
+
if (owner) this.refreshRegistryOwner(owner);
|
|
4965
|
+
} catch {
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
refreshRegistryOwner(owner) {
|
|
4970
|
+
if (this.config.tinycloudRegistryUrl === null) return;
|
|
4971
|
+
const subject = owner.startsWith("did:pkh:") ? pkhDid2(owner.slice(owner.lastIndexOf(":") + 1), this.config.registryChainId ?? 1) : owner;
|
|
4972
|
+
const key = registryOwnerKeyV2(subject);
|
|
4973
|
+
if (this.registryRefreshOwners.has(key)) return;
|
|
4974
|
+
this.registryRefreshOwners.add(key);
|
|
4975
|
+
void refreshOwnerRegistry(subject, {
|
|
4976
|
+
registryUrl: this.config.tinycloudRegistryUrl,
|
|
4977
|
+
registryChainId: this.config.registryChainId,
|
|
4978
|
+
sequenceStore: this.config.registrySequenceStore,
|
|
4979
|
+
cacheStore: this.config.registryCacheStore
|
|
4980
|
+
}).catch(() => void 0);
|
|
4981
|
+
}
|
|
4778
4982
|
/** Normalize signed capabilities without collapsing nested encryption into raw authority. */
|
|
4779
4983
|
projectSignedRecapCapabilities(siwe) {
|
|
4780
4984
|
const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
|
|
@@ -5206,6 +5410,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5206
5410
|
const primarySession = this.currentTinyCloudSession();
|
|
5207
5411
|
if (primarySession) {
|
|
5208
5412
|
this.registerPrimarySessionGrant(primarySession);
|
|
5413
|
+
this.refreshRegistryOwner(pkhDid2(primarySession.address, primarySession.chainId));
|
|
5414
|
+
this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
|
|
5209
5415
|
if (this.auth?.spaceHosting === "unknown") {
|
|
5210
5416
|
await this.checkSpaceHosting();
|
|
5211
5417
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
@@ -5428,8 +5634,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5428
5634
|
const primarySession = auth.tinyCloudSession;
|
|
5429
5635
|
const defaultSpaceId = this.ownedSpaceId("default");
|
|
5430
5636
|
const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === BOOTSTRAP_SESSION_REQUESTS.default;
|
|
5431
|
-
|
|
5432
|
-
await this.assertMayCreateSpace();
|
|
5637
|
+
for (const step of steps) {
|
|
5638
|
+
if (step.kind === "host") await this.assertMayCreateSpace(step.spaceId);
|
|
5433
5639
|
}
|
|
5434
5640
|
for (const step of steps) {
|
|
5435
5641
|
if (step.kind !== "session") continue;
|
|
@@ -5478,10 +5684,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5478
5684
|
if (!session) {
|
|
5479
5685
|
throw new Error(`Missing bootstrap session for ${step.space}`);
|
|
5480
5686
|
}
|
|
5481
|
-
const
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5687
|
+
const destinations = await this.routingDestinations(step.spaceId, "host");
|
|
5688
|
+
let activated;
|
|
5689
|
+
for (const destination of destinations) {
|
|
5690
|
+
try {
|
|
5691
|
+
await verifyResourceDestination2(destination, globalThis.fetch.bind(globalThis));
|
|
5692
|
+
const result = await activateSessionWithHost2(destination.url, session.delegationHeader, graph.signal);
|
|
5693
|
+
if (!result.success) throw Object.assign(new Error("Bootstrap activation failed"), { cause: result });
|
|
5694
|
+
activated = result;
|
|
5695
|
+
} catch (error) {
|
|
5696
|
+
graph.assertActive();
|
|
5697
|
+
throw error;
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5700
|
+
if (!activated) throw new Error("Bootstrap space has no destination");
|
|
5485
5701
|
graph.assertActive();
|
|
5486
5702
|
if (!activated.success || activated.skipped?.includes(step.spaceId)) {
|
|
5487
5703
|
throw Object.assign(
|
|
@@ -6064,6 +6280,13 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6064
6280
|
if (!session) {
|
|
6065
6281
|
throw new Error("Owned space hosting requires an active session");
|
|
6066
6282
|
}
|
|
6283
|
+
await this.assertMayCreateSpace(spaceId);
|
|
6284
|
+
if (this.auth.registryRecordPresent) {
|
|
6285
|
+
const hosted = await this.hostOwnedSpaceOnce(spaceId, signal);
|
|
6286
|
+
if (!hosted.success) throw Object.assign(new Error(`Failed to activate listed space ${spaceId}: ${describeHostFailure2(hosted)}`), { cause: hosted });
|
|
6287
|
+
if (this.auth.tinyCloudSession === session) this.confirmedHostedSpaceIds.add(spaceId);
|
|
6288
|
+
return;
|
|
6289
|
+
}
|
|
6067
6290
|
if (this.confirmedHostedSpaceIds.has(spaceId) || this.hostedOwnedSpaceIds.has(spaceId)) {
|
|
6068
6291
|
return;
|
|
6069
6292
|
}
|
|
@@ -6082,7 +6305,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6082
6305
|
{ cause: activation }
|
|
6083
6306
|
);
|
|
6084
6307
|
}
|
|
6085
|
-
await this.assertMayCreateSpace();
|
|
6308
|
+
await this.assertMayCreateSpace(spaceId);
|
|
6086
6309
|
if (signal?.aborted) throw signal.reason;
|
|
6087
6310
|
const created = await this.hostOwnedSpaceOnce(spaceId, signal);
|
|
6088
6311
|
if (!created.success) {
|
|
@@ -6143,17 +6366,19 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6143
6366
|
{ cause: hosted }
|
|
6144
6367
|
);
|
|
6145
6368
|
}
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
if (!activation.success || activation.skipped?.includes(spaceId)) {
|
|
6151
|
-
throw Object.assign(
|
|
6152
|
-
new Error(
|
|
6153
|
-
`Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
|
|
6154
|
-
),
|
|
6155
|
-
{ cause: activation }
|
|
6369
|
+
if (!this.auth.registryRecordPresent) {
|
|
6370
|
+
const activation = await activateSessionWithHost2(
|
|
6371
|
+
host,
|
|
6372
|
+
session.delegationHeader
|
|
6156
6373
|
);
|
|
6374
|
+
if (!activation.success || activation.skipped?.includes(spaceId)) {
|
|
6375
|
+
throw Object.assign(
|
|
6376
|
+
new Error(
|
|
6377
|
+
`Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
|
|
6378
|
+
),
|
|
6379
|
+
{ cause: activation }
|
|
6380
|
+
);
|
|
6381
|
+
}
|
|
6157
6382
|
}
|
|
6158
6383
|
if (this.auth?.tinyCloudSession === session) this.hostedOwnedSpaceIds.add(spaceId);
|
|
6159
6384
|
try {
|
|
@@ -6197,11 +6422,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6197
6422
|
* session's recap doesn't cover it and the registry has no record; the host
|
|
6198
6423
|
* SIWE is idempotent server-side. Must be called after {@link signIn}.
|
|
6199
6424
|
*
|
|
6200
|
-
*
|
|
6201
|
-
*
|
|
6202
|
-
*
|
|
6203
|
-
* verdict. {@link hostOwnedSpace} itself is an explicit action and is not
|
|
6204
|
-
* guarded.
|
|
6425
|
+
* Current signed per-space membership is checked before both automatic and
|
|
6426
|
+
* explicit hosting. Registered replicas use the session grant; no-entry
|
|
6427
|
+
* onboarding retains the host-SIWE flow.
|
|
6205
6428
|
*
|
|
6206
6429
|
* @param name - The owned space name (e.g. `"secrets"`).
|
|
6207
6430
|
* @returns The hosted owned-space URI.
|
|
@@ -6211,27 +6434,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6211
6434
|
throw new Error("Not signed in. Call signIn() first.");
|
|
6212
6435
|
}
|
|
6213
6436
|
const spaceId = this.ownedSpaceId(name);
|
|
6437
|
+
await this.assertMayCreateSpace(spaceId);
|
|
6214
6438
|
if (this.hostedOwnedSpaceIds.has(spaceId) || await this.isOwnedSpaceRegistered(spaceId)) {
|
|
6215
6439
|
return spaceId;
|
|
6216
6440
|
}
|
|
6217
|
-
await this.assertMayCreateSpace();
|
|
6218
6441
|
return this.hostOwnedSpace(name);
|
|
6219
6442
|
}
|
|
6220
|
-
/**
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
* {@link ensurePublicSpace}. Only `registryPolicy: "fail-closed"` consults
|
|
6224
|
-
* the registry ({@link NodeUserAuthorization.assertMayCreateSpace}); the
|
|
6225
|
-
* default policy performs no lookup.
|
|
6226
|
-
*/
|
|
6227
|
-
async assertMayCreateSpace() {
|
|
6228
|
-
if (this.config.registryPolicy !== "fail-closed") return;
|
|
6229
|
-
await this.auth.assertMayCreateSpace();
|
|
6443
|
+
/** Refresh full-space membership before any automatic or explicit hosting. */
|
|
6444
|
+
async assertMayCreateSpace(spaceId) {
|
|
6445
|
+
await this.routingDestinations(spaceId, "host");
|
|
6230
6446
|
}
|
|
6231
6447
|
/** Registry-policy settings shared by every authorization this client creates. */
|
|
6232
6448
|
registryPolicyConfig() {
|
|
6233
|
-
const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound } = this.config;
|
|
6234
|
-
return { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound };
|
|
6449
|
+
const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, registryCacheStore, onNodeNotFound, onRegistryFallback } = this.config;
|
|
6450
|
+
return { registryPolicy, newUserHost, registryChainId, registrySequenceStore, registryCacheStore, onNodeNotFound, onRegistryFallback };
|
|
6235
6451
|
}
|
|
6236
6452
|
/**
|
|
6237
6453
|
* Submit the host delegation for an owned space, sharing one in-flight
|
|
@@ -6437,12 +6653,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6437
6653
|
}
|
|
6438
6654
|
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
6439
6655
|
}
|
|
6440
|
-
const
|
|
6441
|
-
|
|
6442
|
-
restoredAddress,
|
|
6443
|
-
sessionData.chainId
|
|
6444
|
-
);
|
|
6445
|
-
const stagedHost = resolvedHost ?? this.config.host;
|
|
6656
|
+
const restoreHints = this.explicitHost ? [this.explicitHost] : sessionData.tinycloudHosts ?? [];
|
|
6657
|
+
const stagedHost = exactHttpOrigin2(restoreHints[0] ?? this.config.host, "restored host hint");
|
|
6446
6658
|
let restoredNodeFeatures;
|
|
6447
6659
|
let replicaProtocolMismatch = false;
|
|
6448
6660
|
if (this.config.replication?.enabled) {
|
|
@@ -6559,7 +6771,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6559
6771
|
this.sessionManager = stagedManager;
|
|
6560
6772
|
this.sessionKeyId = stagedKeyId;
|
|
6561
6773
|
this.sessionKeyJwk = stagedJwk;
|
|
6562
|
-
|
|
6774
|
+
this.config.host = stagedHost;
|
|
6563
6775
|
if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
|
|
6564
6776
|
this.storageFull.clear();
|
|
6565
6777
|
this._requestLog?.clear();
|
|
@@ -6626,6 +6838,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6626
6838
|
if (attempt === this.latestSessionAttempt) await this.endSession();
|
|
6627
6839
|
this.assertSessionEpoch(epoch, "Session restore");
|
|
6628
6840
|
}
|
|
6841
|
+
if (this.config.replication?.enabled) {
|
|
6842
|
+
if (stagedAddress !== void 0) this.refreshRegistryOwner(pkhDid2(stagedAddress, stagedChainId));
|
|
6843
|
+
this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
|
|
6844
|
+
}
|
|
6629
6845
|
}
|
|
6630
6846
|
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
6631
6847
|
if (!session?.siwe) return { cache: void 0, grants: [] };
|
|
@@ -6674,7 +6890,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6674
6890
|
};
|
|
6675
6891
|
}
|
|
6676
6892
|
stageRestoredServiceGraph(input) {
|
|
6677
|
-
const graph = this.createServiceGraphLifetime();
|
|
6893
|
+
const graph = this.createServiceGraphLifetime(input.serviceSession);
|
|
6678
6894
|
const core = this.auth ? new TinyCloud(this.auth, {
|
|
6679
6895
|
invokeAny: graph.invokeAny,
|
|
6680
6896
|
telemetry: this.config.telemetry,
|
|
@@ -6899,56 +7115,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6899
7115
|
spaceService.updateConfig({ sharingService });
|
|
6900
7116
|
return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
|
|
6901
7117
|
}
|
|
6902
|
-
/**
|
|
6903
|
-
* Resolve the host a restored session should target.
|
|
6904
|
-
*
|
|
6905
|
-
* Mirrors fresh sign-in host resolution but for the restore path:
|
|
6906
|
-
* an explicit/pinned host always wins, then the hosts the session was
|
|
6907
|
-
* persisted with, then a lazy registry/fallback resolution for sessions
|
|
6908
|
-
* that predate the persisted `tinycloudHosts` field. Returns `undefined`
|
|
6909
|
-
* only when there's nothing to resolve from (no explicit host, no
|
|
6910
|
-
* persisted hosts, and no address/chainId) — in which case the existing
|
|
6911
|
-
* `config.host` (default) is left in place.
|
|
6912
|
-
*
|
|
6913
|
-
* Resolution failures are surfaced, not swallowed: a genuinely broken
|
|
6914
|
-
* registry lookup throws rather than silently falling back to a wrong host.
|
|
6915
|
-
*/
|
|
6916
|
-
async resolveRestoredHost(persistedHosts, address, chainId) {
|
|
6917
|
-
if (this.explicitHost) {
|
|
6918
|
-
return this.explicitHost;
|
|
6919
|
-
}
|
|
6920
|
-
if (persistedHosts && persistedHosts.length > 0) {
|
|
6921
|
-
return persistedHosts[0];
|
|
6922
|
-
}
|
|
6923
|
-
if (address === void 0 || chainId === void 0) {
|
|
6924
|
-
return void 0;
|
|
6925
|
-
}
|
|
6926
|
-
if (this.config.registryPolicy === "fail-closed") {
|
|
6927
|
-
const resolved2 = await resolveFailClosedTinyCloudHosts2(address, {
|
|
6928
|
-
registryUrl: this.config.tinycloudRegistryUrl,
|
|
6929
|
-
registryChainId: this.config.registryChainId,
|
|
6930
|
-
newUserHost: this.config.newUserHost,
|
|
6931
|
-
sequenceStore: this.config.registrySequenceStore,
|
|
6932
|
-
onNodeNotFound: this.config.onNodeNotFound,
|
|
6933
|
-
autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
|
|
6934
|
-
localNodeUrl: this.config.localNodeUrl,
|
|
6935
|
-
localLinkName: this.config.localLinkName,
|
|
6936
|
-
expectedNodeDid: this.config.expectedNodeDid,
|
|
6937
|
-
localNodeIdentityStore: this.config.localNodeIdentityStore
|
|
6938
|
-
});
|
|
6939
|
-
return resolved2.hosts[0];
|
|
6940
|
-
}
|
|
6941
|
-
const resolved = await resolveTinyCloudHosts2(pkhDid2(address, chainId), {
|
|
6942
|
-
registryUrl: this.config.tinycloudRegistryUrl,
|
|
6943
|
-
fallbackHosts: this.config.tinycloudFallbackHosts,
|
|
6944
|
-
autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
|
|
6945
|
-
localNodeUrl: this.config.localNodeUrl,
|
|
6946
|
-
localLinkName: this.config.localLinkName,
|
|
6947
|
-
expectedNodeDid: this.config.expectedNodeDid,
|
|
6948
|
-
localNodeIdentityStore: this.config.localNodeIdentityStore
|
|
6949
|
-
});
|
|
6950
|
-
return resolved.hosts[0];
|
|
6951
|
-
}
|
|
6952
7118
|
/**
|
|
6953
7119
|
* Resolve the currently-active TinyCloudSession, preferring the auth
|
|
6954
7120
|
* layer's value (wallet mode) and falling back to the node-level
|
|
@@ -7423,10 +7589,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7423
7589
|
* second time. Only a value recorded for this exact host qualifies; older
|
|
7424
7590
|
* nodes that omit `nodeId` fall through to the fetch.
|
|
7425
7591
|
*/
|
|
7426
|
-
async fetchNodeId() {
|
|
7427
|
-
const cached = this.auth?.nodeIdForHost(
|
|
7592
|
+
async fetchNodeId(host = this.config.host) {
|
|
7593
|
+
const cached = this.auth?.nodeIdForHost(host);
|
|
7428
7594
|
if (cached) return cached;
|
|
7429
|
-
const response = await fetch(`${
|
|
7595
|
+
const response = await fetch(`${host}/info`);
|
|
7430
7596
|
if (!response.ok) {
|
|
7431
7597
|
throw await httpResponseError(response, "Failed to fetch node info");
|
|
7432
7598
|
}
|
|
@@ -7474,10 +7640,24 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7474
7640
|
)
|
|
7475
7641
|
};
|
|
7476
7642
|
}
|
|
7477
|
-
async
|
|
7478
|
-
const
|
|
7479
|
-
|
|
7480
|
-
);
|
|
7643
|
+
async encryptionDestination(networkId) {
|
|
7644
|
+
const destination = (await this.routingDestinations(networkId, "encryption"))[0];
|
|
7645
|
+
if (!destination) throw new Error("No encryption destination is registered");
|
|
7646
|
+
await verifyResourceDestination2(destination, globalThis.fetch.bind(globalThis));
|
|
7647
|
+
return { ...destination, nodeDid: destination.nodeDid ?? await this.fetchNodeId(destination.url) };
|
|
7648
|
+
}
|
|
7649
|
+
async fetchEncryptionNetworkAt(host, networkId, fetchFn, callerSignal) {
|
|
7650
|
+
let response;
|
|
7651
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
7652
|
+
try {
|
|
7653
|
+
response = await fetchFn(`${host}/encryption/networks/${encodeURIComponent(networkId)}`, {
|
|
7654
|
+
signal: callerSignal ? AbortSignal.any([callerSignal, timeout]) : timeout,
|
|
7655
|
+
redirect: "error"
|
|
7656
|
+
});
|
|
7657
|
+
} catch (error) {
|
|
7658
|
+
if (routingFailure3(error) || callerSignal?.aborted || !timeout.aborted && error instanceof Error && error.name === "AbortError") throw error;
|
|
7659
|
+
throw new NodeNotRespondingError2([host], error);
|
|
7660
|
+
}
|
|
7481
7661
|
if (response.status === 404) return null;
|
|
7482
7662
|
if (!response.ok) {
|
|
7483
7663
|
throw await httpResponseError(response, `Failed to fetch encryption network ${networkId}`);
|
|
@@ -7487,18 +7667,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7487
7667
|
}
|
|
7488
7668
|
createEncryptionService(binding) {
|
|
7489
7669
|
const graph = binding?.graph ?? this._serviceGraph;
|
|
7490
|
-
const host = binding?.host ?? this.config.host;
|
|
7491
7670
|
const session = binding?.session ?? this.requireEncryptionSession();
|
|
7492
7671
|
const did = binding?.did ?? this.did;
|
|
7493
7672
|
const address = binding?.address ?? this._address;
|
|
7494
7673
|
const chainId = binding?.chainId ?? this._chainId;
|
|
7495
7674
|
const crypto2 = this.createEncryptionCrypto();
|
|
7496
|
-
const descriptorCache = /* @__PURE__ */ new Map();
|
|
7497
7675
|
const transport = {
|
|
7498
|
-
postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
|
|
7676
|
+
postDecrypt: async ({ targetNode, networkId, authorization, canonicalBody, signal }) => {
|
|
7499
7677
|
graph.assertActive();
|
|
7678
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7679
|
+
if (destination.nodeDid !== targetNode) throw new NodeIdentityMismatchError(destination.url, destination.nodeDid, targetNode);
|
|
7500
7680
|
const response = await graph.fetch(
|
|
7501
|
-
`${
|
|
7681
|
+
`${destination.url}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
|
|
7502
7682
|
{
|
|
7503
7683
|
method: "POST",
|
|
7504
7684
|
headers: {
|
|
@@ -7526,6 +7706,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7526
7706
|
}
|
|
7527
7707
|
};
|
|
7528
7708
|
return new EncryptionService({
|
|
7709
|
+
resolveTargetNode: async (networkId) => (await this.encryptionDestination(networkId)).nodeDid,
|
|
7529
7710
|
crypto: crypto2,
|
|
7530
7711
|
signer: {
|
|
7531
7712
|
signDecryptInvocation: async (input) => {
|
|
@@ -7549,18 +7730,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7549
7730
|
node: {
|
|
7550
7731
|
fetchByNetworkId: async (networkId) => {
|
|
7551
7732
|
graph.assertActive();
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
pending = this.fetchEncryptionNetworkAt(host, networkId, graph.fetch).then((descriptor2) => {
|
|
7555
|
-
if (descriptor2 === null) descriptorCache.delete(networkId);
|
|
7556
|
-
return descriptor2;
|
|
7557
|
-
}).catch((error) => {
|
|
7558
|
-
descriptorCache.delete(networkId);
|
|
7559
|
-
throw error;
|
|
7560
|
-
});
|
|
7561
|
-
descriptorCache.set(networkId, pending);
|
|
7562
|
-
}
|
|
7563
|
-
const descriptor = await pending;
|
|
7733
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7734
|
+
const descriptor = await this.fetchEncryptionNetworkAt(destination.url, networkId, graph.fetch, graph.signal);
|
|
7564
7735
|
graph.assertActive();
|
|
7565
7736
|
return descriptor;
|
|
7566
7737
|
}
|
|
@@ -7568,13 +7739,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7568
7739
|
wellKnown: {
|
|
7569
7740
|
fetchWellKnown: async (principal, discoveryKey) => {
|
|
7570
7741
|
graph.assertActive();
|
|
7742
|
+
const destination = await this.encryptionDestination(this.getDefaultEncryptionNetworkId());
|
|
7743
|
+
if (this.auth?.registryRecordPresent) return null;
|
|
7571
7744
|
if (!address || !didPrincipalMatches2(principal, did)) {
|
|
7572
7745
|
return null;
|
|
7573
7746
|
}
|
|
7574
7747
|
const publicSpaceId = makePublicSpaceId(address, chainId);
|
|
7575
7748
|
const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
|
|
7576
7749
|
const response = await graph.fetch(
|
|
7577
|
-
`${
|
|
7750
|
+
`${destination.url}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
|
|
7578
7751
|
{ method: "GET" }
|
|
7579
7752
|
);
|
|
7580
7753
|
graph.assertActive();
|
|
@@ -7944,7 +8117,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7944
8117
|
const signature = await this.signer.signMessage(prepared.siwe);
|
|
7945
8118
|
assertOwnerGraphActive();
|
|
7946
8119
|
const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature });
|
|
7947
|
-
const activation = await
|
|
8120
|
+
const activation = await this.activateSignedSession(delegationSession, prepared.siwe, host);
|
|
7948
8121
|
assertOwnerGraphActive();
|
|
7949
8122
|
if (!activation.success) {
|
|
7950
8123
|
throw Object.assign(
|
|
@@ -8263,15 +8436,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8263
8436
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
8264
8437
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
8265
8438
|
return this.fetchEncryptionNetworkAt(
|
|
8266
|
-
this.
|
|
8439
|
+
(await this.encryptionDestination(networkId)).url,
|
|
8267
8440
|
networkId,
|
|
8268
8441
|
globalThis.fetch.bind(globalThis)
|
|
8269
8442
|
);
|
|
8270
8443
|
}
|
|
8271
8444
|
async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
|
|
8272
|
-
const targetNode = await this.fetchNodeId();
|
|
8273
8445
|
const ownerDid = this.did;
|
|
8274
8446
|
const networkId = this.getDefaultEncryptionNetworkId(name);
|
|
8447
|
+
const destination = await this.encryptionDestination(networkId);
|
|
8448
|
+
const targetNode = destination.nodeDid;
|
|
8275
8449
|
const body = {
|
|
8276
8450
|
name,
|
|
8277
8451
|
ownerDid,
|
|
@@ -8294,7 +8468,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8294
8468
|
action: NETWORK_CREATE_ACTION2,
|
|
8295
8469
|
facts
|
|
8296
8470
|
});
|
|
8297
|
-
const response = await fetch(`${
|
|
8471
|
+
const response = await this._serviceGraph.fetch(`${destination.url}/encryption/networks`, {
|
|
8298
8472
|
method: "POST",
|
|
8299
8473
|
headers: {
|
|
8300
8474
|
Authorization: signed.authorization,
|
|
@@ -8634,6 +8808,97 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8634
8808
|
}
|
|
8635
8809
|
return { recap: exactVerifiedRecap(verified), expiresAt };
|
|
8636
8810
|
}
|
|
8811
|
+
async activateResourceSession(delegationHeader, resources, hostHint, signal) {
|
|
8812
|
+
const targets = /* @__PURE__ */ new Map();
|
|
8813
|
+
const routedSpaces = [];
|
|
8814
|
+
let unserved;
|
|
8815
|
+
let requestedResource;
|
|
8816
|
+
for (const resource of new Set(resources)) {
|
|
8817
|
+
requestedResource ?? (requestedResource = resource);
|
|
8818
|
+
signal?.throwIfAborted();
|
|
8819
|
+
try {
|
|
8820
|
+
const encryption = resource.startsWith("urn:tinycloud:encryption:");
|
|
8821
|
+
const destinations = await this.routingDestinations(
|
|
8822
|
+
resource,
|
|
8823
|
+
encryption ? "encryption" : "host",
|
|
8824
|
+
[hostHint ?? this.config.host]
|
|
8825
|
+
);
|
|
8826
|
+
if (!encryption && destinations.some(({ nodeDid }) => nodeDid !== void 0)) routedSpaces.push(resource);
|
|
8827
|
+
for (const destination of destinations) {
|
|
8828
|
+
let target = targets.get(destination.url);
|
|
8829
|
+
if (!target) {
|
|
8830
|
+
target = { destination, spaces: [] };
|
|
8831
|
+
targets.set(destination.url, target);
|
|
8832
|
+
}
|
|
8833
|
+
if (!encryption && destination.nodeDid !== void 0) target.spaces.push(resource);
|
|
8834
|
+
}
|
|
8835
|
+
} catch (error) {
|
|
8836
|
+
if (!(error instanceof NodeNotFoundError2)) throw error;
|
|
8837
|
+
unserved ?? (unserved = error);
|
|
8838
|
+
}
|
|
8839
|
+
}
|
|
8840
|
+
if (targets.size === 0) throw unserved ?? new ResourceNotRoutedError(requestedResource ?? "session grant");
|
|
8841
|
+
const succeeded = [];
|
|
8842
|
+
const accepted = [];
|
|
8843
|
+
const failed = [];
|
|
8844
|
+
let refusal;
|
|
8845
|
+
for (const { destination: target, spaces } of targets.values()) {
|
|
8846
|
+
signal?.throwIfAborted();
|
|
8847
|
+
try {
|
|
8848
|
+
await verifyResourceDestination2(target, globalThis.fetch.bind(globalThis));
|
|
8849
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
8850
|
+
const result = await activateSessionWithHost2(
|
|
8851
|
+
target.url,
|
|
8852
|
+
delegationHeader,
|
|
8853
|
+
signal ? AbortSignal.any([signal, timeout]) : timeout
|
|
8854
|
+
);
|
|
8855
|
+
this.throwRegistryRefusal(result.status, result.error ?? "");
|
|
8856
|
+
if (result.success) {
|
|
8857
|
+
const skipped = result.skipped?.find((space) => spaces.some((expected) => this.spaceIdsEqual(space, expected)));
|
|
8858
|
+
if (skipped) {
|
|
8859
|
+
failed.push({ url: target.url, status: result.status, cause: new ResourceNotRoutedError(skipped) });
|
|
8860
|
+
continue;
|
|
8861
|
+
}
|
|
8862
|
+
succeeded.push(target.url);
|
|
8863
|
+
accepted.push(result);
|
|
8864
|
+
} else {
|
|
8865
|
+
refusal = result;
|
|
8866
|
+
failed.push({ url: target.url, status: result.status, cause: result });
|
|
8867
|
+
}
|
|
8868
|
+
} catch (error) {
|
|
8869
|
+
if (signal?.aborted) throw error;
|
|
8870
|
+
failed.push({ url: target.url, cause: routingFailure3(error) ? error : new NodeNotRespondingError2([target.url], error) });
|
|
8871
|
+
}
|
|
8872
|
+
}
|
|
8873
|
+
if (failed.length > 0) {
|
|
8874
|
+
if (targets.size > 1) throw new PartialNodeWriteError2(succeeded, failed);
|
|
8875
|
+
if (refusal) return refusal;
|
|
8876
|
+
throw failed[0].cause;
|
|
8877
|
+
}
|
|
8878
|
+
const activated = new Set(accepted.flatMap((result) => result.activated ?? []));
|
|
8879
|
+
return {
|
|
8880
|
+
...accepted[0],
|
|
8881
|
+
activated: [...activated],
|
|
8882
|
+
skipped: [...new Set(accepted.flatMap((result) => result.skipped ?? []))].filter((space) => !activated.has(space) && !routedSpaces.some((expected) => this.spaceIdsEqual(space, expected)))
|
|
8883
|
+
};
|
|
8884
|
+
}
|
|
8885
|
+
activateRoutedDelegation(delegation, signal) {
|
|
8886
|
+
const resources = this.operationsFromDelegation(delegation).map(
|
|
8887
|
+
(operation) => operation.resource?.startsWith("urn:tinycloud:encryption:") ? operation.resource.split("/")[0] : operation.spaceId ?? delegation.spaceId
|
|
8888
|
+
);
|
|
8889
|
+
return this.activateResourceSession(delegation.delegationHeader, resources, delegation.host, signal);
|
|
8890
|
+
}
|
|
8891
|
+
activateSignedSession(session, siwe, hostHint, signal) {
|
|
8892
|
+
const resources = this.parseRecapWithCaveats(siwe).map(
|
|
8893
|
+
(entry) => this.isEncryptionNetworkOperation(this.invocationServiceName(entry.service), entry.path, entry.space) ? entry.path.split("/")[0] : entry.space.startsWith("tinycloud:") ? entry.space : session.spaceId
|
|
8894
|
+
);
|
|
8895
|
+
return this.activateResourceSession(
|
|
8896
|
+
session.delegationHeader,
|
|
8897
|
+
resources.length > 0 ? resources : [session.spaceId],
|
|
8898
|
+
hostHint,
|
|
8899
|
+
signal
|
|
8900
|
+
);
|
|
8901
|
+
}
|
|
8637
8902
|
/**
|
|
8638
8903
|
* Install a portable runtime permission delegation into this SDK instance so
|
|
8639
8904
|
* matching service calls and downstream `delegateTo()` calls can use it.
|
|
@@ -8653,16 +8918,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8653
8918
|
`Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
|
|
8654
8919
|
);
|
|
8655
8920
|
}
|
|
8656
|
-
const targetHost = delegation.host ?? this.config.host;
|
|
8657
8921
|
let activateResult;
|
|
8658
8922
|
try {
|
|
8659
|
-
activateResult = await
|
|
8660
|
-
targetHost,
|
|
8661
|
-
delegation.delegationHeader,
|
|
8662
|
-
graph.signal
|
|
8663
|
-
);
|
|
8923
|
+
activateResult = await this.activateRoutedDelegation(delegation, graph.signal);
|
|
8664
8924
|
} catch (cause) {
|
|
8665
8925
|
graph.assertActive();
|
|
8926
|
+
if (routingFailure3(cause)) throw cause;
|
|
8666
8927
|
throw Object.assign(
|
|
8667
8928
|
new Error("The TinyCloud host could not be reached to activate the delegation."),
|
|
8668
8929
|
{ code: "ACTIVATION_TRANSPORT_FAILED", cause }
|
|
@@ -8818,11 +9079,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8818
9079
|
...prepared,
|
|
8819
9080
|
signature
|
|
8820
9081
|
});
|
|
8821
|
-
const
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
9082
|
+
const delegation = this.runtimeDelegationFromSession(
|
|
9083
|
+
delegatedSession,
|
|
9084
|
+
delegatedEntries,
|
|
9085
|
+
spaceId,
|
|
9086
|
+
captured,
|
|
9087
|
+
expiresAt,
|
|
9088
|
+
{ siwe: prepared.siwe, signature }
|
|
9089
|
+
);
|
|
9090
|
+
const activateResult = await this.activateRoutedDelegation(delegation, graph.signal).catch((error) => {
|
|
8826
9091
|
graph.assertActive();
|
|
8827
9092
|
throw error;
|
|
8828
9093
|
});
|
|
@@ -8838,14 +9103,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8838
9103
|
{ cause: activateResult }
|
|
8839
9104
|
);
|
|
8840
9105
|
}
|
|
8841
|
-
const delegation = this.runtimeDelegationFromSession(
|
|
8842
|
-
delegatedSession,
|
|
8843
|
-
delegatedEntries,
|
|
8844
|
-
spaceId,
|
|
8845
|
-
captured,
|
|
8846
|
-
expiresAt,
|
|
8847
|
-
{ siwe: prepared.siwe, signature }
|
|
8848
|
-
);
|
|
8849
9106
|
graph.assertActive();
|
|
8850
9107
|
assertCapturedKey?.();
|
|
8851
9108
|
this.runtimePermissionGrants.push({
|
|
@@ -8990,7 +9247,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8990
9247
|
if (!publicSpaceId) {
|
|
8991
9248
|
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
8992
9249
|
}
|
|
8993
|
-
await this.assertMayCreateSpace();
|
|
9250
|
+
await this.assertMayCreateSpace(publicSpaceId);
|
|
8994
9251
|
await this.auth.hostPublicSpace(publicSpaceId);
|
|
8995
9252
|
const kvActions = [KV3.PUT, KV3.GET, KV3.DEL, KV3.LIST, KV3.METADATA];
|
|
8996
9253
|
const abilities = { kv: { "": kvActions } };
|
|
@@ -9013,10 +9270,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9013
9270
|
...prepared,
|
|
9014
9271
|
signature
|
|
9015
9272
|
});
|
|
9016
|
-
const activateResult = await
|
|
9017
|
-
this.config.host,
|
|
9018
|
-
delegationSession.delegationHeader
|
|
9019
|
-
);
|
|
9273
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
9020
9274
|
if (!activateResult.success) {
|
|
9021
9275
|
throw Object.assign(
|
|
9022
9276
|
new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -9566,10 +9820,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9566
9820
|
if (onPrepared) {
|
|
9567
9821
|
await onPrepared(delegation);
|
|
9568
9822
|
}
|
|
9569
|
-
const activateResult = await
|
|
9570
|
-
this.config.host,
|
|
9571
|
-
delegationHeader
|
|
9572
|
-
);
|
|
9823
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9573
9824
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9574
9825
|
return delegation;
|
|
9575
9826
|
}
|
|
@@ -9603,10 +9854,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9603
9854
|
if (onPrepared) {
|
|
9604
9855
|
await onPrepared(delegation);
|
|
9605
9856
|
}
|
|
9606
|
-
const activateResult = await
|
|
9607
|
-
targetHost,
|
|
9608
|
-
delegationHeader
|
|
9609
|
-
);
|
|
9857
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9610
9858
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9611
9859
|
return delegation;
|
|
9612
9860
|
}
|
|
@@ -10258,10 +10506,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10258
10506
|
...prepared,
|
|
10259
10507
|
signature
|
|
10260
10508
|
});
|
|
10261
|
-
const activateResult = await
|
|
10262
|
-
this.config.host,
|
|
10263
|
-
delegationSession.delegationHeader
|
|
10264
|
-
);
|
|
10509
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
10265
10510
|
if (!activateResult.success) {
|
|
10266
10511
|
throw Object.assign(
|
|
10267
10512
|
new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10306,10 +10551,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10306
10551
|
...publicPrepared,
|
|
10307
10552
|
signature: publicSignature
|
|
10308
10553
|
});
|
|
10309
|
-
const publicActivateResult = await
|
|
10310
|
-
this.config.host,
|
|
10311
|
-
publicSession.delegationHeader
|
|
10312
|
-
);
|
|
10554
|
+
const publicActivateResult = await this.activateSignedSession(publicSession, publicPrepared.siwe);
|
|
10313
10555
|
if (publicActivateResult.success) {
|
|
10314
10556
|
result.publicDelegation = {
|
|
10315
10557
|
cid: publicSession.delegationCid,
|
|
@@ -10415,11 +10657,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10415
10657
|
...prepared,
|
|
10416
10658
|
signature
|
|
10417
10659
|
});
|
|
10418
|
-
const activateResult = await
|
|
10419
|
-
targetHost,
|
|
10420
|
-
invokerSession.delegationHeader,
|
|
10421
|
-
graph.signal
|
|
10422
|
-
).catch((error) => {
|
|
10660
|
+
const activateResult = await this.activateSignedSession(invokerSession, prepared.siwe, targetHost, graph.signal).catch((error) => {
|
|
10423
10661
|
graph.assertActive();
|
|
10424
10662
|
throw error;
|
|
10425
10663
|
});
|
|
@@ -10546,10 +10784,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10546
10784
|
...prepared,
|
|
10547
10785
|
signature
|
|
10548
10786
|
});
|
|
10549
|
-
const activateResult = await
|
|
10550
|
-
targetHost,
|
|
10551
|
-
subDelegationSession.delegationHeader
|
|
10552
|
-
);
|
|
10787
|
+
const activateResult = await this.activateSignedSession(subDelegationSession, prepared.siwe, targetHost);
|
|
10553
10788
|
if (!activateResult.success) {
|
|
10554
10789
|
throw Object.assign(
|
|
10555
10790
|
new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10652,7 +10887,7 @@ import {
|
|
|
10652
10887
|
SpaceService as SpaceService2,
|
|
10653
10888
|
SpaceErrorCodes,
|
|
10654
10889
|
createSpaceService,
|
|
10655
|
-
parseSpaceUri as
|
|
10890
|
+
parseSpaceUri as parseSpaceUri3,
|
|
10656
10891
|
buildSpaceUri,
|
|
10657
10892
|
makePublicSpaceId as makePublicSpaceId2,
|
|
10658
10893
|
Space
|
|
@@ -10689,7 +10924,10 @@ export {
|
|
|
10689
10924
|
KVService3 as KVService,
|
|
10690
10925
|
ManifestValidationError3 as ManifestValidationError,
|
|
10691
10926
|
MemorySessionStorage,
|
|
10927
|
+
NodeIdentityMismatchError2 as NodeIdentityMismatchError,
|
|
10928
|
+
NodeNotRespondingError3 as NodeNotRespondingError,
|
|
10692
10929
|
NodeUserAuthorization,
|
|
10930
|
+
PartialNodeWriteError3 as PartialNodeWriteError,
|
|
10693
10931
|
PermissionNotInManifestError2 as PermissionNotInManifestError,
|
|
10694
10932
|
PrefixedKVService,
|
|
10695
10933
|
ProtocolMismatchError2 as ProtocolMismatchError,
|
|
@@ -10756,7 +10994,7 @@ export {
|
|
|
10756
10994
|
parseExpiry2 as parseExpiry,
|
|
10757
10995
|
parsePkhDid,
|
|
10758
10996
|
parseSecretCatalogKey,
|
|
10759
|
-
|
|
10997
|
+
parseSpaceUri3 as parseSpaceUri,
|
|
10760
10998
|
pkhDid3 as pkhDid,
|
|
10761
10999
|
principalDid,
|
|
10762
11000
|
principalDidEquals4 as principalDidEquals,
|