@tinycloud/node-sdk 3.12.0-beta.2 → 3.12.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/{NodeUserAuthorization-B05Wjjpb.d.cts → NodeUserAuthorization-B_TFUlua.d.cts} +18 -52
- package/dist/{NodeUserAuthorization-B05Wjjpb.d.ts → NodeUserAuthorization-B_TFUlua.d.ts} +18 -52
- package/dist/{TinyCloudNode-CEFGtSWn.d.ts → TinyCloudNode-Brcu8QoX.d.cts} +31 -59
- package/dist/{TinyCloudNode-6_IumWu0.d.cts → TinyCloudNode-DBJUnsMN.d.ts} +31 -59
- package/dist/{authority-DXpnuId1.d.ts → authority-BFc5lesl.d.ts} +1 -1
- package/dist/{authority-DODDXxoP.d.cts → authority-DQ6R1HEG.d.cts} +1 -1
- package/dist/core.cjs +500 -283
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +5 -5
- package/dist/core.d.ts +5 -5
- package/dist/core.js +526 -287
- package/dist/core.js.map +1 -1
- package/dist/credentials-core.d.cts +1 -1
- package/dist/credentials-core.d.ts +1 -1
- package/dist/credentials.d.cts +1 -1
- package/dist/credentials.d.ts +1 -1
- package/dist/{delegation-BQbAuKad.d.cts → delegation-CeOWDnBB.d.cts} +7 -0
- package/dist/{delegation-BQbAuKad.d.ts → delegation-CeOWDnBB.d.ts} +7 -0
- package/dist/encryption-keys.d.cts +3 -3
- package/dist/encryption-keys.d.ts +3 -3
- package/dist/index.cjs +517 -300
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +527 -288
- package/dist/index.js.map +1 -1
- package/dist/replication/authority.d.cts +4 -4
- package/dist/replication/authority.d.ts +4 -4
- package/dist/space-hosting.d.cts +1 -1
- package/dist/space-hosting.d.ts +1 -1
- package/package.json +4 -4
package/dist/core.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,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6437
6653
|
}
|
|
6438
6654
|
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
6439
6655
|
}
|
|
6440
|
-
const
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
sessionData.chainId
|
|
6444
|
-
);
|
|
6445
|
-
const stagedHost = resolvedHost ?? this.config.host;
|
|
6656
|
+
const restoreHints = this.explicitHost ? [this.explicitHost] : sessionData.tinycloudHosts ?? [];
|
|
6657
|
+
const resolvedHost = this.config.replication?.enabled ? (await this.routingDestinations(sessionData.spaceId, "read", restoreHints))[0]?.url : void 0;
|
|
6658
|
+
const stagedHost = resolvedHost ?? exactHttpOrigin2(restoreHints[0] ?? this.config.host, "restored host hint");
|
|
6446
6659
|
let restoredNodeFeatures;
|
|
6447
6660
|
let replicaProtocolMismatch = false;
|
|
6448
6661
|
if (this.config.replication?.enabled) {
|
|
@@ -6559,7 +6772,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6559
6772
|
this.sessionManager = stagedManager;
|
|
6560
6773
|
this.sessionKeyId = stagedKeyId;
|
|
6561
6774
|
this.sessionKeyJwk = stagedJwk;
|
|
6562
|
-
|
|
6775
|
+
this.config.host = stagedHost;
|
|
6563
6776
|
if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
|
|
6564
6777
|
this.storageFull.clear();
|
|
6565
6778
|
this._requestLog?.clear();
|
|
@@ -6626,6 +6839,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6626
6839
|
if (attempt === this.latestSessionAttempt) await this.endSession();
|
|
6627
6840
|
this.assertSessionEpoch(epoch, "Session restore");
|
|
6628
6841
|
}
|
|
6842
|
+
if (this.config.replication?.enabled) {
|
|
6843
|
+
if (stagedAddress !== void 0) this.refreshRegistryOwner(pkhDid2(stagedAddress, stagedChainId));
|
|
6844
|
+
this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
|
|
6845
|
+
}
|
|
6629
6846
|
}
|
|
6630
6847
|
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
6631
6848
|
if (!session?.siwe) return { cache: void 0, grants: [] };
|
|
@@ -6674,7 +6891,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6674
6891
|
};
|
|
6675
6892
|
}
|
|
6676
6893
|
stageRestoredServiceGraph(input) {
|
|
6677
|
-
const graph = this.createServiceGraphLifetime();
|
|
6894
|
+
const graph = this.createServiceGraphLifetime(input.serviceSession);
|
|
6678
6895
|
const core = this.auth ? new TinyCloud(this.auth, {
|
|
6679
6896
|
invokeAny: graph.invokeAny,
|
|
6680
6897
|
telemetry: this.config.telemetry,
|
|
@@ -6899,56 +7116,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6899
7116
|
spaceService.updateConfig({ sharingService });
|
|
6900
7117
|
return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
|
|
6901
7118
|
}
|
|
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
7119
|
/**
|
|
6953
7120
|
* Resolve the currently-active TinyCloudSession, preferring the auth
|
|
6954
7121
|
* layer's value (wallet mode) and falling back to the node-level
|
|
@@ -7423,10 +7590,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7423
7590
|
* second time. Only a value recorded for this exact host qualifies; older
|
|
7424
7591
|
* nodes that omit `nodeId` fall through to the fetch.
|
|
7425
7592
|
*/
|
|
7426
|
-
async fetchNodeId() {
|
|
7427
|
-
const cached = this.auth?.nodeIdForHost(
|
|
7593
|
+
async fetchNodeId(host = this.config.host) {
|
|
7594
|
+
const cached = this.auth?.nodeIdForHost(host);
|
|
7428
7595
|
if (cached) return cached;
|
|
7429
|
-
const response = await fetch(`${
|
|
7596
|
+
const response = await fetch(`${host}/info`);
|
|
7430
7597
|
if (!response.ok) {
|
|
7431
7598
|
throw await httpResponseError(response, "Failed to fetch node info");
|
|
7432
7599
|
}
|
|
@@ -7474,10 +7641,24 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7474
7641
|
)
|
|
7475
7642
|
};
|
|
7476
7643
|
}
|
|
7477
|
-
async
|
|
7478
|
-
const
|
|
7479
|
-
|
|
7480
|
-
);
|
|
7644
|
+
async encryptionDestination(networkId) {
|
|
7645
|
+
const destination = (await this.routingDestinations(networkId, "encryption"))[0];
|
|
7646
|
+
if (!destination) throw new Error("No encryption destination is registered");
|
|
7647
|
+
await verifyResourceDestination2(destination, globalThis.fetch.bind(globalThis));
|
|
7648
|
+
return { ...destination, nodeDid: destination.nodeDid ?? await this.fetchNodeId(destination.url) };
|
|
7649
|
+
}
|
|
7650
|
+
async fetchEncryptionNetworkAt(host, networkId, fetchFn, callerSignal) {
|
|
7651
|
+
let response;
|
|
7652
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
7653
|
+
try {
|
|
7654
|
+
response = await fetchFn(`${host}/encryption/networks/${encodeURIComponent(networkId)}`, {
|
|
7655
|
+
signal: callerSignal ? AbortSignal.any([callerSignal, timeout]) : timeout,
|
|
7656
|
+
redirect: "error"
|
|
7657
|
+
});
|
|
7658
|
+
} catch (error) {
|
|
7659
|
+
if (routingFailure3(error) || callerSignal?.aborted || !timeout.aborted && error instanceof Error && error.name === "AbortError") throw error;
|
|
7660
|
+
throw new NodeNotRespondingError2([host], error);
|
|
7661
|
+
}
|
|
7481
7662
|
if (response.status === 404) return null;
|
|
7482
7663
|
if (!response.ok) {
|
|
7483
7664
|
throw await httpResponseError(response, `Failed to fetch encryption network ${networkId}`);
|
|
@@ -7487,18 +7668,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7487
7668
|
}
|
|
7488
7669
|
createEncryptionService(binding) {
|
|
7489
7670
|
const graph = binding?.graph ?? this._serviceGraph;
|
|
7490
|
-
const host = binding?.host ?? this.config.host;
|
|
7491
7671
|
const session = binding?.session ?? this.requireEncryptionSession();
|
|
7492
7672
|
const did = binding?.did ?? this.did;
|
|
7493
7673
|
const address = binding?.address ?? this._address;
|
|
7494
7674
|
const chainId = binding?.chainId ?? this._chainId;
|
|
7495
7675
|
const crypto2 = this.createEncryptionCrypto();
|
|
7496
|
-
const descriptorCache = /* @__PURE__ */ new Map();
|
|
7497
7676
|
const transport = {
|
|
7498
|
-
postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
|
|
7677
|
+
postDecrypt: async ({ targetNode, networkId, authorization, canonicalBody, signal }) => {
|
|
7499
7678
|
graph.assertActive();
|
|
7679
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7680
|
+
if (destination.nodeDid !== targetNode) throw new NodeIdentityMismatchError(destination.url, destination.nodeDid, targetNode);
|
|
7500
7681
|
const response = await graph.fetch(
|
|
7501
|
-
`${
|
|
7682
|
+
`${destination.url}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
|
|
7502
7683
|
{
|
|
7503
7684
|
method: "POST",
|
|
7504
7685
|
headers: {
|
|
@@ -7526,6 +7707,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7526
7707
|
}
|
|
7527
7708
|
};
|
|
7528
7709
|
return new EncryptionService({
|
|
7710
|
+
resolveTargetNode: async (networkId) => (await this.encryptionDestination(networkId)).nodeDid,
|
|
7529
7711
|
crypto: crypto2,
|
|
7530
7712
|
signer: {
|
|
7531
7713
|
signDecryptInvocation: async (input) => {
|
|
@@ -7549,18 +7731,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7549
7731
|
node: {
|
|
7550
7732
|
fetchByNetworkId: async (networkId) => {
|
|
7551
7733
|
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;
|
|
7734
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7735
|
+
const descriptor = await this.fetchEncryptionNetworkAt(destination.url, networkId, graph.fetch, graph.signal);
|
|
7564
7736
|
graph.assertActive();
|
|
7565
7737
|
return descriptor;
|
|
7566
7738
|
}
|
|
@@ -7568,13 +7740,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7568
7740
|
wellKnown: {
|
|
7569
7741
|
fetchWellKnown: async (principal, discoveryKey) => {
|
|
7570
7742
|
graph.assertActive();
|
|
7743
|
+
const destination = await this.encryptionDestination(this.getDefaultEncryptionNetworkId());
|
|
7744
|
+
if (this.auth?.registryRecordPresent) return null;
|
|
7571
7745
|
if (!address || !didPrincipalMatches2(principal, did)) {
|
|
7572
7746
|
return null;
|
|
7573
7747
|
}
|
|
7574
7748
|
const publicSpaceId = makePublicSpaceId(address, chainId);
|
|
7575
7749
|
const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
|
|
7576
7750
|
const response = await graph.fetch(
|
|
7577
|
-
`${
|
|
7751
|
+
`${destination.url}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
|
|
7578
7752
|
{ method: "GET" }
|
|
7579
7753
|
);
|
|
7580
7754
|
graph.assertActive();
|
|
@@ -7944,7 +8118,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7944
8118
|
const signature = await this.signer.signMessage(prepared.siwe);
|
|
7945
8119
|
assertOwnerGraphActive();
|
|
7946
8120
|
const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature });
|
|
7947
|
-
const activation = await
|
|
8121
|
+
const activation = await this.activateSignedSession(delegationSession, prepared.siwe, host);
|
|
7948
8122
|
assertOwnerGraphActive();
|
|
7949
8123
|
if (!activation.success) {
|
|
7950
8124
|
throw Object.assign(
|
|
@@ -8263,15 +8437,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8263
8437
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
8264
8438
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
8265
8439
|
return this.fetchEncryptionNetworkAt(
|
|
8266
|
-
this.
|
|
8440
|
+
(await this.encryptionDestination(networkId)).url,
|
|
8267
8441
|
networkId,
|
|
8268
8442
|
globalThis.fetch.bind(globalThis)
|
|
8269
8443
|
);
|
|
8270
8444
|
}
|
|
8271
8445
|
async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
|
|
8272
|
-
const targetNode = await this.fetchNodeId();
|
|
8273
8446
|
const ownerDid = this.did;
|
|
8274
8447
|
const networkId = this.getDefaultEncryptionNetworkId(name);
|
|
8448
|
+
const destination = await this.encryptionDestination(networkId);
|
|
8449
|
+
const targetNode = destination.nodeDid;
|
|
8275
8450
|
const body = {
|
|
8276
8451
|
name,
|
|
8277
8452
|
ownerDid,
|
|
@@ -8294,7 +8469,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8294
8469
|
action: NETWORK_CREATE_ACTION2,
|
|
8295
8470
|
facts
|
|
8296
8471
|
});
|
|
8297
|
-
const response = await fetch(`${
|
|
8472
|
+
const response = await this._serviceGraph.fetch(`${destination.url}/encryption/networks`, {
|
|
8298
8473
|
method: "POST",
|
|
8299
8474
|
headers: {
|
|
8300
8475
|
Authorization: signed.authorization,
|
|
@@ -8634,6 +8809,97 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8634
8809
|
}
|
|
8635
8810
|
return { recap: exactVerifiedRecap(verified), expiresAt };
|
|
8636
8811
|
}
|
|
8812
|
+
async activateResourceSession(delegationHeader, resources, hostHint, signal) {
|
|
8813
|
+
const targets = /* @__PURE__ */ new Map();
|
|
8814
|
+
const routedSpaces = [];
|
|
8815
|
+
let unserved;
|
|
8816
|
+
let requestedResource;
|
|
8817
|
+
for (const resource of new Set(resources)) {
|
|
8818
|
+
requestedResource ?? (requestedResource = resource);
|
|
8819
|
+
signal?.throwIfAborted();
|
|
8820
|
+
try {
|
|
8821
|
+
const encryption = resource.startsWith("urn:tinycloud:encryption:");
|
|
8822
|
+
const destinations = await this.routingDestinations(
|
|
8823
|
+
resource,
|
|
8824
|
+
encryption ? "encryption" : "host",
|
|
8825
|
+
[hostHint ?? this.config.host]
|
|
8826
|
+
);
|
|
8827
|
+
if (!encryption && destinations.some(({ nodeDid }) => nodeDid !== void 0)) routedSpaces.push(resource);
|
|
8828
|
+
for (const destination of destinations) {
|
|
8829
|
+
let target = targets.get(destination.url);
|
|
8830
|
+
if (!target) {
|
|
8831
|
+
target = { destination, spaces: [] };
|
|
8832
|
+
targets.set(destination.url, target);
|
|
8833
|
+
}
|
|
8834
|
+
if (!encryption && destination.nodeDid !== void 0) target.spaces.push(resource);
|
|
8835
|
+
}
|
|
8836
|
+
} catch (error) {
|
|
8837
|
+
if (!(error instanceof NodeNotFoundError2)) throw error;
|
|
8838
|
+
unserved ?? (unserved = error);
|
|
8839
|
+
}
|
|
8840
|
+
}
|
|
8841
|
+
if (targets.size === 0) throw unserved ?? new ResourceNotRoutedError(requestedResource ?? "session grant");
|
|
8842
|
+
const succeeded = [];
|
|
8843
|
+
const accepted = [];
|
|
8844
|
+
const failed = [];
|
|
8845
|
+
let refusal;
|
|
8846
|
+
for (const { destination: target, spaces } of targets.values()) {
|
|
8847
|
+
signal?.throwIfAborted();
|
|
8848
|
+
try {
|
|
8849
|
+
await verifyResourceDestination2(target, globalThis.fetch.bind(globalThis));
|
|
8850
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
8851
|
+
const result = await activateSessionWithHost2(
|
|
8852
|
+
target.url,
|
|
8853
|
+
delegationHeader,
|
|
8854
|
+
signal ? AbortSignal.any([signal, timeout]) : timeout
|
|
8855
|
+
);
|
|
8856
|
+
this.throwRegistryRefusal(result.status, result.error ?? "");
|
|
8857
|
+
if (result.success) {
|
|
8858
|
+
const skipped = result.skipped?.find((space) => spaces.some((expected) => this.spaceIdsEqual(space, expected)));
|
|
8859
|
+
if (skipped) {
|
|
8860
|
+
failed.push({ url: target.url, status: result.status, cause: new ResourceNotRoutedError(skipped) });
|
|
8861
|
+
continue;
|
|
8862
|
+
}
|
|
8863
|
+
succeeded.push(target.url);
|
|
8864
|
+
accepted.push(result);
|
|
8865
|
+
} else {
|
|
8866
|
+
refusal = result;
|
|
8867
|
+
failed.push({ url: target.url, status: result.status, cause: result });
|
|
8868
|
+
}
|
|
8869
|
+
} catch (error) {
|
|
8870
|
+
if (signal?.aborted) throw error;
|
|
8871
|
+
failed.push({ url: target.url, cause: routingFailure3(error) ? error : new NodeNotRespondingError2([target.url], error) });
|
|
8872
|
+
}
|
|
8873
|
+
}
|
|
8874
|
+
if (failed.length > 0) {
|
|
8875
|
+
if (targets.size > 1) throw new PartialNodeWriteError2(succeeded, failed);
|
|
8876
|
+
if (refusal) return refusal;
|
|
8877
|
+
throw failed[0].cause;
|
|
8878
|
+
}
|
|
8879
|
+
const activated = new Set(accepted.flatMap((result) => result.activated ?? []));
|
|
8880
|
+
return {
|
|
8881
|
+
...accepted[0],
|
|
8882
|
+
activated: [...activated],
|
|
8883
|
+
skipped: [...new Set(accepted.flatMap((result) => result.skipped ?? []))].filter((space) => !activated.has(space) && !routedSpaces.some((expected) => this.spaceIdsEqual(space, expected)))
|
|
8884
|
+
};
|
|
8885
|
+
}
|
|
8886
|
+
activateRoutedDelegation(delegation, signal) {
|
|
8887
|
+
const resources = this.operationsFromDelegation(delegation).map(
|
|
8888
|
+
(operation) => operation.resource?.startsWith("urn:tinycloud:encryption:") ? operation.resource.split("/")[0] : operation.spaceId ?? delegation.spaceId
|
|
8889
|
+
);
|
|
8890
|
+
return this.activateResourceSession(delegation.delegationHeader, resources, delegation.host, signal);
|
|
8891
|
+
}
|
|
8892
|
+
activateSignedSession(session, siwe, hostHint, signal) {
|
|
8893
|
+
const resources = this.parseRecapWithCaveats(siwe).map(
|
|
8894
|
+
(entry) => this.isEncryptionNetworkOperation(this.invocationServiceName(entry.service), entry.path, entry.space) ? entry.path.split("/")[0] : entry.space.startsWith("tinycloud:") ? entry.space : session.spaceId
|
|
8895
|
+
);
|
|
8896
|
+
return this.activateResourceSession(
|
|
8897
|
+
session.delegationHeader,
|
|
8898
|
+
resources.length > 0 ? resources : [session.spaceId],
|
|
8899
|
+
hostHint,
|
|
8900
|
+
signal
|
|
8901
|
+
);
|
|
8902
|
+
}
|
|
8637
8903
|
/**
|
|
8638
8904
|
* Install a portable runtime permission delegation into this SDK instance so
|
|
8639
8905
|
* matching service calls and downstream `delegateTo()` calls can use it.
|
|
@@ -8653,16 +8919,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8653
8919
|
`Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
|
|
8654
8920
|
);
|
|
8655
8921
|
}
|
|
8656
|
-
const targetHost = delegation.host ?? this.config.host;
|
|
8657
8922
|
let activateResult;
|
|
8658
8923
|
try {
|
|
8659
|
-
activateResult = await
|
|
8660
|
-
targetHost,
|
|
8661
|
-
delegation.delegationHeader,
|
|
8662
|
-
graph.signal
|
|
8663
|
-
);
|
|
8924
|
+
activateResult = await this.activateRoutedDelegation(delegation, graph.signal);
|
|
8664
8925
|
} catch (cause) {
|
|
8665
8926
|
graph.assertActive();
|
|
8927
|
+
if (routingFailure3(cause)) throw cause;
|
|
8666
8928
|
throw Object.assign(
|
|
8667
8929
|
new Error("The TinyCloud host could not be reached to activate the delegation."),
|
|
8668
8930
|
{ code: "ACTIVATION_TRANSPORT_FAILED", cause }
|
|
@@ -8818,11 +9080,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8818
9080
|
...prepared,
|
|
8819
9081
|
signature
|
|
8820
9082
|
});
|
|
8821
|
-
const
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
9083
|
+
const delegation = this.runtimeDelegationFromSession(
|
|
9084
|
+
delegatedSession,
|
|
9085
|
+
delegatedEntries,
|
|
9086
|
+
spaceId,
|
|
9087
|
+
captured,
|
|
9088
|
+
expiresAt,
|
|
9089
|
+
{ siwe: prepared.siwe, signature }
|
|
9090
|
+
);
|
|
9091
|
+
const activateResult = await this.activateRoutedDelegation(delegation, graph.signal).catch((error) => {
|
|
8826
9092
|
graph.assertActive();
|
|
8827
9093
|
throw error;
|
|
8828
9094
|
});
|
|
@@ -8838,14 +9104,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8838
9104
|
{ cause: activateResult }
|
|
8839
9105
|
);
|
|
8840
9106
|
}
|
|
8841
|
-
const delegation = this.runtimeDelegationFromSession(
|
|
8842
|
-
delegatedSession,
|
|
8843
|
-
delegatedEntries,
|
|
8844
|
-
spaceId,
|
|
8845
|
-
captured,
|
|
8846
|
-
expiresAt,
|
|
8847
|
-
{ siwe: prepared.siwe, signature }
|
|
8848
|
-
);
|
|
8849
9107
|
graph.assertActive();
|
|
8850
9108
|
assertCapturedKey?.();
|
|
8851
9109
|
this.runtimePermissionGrants.push({
|
|
@@ -8990,7 +9248,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8990
9248
|
if (!publicSpaceId) {
|
|
8991
9249
|
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
8992
9250
|
}
|
|
8993
|
-
await this.assertMayCreateSpace();
|
|
9251
|
+
await this.assertMayCreateSpace(publicSpaceId);
|
|
8994
9252
|
await this.auth.hostPublicSpace(publicSpaceId);
|
|
8995
9253
|
const kvActions = [KV3.PUT, KV3.GET, KV3.DEL, KV3.LIST, KV3.METADATA];
|
|
8996
9254
|
const abilities = { kv: { "": kvActions } };
|
|
@@ -9013,10 +9271,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9013
9271
|
...prepared,
|
|
9014
9272
|
signature
|
|
9015
9273
|
});
|
|
9016
|
-
const activateResult = await
|
|
9017
|
-
this.config.host,
|
|
9018
|
-
delegationSession.delegationHeader
|
|
9019
|
-
);
|
|
9274
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
9020
9275
|
if (!activateResult.success) {
|
|
9021
9276
|
throw Object.assign(
|
|
9022
9277
|
new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -9566,10 +9821,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9566
9821
|
if (onPrepared) {
|
|
9567
9822
|
await onPrepared(delegation);
|
|
9568
9823
|
}
|
|
9569
|
-
const activateResult = await
|
|
9570
|
-
this.config.host,
|
|
9571
|
-
delegationHeader
|
|
9572
|
-
);
|
|
9824
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9573
9825
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9574
9826
|
return delegation;
|
|
9575
9827
|
}
|
|
@@ -9603,10 +9855,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9603
9855
|
if (onPrepared) {
|
|
9604
9856
|
await onPrepared(delegation);
|
|
9605
9857
|
}
|
|
9606
|
-
const activateResult = await
|
|
9607
|
-
targetHost,
|
|
9608
|
-
delegationHeader
|
|
9609
|
-
);
|
|
9858
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9610
9859
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9611
9860
|
return delegation;
|
|
9612
9861
|
}
|
|
@@ -10258,10 +10507,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10258
10507
|
...prepared,
|
|
10259
10508
|
signature
|
|
10260
10509
|
});
|
|
10261
|
-
const activateResult = await
|
|
10262
|
-
this.config.host,
|
|
10263
|
-
delegationSession.delegationHeader
|
|
10264
|
-
);
|
|
10510
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
10265
10511
|
if (!activateResult.success) {
|
|
10266
10512
|
throw Object.assign(
|
|
10267
10513
|
new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10306,10 +10552,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10306
10552
|
...publicPrepared,
|
|
10307
10553
|
signature: publicSignature
|
|
10308
10554
|
});
|
|
10309
|
-
const publicActivateResult = await
|
|
10310
|
-
this.config.host,
|
|
10311
|
-
publicSession.delegationHeader
|
|
10312
|
-
);
|
|
10555
|
+
const publicActivateResult = await this.activateSignedSession(publicSession, publicPrepared.siwe);
|
|
10313
10556
|
if (publicActivateResult.success) {
|
|
10314
10557
|
result.publicDelegation = {
|
|
10315
10558
|
cid: publicSession.delegationCid,
|
|
@@ -10415,11 +10658,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10415
10658
|
...prepared,
|
|
10416
10659
|
signature
|
|
10417
10660
|
});
|
|
10418
|
-
const activateResult = await
|
|
10419
|
-
targetHost,
|
|
10420
|
-
invokerSession.delegationHeader,
|
|
10421
|
-
graph.signal
|
|
10422
|
-
).catch((error) => {
|
|
10661
|
+
const activateResult = await this.activateSignedSession(invokerSession, prepared.siwe, targetHost, graph.signal).catch((error) => {
|
|
10423
10662
|
graph.assertActive();
|
|
10424
10663
|
throw error;
|
|
10425
10664
|
});
|
|
@@ -10546,10 +10785,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10546
10785
|
...prepared,
|
|
10547
10786
|
signature
|
|
10548
10787
|
});
|
|
10549
|
-
const activateResult = await
|
|
10550
|
-
targetHost,
|
|
10551
|
-
subDelegationSession.delegationHeader
|
|
10552
|
-
);
|
|
10788
|
+
const activateResult = await this.activateSignedSession(subDelegationSession, prepared.siwe, targetHost);
|
|
10553
10789
|
if (!activateResult.success) {
|
|
10554
10790
|
throw Object.assign(
|
|
10555
10791
|
new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10652,7 +10888,7 @@ import {
|
|
|
10652
10888
|
SpaceService as SpaceService2,
|
|
10653
10889
|
SpaceErrorCodes,
|
|
10654
10890
|
createSpaceService,
|
|
10655
|
-
parseSpaceUri as
|
|
10891
|
+
parseSpaceUri as parseSpaceUri3,
|
|
10656
10892
|
buildSpaceUri,
|
|
10657
10893
|
makePublicSpaceId as makePublicSpaceId2,
|
|
10658
10894
|
Space
|
|
@@ -10689,7 +10925,10 @@ export {
|
|
|
10689
10925
|
KVService3 as KVService,
|
|
10690
10926
|
ManifestValidationError3 as ManifestValidationError,
|
|
10691
10927
|
MemorySessionStorage,
|
|
10928
|
+
NodeIdentityMismatchError2 as NodeIdentityMismatchError,
|
|
10929
|
+
NodeNotRespondingError3 as NodeNotRespondingError,
|
|
10692
10930
|
NodeUserAuthorization,
|
|
10931
|
+
PartialNodeWriteError3 as PartialNodeWriteError,
|
|
10693
10932
|
PermissionNotInManifestError2 as PermissionNotInManifestError,
|
|
10694
10933
|
PrefixedKVService,
|
|
10695
10934
|
ProtocolMismatchError2 as ProtocolMismatchError,
|
|
@@ -10756,7 +10995,7 @@ export {
|
|
|
10756
10995
|
parseExpiry2 as parseExpiry,
|
|
10757
10996
|
parsePkhDid,
|
|
10758
10997
|
parseSecretCatalogKey,
|
|
10759
|
-
|
|
10998
|
+
parseSpaceUri3 as parseSpaceUri,
|
|
10760
10999
|
pkhDid3 as pkhDid,
|
|
10761
11000
|
principalDid,
|
|
10762
11001
|
principalDidEquals4 as principalDidEquals,
|