@tinycloud/node-sdk 3.12.0-beta.1 → 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-BtoDI9in.d.cts → NodeUserAuthorization-B_TFUlua.d.cts} +19 -53
- package/dist/{NodeUserAuthorization-BtoDI9in.d.ts → NodeUserAuthorization-B_TFUlua.d.ts} +19 -53
- package/dist/{TinyCloudNode-DLrINr98.d.ts → TinyCloudNode-Brcu8QoX.d.cts} +33 -59
- package/dist/{TinyCloudNode-C24728N_.d.cts → TinyCloudNode-DBJUnsMN.d.ts} +33 -59
- package/dist/{authority-BQN2Z3st.d.ts → authority-BFc5lesl.d.ts} +1 -1
- package/dist/{authority-DDdheOL6.d.cts → authority-DQ6R1HEG.d.cts} +1 -1
- package/dist/core.cjs +555 -320
- 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 +581 -324
- 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 +572 -337
- 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 +582 -325
- 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.cjs +48 -6
- package/dist/space-hosting.cjs.map +1 -1
- package/dist/space-hosting.d.cts +23 -8
- package/dist/space-hosting.d.ts +23 -8
- package/dist/space-hosting.js +48 -6
- package/dist/space-hosting.js.map +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");
|
|
755
|
-
}
|
|
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;
|
|
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);
|
|
765
769
|
}
|
|
766
|
-
assertSpaceCreationAllowed(verdict, this.primaryTinyCloudHost);
|
|
767
770
|
}
|
|
768
771
|
requireTinyCloudHosts() {
|
|
769
772
|
if (!this.tinycloudHosts || this.tinycloudHosts.length === 0) {
|
|
@@ -1193,23 +1196,60 @@ var NodeUserAuthorization = class {
|
|
|
1193
1196
|
* Create the space on the TinyCloud server (host delegation).
|
|
1194
1197
|
* This registers the user as the owner of the space.
|
|
1195
1198
|
*/
|
|
1196
|
-
async hostSpace(targetSpaceId, purpose, signal) {
|
|
1197
|
-
|
|
1199
|
+
async hostSpace(targetSpaceId, purpose, signal, assertCurrent, siweParameters) {
|
|
1200
|
+
const context = typeof targetSpaceId === "object" ? targetSpaceId : void 0;
|
|
1201
|
+
const address = context?.address ?? this._address;
|
|
1202
|
+
const chainId = context?.chainId ?? this._chainId;
|
|
1203
|
+
if (!address || chainId === void 0 || !this._tinyCloudSession) {
|
|
1198
1204
|
throw new Error("Must be signed in to host space");
|
|
1199
1205
|
}
|
|
1206
|
+
const checkCurrent = () => {
|
|
1207
|
+
signal?.throwIfAborted();
|
|
1208
|
+
assertCurrent?.();
|
|
1209
|
+
};
|
|
1210
|
+
checkCurrent();
|
|
1200
1211
|
await this.ensureTinyCloudHosts();
|
|
1201
|
-
|
|
1202
|
-
const
|
|
1212
|
+
checkCurrent();
|
|
1213
|
+
const host = context?.host ?? this.primaryTinyCloudHost;
|
|
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
|
+
}
|
|
1203
1240
|
const peerId = await fetchPeerId(host, spaceId, signal);
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1241
|
+
checkCurrent();
|
|
1242
|
+
const siwe = this.wasm.generateHostSIWEMessage(
|
|
1243
|
+
context && siweParameters ? siweParameters(peerId) : {
|
|
1244
|
+
address,
|
|
1245
|
+
chainId,
|
|
1246
|
+
domain: this.domain,
|
|
1247
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1248
|
+
spaceId,
|
|
1249
|
+
peerId
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
checkCurrent();
|
|
1213
1253
|
const signing = this.signMessage(siwe, purpose);
|
|
1214
1254
|
let signature;
|
|
1215
1255
|
if (!signal) {
|
|
@@ -1228,8 +1268,9 @@ var NodeUserAuthorization = class {
|
|
|
1228
1268
|
signal.removeEventListener("abort", onAbort);
|
|
1229
1269
|
}
|
|
1230
1270
|
}
|
|
1231
|
-
|
|
1271
|
+
checkCurrent();
|
|
1232
1272
|
const headers = this.wasm.siweToDelegationHeaders({ siwe, signature });
|
|
1273
|
+
checkCurrent();
|
|
1233
1274
|
return submitHostDelegation(host, headers, signal);
|
|
1234
1275
|
}
|
|
1235
1276
|
/**
|
|
@@ -1266,13 +1307,45 @@ var NodeUserAuthorization = class {
|
|
|
1266
1307
|
if (!this._tinyCloudSession) {
|
|
1267
1308
|
throw new Error("Must be signed in to ensure space exists");
|
|
1268
1309
|
}
|
|
1269
|
-
await this.ensureTinyCloudHosts();
|
|
1270
|
-
const host = this.primaryTinyCloudHost;
|
|
1271
1310
|
const primarySpaceId = this._tinyCloudSession.spaceId;
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
)
|
|
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");
|
|
1276
1349
|
this.recordActivationSkippedSpaces(result, primarySpaceId);
|
|
1277
1350
|
if (!result.success && result.status !== 404) {
|
|
1278
1351
|
throw Object.assign(
|
|
@@ -1291,43 +1364,36 @@ var NodeUserAuthorization = class {
|
|
|
1291
1364
|
*
|
|
1292
1365
|
* @throws Error if the creation handler fails or times out, or hosting fails
|
|
1293
1366
|
*/
|
|
1294
|
-
async ensurePrimarySpaceHosted(hosting) {
|
|
1295
|
-
if (hosting !== "not-hosted") {
|
|
1296
|
-
return;
|
|
1297
|
-
}
|
|
1367
|
+
async ensurePrimarySpaceHosted(hosting, signal) {
|
|
1298
1368
|
const session = this._tinyCloudSession;
|
|
1299
|
-
if (
|
|
1300
|
-
|
|
1301
|
-
}
|
|
1369
|
+
if (hosting !== "not-hosted") return;
|
|
1370
|
+
if (!session) throw new Error("Must be signed in to host the primary space");
|
|
1302
1371
|
const handler = this.spaceCreationHandler ?? (this.autoCreateSpace ? new AutoApproveSpaceCreationHandler() : void 0);
|
|
1303
|
-
if (!handler)
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
await this.ensureTinyCloudHosts();
|
|
1307
|
-
const host = this.primaryTinyCloudHost;
|
|
1308
|
-
const primarySpaceId = session.spaceId;
|
|
1309
|
-
const creationContext = {
|
|
1310
|
-
spaceId: primarySpaceId,
|
|
1311
|
-
address: this._address,
|
|
1312
|
-
chainId: this._chainId,
|
|
1313
|
-
host
|
|
1314
|
-
};
|
|
1372
|
+
if (!handler) return;
|
|
1315
1373
|
const { createPrimarySpace } = await import("./space-hosting.js");
|
|
1316
1374
|
await createPrimarySpace({
|
|
1317
1375
|
handler,
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1376
|
+
session,
|
|
1377
|
+
currentSession: () => this._tinyCloudSession,
|
|
1378
|
+
signerAddress: () => this.signer.getAddress(),
|
|
1379
|
+
resolveHost: async () => {
|
|
1380
|
+
await this.ensureTinyCloudHosts();
|
|
1381
|
+
return this.primaryTinyCloudHost;
|
|
1382
|
+
},
|
|
1383
|
+
domain: this.domain,
|
|
1384
|
+
guard: (context) => this.guardSpaceCreation(handler, context),
|
|
1385
|
+
host: (context, assertCurrent, siweParameters) => this.hostSpace(context, void 0, signal, assertCurrent, siweParameters),
|
|
1321
1386
|
onHosted: () => {
|
|
1322
1387
|
this._spaceHosting = "hosted";
|
|
1323
1388
|
},
|
|
1324
|
-
activate: () => activateSessionWithHost(host, session.delegationHeader)
|
|
1389
|
+
activate: (context) => activateSessionWithHost(context.host, session.delegationHeader),
|
|
1390
|
+
signal
|
|
1325
1391
|
});
|
|
1326
1392
|
}
|
|
1327
1393
|
/** {@link assertMayCreateSpace}, reporting a refusal to the creation handler before rethrowing it. */
|
|
1328
1394
|
async guardSpaceCreation(handler, context) {
|
|
1329
1395
|
try {
|
|
1330
|
-
await this.assertMayCreateSpace();
|
|
1396
|
+
await this.assertMayCreateSpace(context.spaceId, context.host);
|
|
1331
1397
|
} catch (error) {
|
|
1332
1398
|
const failure = error instanceof Error ? error : new Error(String(error));
|
|
1333
1399
|
handler.onSpaceCreationFailed?.(context, failure);
|
|
@@ -2481,6 +2547,7 @@ import {
|
|
|
2481
2547
|
decodeBase64Url,
|
|
2482
2548
|
principalDidEquals as principalDidEquals2
|
|
2483
2549
|
} from "@tinycloud/sdk-core";
|
|
2550
|
+
import { routingFailure as routingFailure2 } from "@tinycloud/sdk-services";
|
|
2484
2551
|
var RuntimeDelegationRejectedError = class extends Error {
|
|
2485
2552
|
constructor(reason, message) {
|
|
2486
2553
|
super(message);
|
|
@@ -2507,7 +2574,8 @@ var SERVICE_BY_SHORT_NAME = {
|
|
|
2507
2574
|
kv: "tinycloud.kv",
|
|
2508
2575
|
sql: "tinycloud.sql",
|
|
2509
2576
|
duckdb: "tinycloud.duckdb",
|
|
2510
|
-
hooks: "tinycloud.hooks"
|
|
2577
|
+
hooks: "tinycloud.hooks",
|
|
2578
|
+
capabilities: "tinycloud.capabilities"
|
|
2511
2579
|
};
|
|
2512
2580
|
function authorizationWithoutBearer(authorization) {
|
|
2513
2581
|
return authorization.replace(/^Bearer /i, "");
|
|
@@ -2638,8 +2706,8 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
2638
2706
|
));
|
|
2639
2707
|
continue;
|
|
2640
2708
|
}
|
|
2641
|
-
const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/(
|
|
2642
|
-
if (!match) {
|
|
2709
|
+
const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/([^/]+)(?:\/|$)(.*)$/.exec(resource);
|
|
2710
|
+
if (!match || !Object.prototype.hasOwnProperty.call(SERVICE_BY_SHORT_NAME, match[3])) {
|
|
2643
2711
|
throw new Error(
|
|
2644
2712
|
`Validated runtime delegation has an unsupported signed resource '${resource}'.`
|
|
2645
2713
|
);
|
|
@@ -2784,13 +2852,8 @@ function resourcesMatch(actual, expected) {
|
|
|
2784
2852
|
return JSON.stringify(actual.map(comparable)) === JSON.stringify(expected.map(comparable));
|
|
2785
2853
|
}
|
|
2786
2854
|
async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
reject(
|
|
2790
|
-
"host_mismatch",
|
|
2791
|
-
`Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
|
|
2792
|
-
);
|
|
2793
|
-
}
|
|
2855
|
+
let host = normalizedHost(options.host);
|
|
2856
|
+
const differentHost = delegation.host !== void 0 && normalizedHost(delegation.host) !== host;
|
|
2794
2857
|
if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
|
|
2795
2858
|
reject("authority_invalid", "Runtime delegation has an invalid expiry.");
|
|
2796
2859
|
}
|
|
@@ -2834,6 +2897,13 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2834
2897
|
if (options.authorize !== void 0 && !options.authorize(signed.permissions)) {
|
|
2835
2898
|
throw new Error("Runtime delegation is not authorized for this runtime.");
|
|
2836
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
|
+
}
|
|
2837
2907
|
const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
|
|
2838
2908
|
if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
|
|
2839
2909
|
reject(
|
|
@@ -2877,6 +2947,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2877
2947
|
await node.useRuntimeDelegation(installedCandidate);
|
|
2878
2948
|
}
|
|
2879
2949
|
} catch (error) {
|
|
2950
|
+
if (routingFailure2(error)) throw error;
|
|
2880
2951
|
reject(
|
|
2881
2952
|
activationRejectionReason(error),
|
|
2882
2953
|
error instanceof Error ? error.message : String(error)
|
|
@@ -2895,6 +2966,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2895
2966
|
"Runtime delegation activation installed authority that differs from the validated authority."
|
|
2896
2967
|
);
|
|
2897
2968
|
}
|
|
2969
|
+
node.refreshRegistryForCapabilities?.(signed.permissions);
|
|
2898
2970
|
return {
|
|
2899
2971
|
cid,
|
|
2900
2972
|
delegation: installed,
|
|
@@ -2958,6 +3030,15 @@ function deserializeDelegation(data) {
|
|
|
2958
3030
|
}
|
|
2959
3031
|
|
|
2960
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";
|
|
2961
3042
|
import { generateNonce, SiweMessage as SiweMessage3 } from "siwe";
|
|
2962
3043
|
import { didKeyFromEd25519PublicKey } from "@tinycloud/share-envelope";
|
|
2963
3044
|
import {
|
|
@@ -3017,9 +3098,15 @@ import {
|
|
|
3017
3098
|
verifyDidKeyEd25519Signature,
|
|
3018
3099
|
canonicalizeAddress as canonicalizeAddress2,
|
|
3019
3100
|
pkhDid as pkhDid2,
|
|
3020
|
-
|
|
3021
|
-
|
|
3101
|
+
parseSpaceUri as parseSpaceUri2,
|
|
3102
|
+
resolveRegistryResource as resolveRegistryResource2,
|
|
3103
|
+
warnRegistryDefaultNode as warnRegistryDefaultNode2,
|
|
3104
|
+
NodeNotFoundError as NodeNotFoundError2,
|
|
3105
|
+
exactHttpOrigin as exactHttpOrigin2,
|
|
3022
3106
|
createInMemoryRegistrySequenceStore as createInMemoryRegistrySequenceStore2,
|
|
3107
|
+
createInMemoryRegistryCacheStore as createInMemoryRegistryCacheStore2,
|
|
3108
|
+
refreshOwnerRegistry,
|
|
3109
|
+
registryOwnerKeyV2,
|
|
3023
3110
|
publishLocationRecord,
|
|
3024
3111
|
principalDidEquals as principalDidEquals3,
|
|
3025
3112
|
parseNetworkId as parseNetworkId2,
|
|
@@ -4037,6 +4124,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4037
4124
|
* ```
|
|
4038
4125
|
*/
|
|
4039
4126
|
constructor(config = {}) {
|
|
4127
|
+
this.registryRefreshOwners = /* @__PURE__ */ new Set();
|
|
4040
4128
|
this.signer = null;
|
|
4041
4129
|
this.auth = null;
|
|
4042
4130
|
this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
|
|
@@ -4200,8 +4288,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4200
4288
|
this.config = {
|
|
4201
4289
|
...config,
|
|
4202
4290
|
host: config.host ?? DEFAULT_HOST,
|
|
4291
|
+
registryPolicy: "fail-closed",
|
|
4203
4292
|
// One store for every authorization this client creates.
|
|
4204
|
-
registrySequenceStore: config.registrySequenceStore ?? createInMemoryRegistrySequenceStore2()
|
|
4293
|
+
registrySequenceStore: config.registrySequenceStore ?? createInMemoryRegistrySequenceStore2(),
|
|
4294
|
+
registryCacheStore: config.registryCacheStore ?? createInMemoryRegistryCacheStore2()
|
|
4205
4295
|
};
|
|
4206
4296
|
if (this.config.replication?.enabled) {
|
|
4207
4297
|
const runtime = getNodeReplicationLoaders().runtime().then(({ ReplicationRuntime }) => {
|
|
@@ -4448,6 +4538,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4448
4538
|
return "unknown";
|
|
4449
4539
|
}
|
|
4450
4540
|
}
|
|
4541
|
+
/** Run the existing primary-space creation flow for the restored session. */
|
|
4542
|
+
async ensurePrimarySpaceHosted(hosting) {
|
|
4543
|
+
const graph = this._serviceGraph;
|
|
4544
|
+
const epoch = this.sessionEpoch;
|
|
4545
|
+
graph.assertActive();
|
|
4546
|
+
this.assertSessionEpoch(epoch, "Space hosting");
|
|
4547
|
+
const auth = this.auth;
|
|
4548
|
+
if (!auth || !this.session) return;
|
|
4549
|
+
await auth.ensurePrimarySpaceHosted(hosting, graph.signal);
|
|
4550
|
+
graph.assertActive();
|
|
4551
|
+
this.assertSessionEpoch(epoch, "Space hosting");
|
|
4552
|
+
}
|
|
4451
4553
|
/**
|
|
4452
4554
|
* The node's last answer for the primary space: recorded at sign-in from the
|
|
4453
4555
|
* activation, or by {@link checkSpaceHosting}. `unknown` after a restore until
|
|
@@ -4493,7 +4595,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4493
4595
|
// A session saved but never registered on this node is refused: it registers once.
|
|
4494
4596
|
activate: (signal) => {
|
|
4495
4597
|
assertCurrent();
|
|
4496
|
-
|
|
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);
|
|
4497
4603
|
}
|
|
4498
4604
|
});
|
|
4499
4605
|
assertCurrent();
|
|
@@ -4629,14 +4735,38 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4629
4735
|
get requests() {
|
|
4630
4736
|
return this._requestLog === void 0 ? void 0 : readOnlyRequestLog(this._requestLog);
|
|
4631
4737
|
}
|
|
4632
|
-
|
|
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
|
+
);
|
|
4633
4766
|
return new ServiceGraphLifetime(
|
|
4634
4767
|
this.invokeWithRuntimePermissions,
|
|
4635
4768
|
this.invokeAnyWithRuntimePermissions,
|
|
4636
|
-
|
|
4637
|
-
// signal while preserving the SDK's existing injectable global fetch
|
|
4638
|
-
// behavior (including adapters that install it after construction).
|
|
4639
|
-
(url, init) => globalThis.fetch(url, init),
|
|
4769
|
+
routedFetch,
|
|
4640
4770
|
(context) => {
|
|
4641
4771
|
this.storageFull.observe(context);
|
|
4642
4772
|
if (this._requestLog) observeReplicaReads(context, this._requestLog);
|
|
@@ -4644,6 +4774,75 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4644
4774
|
this._requestLog
|
|
4645
4775
|
);
|
|
4646
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
|
+
}
|
|
4647
4846
|
syncResolvedHostFromAuth() {
|
|
4648
4847
|
const host = this.auth?.hosts[0];
|
|
4649
4848
|
if (host) {
|
|
@@ -4757,6 +4956,29 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4757
4956
|
const session = this.currentTinyCloudSession();
|
|
4758
4957
|
return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
|
|
4759
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
|
+
}
|
|
4760
4982
|
/** Normalize signed capabilities without collapsing nested encryption into raw authority. */
|
|
4761
4983
|
projectSignedRecapCapabilities(siwe) {
|
|
4762
4984
|
const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
|
|
@@ -5188,6 +5410,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5188
5410
|
const primarySession = this.currentTinyCloudSession();
|
|
5189
5411
|
if (primarySession) {
|
|
5190
5412
|
this.registerPrimarySessionGrant(primarySession);
|
|
5413
|
+
this.refreshRegistryOwner(pkhDid2(primarySession.address, primarySession.chainId));
|
|
5414
|
+
this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
|
|
5191
5415
|
if (this.auth?.spaceHosting === "unknown") {
|
|
5192
5416
|
await this.checkSpaceHosting();
|
|
5193
5417
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
@@ -5410,8 +5634,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5410
5634
|
const primarySession = auth.tinyCloudSession;
|
|
5411
5635
|
const defaultSpaceId = this.ownedSpaceId("default");
|
|
5412
5636
|
const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === BOOTSTRAP_SESSION_REQUESTS.default;
|
|
5413
|
-
|
|
5414
|
-
await this.assertMayCreateSpace();
|
|
5637
|
+
for (const step of steps) {
|
|
5638
|
+
if (step.kind === "host") await this.assertMayCreateSpace(step.spaceId);
|
|
5415
5639
|
}
|
|
5416
5640
|
for (const step of steps) {
|
|
5417
5641
|
if (step.kind !== "session") continue;
|
|
@@ -5460,10 +5684,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5460
5684
|
if (!session) {
|
|
5461
5685
|
throw new Error(`Missing bootstrap session for ${step.space}`);
|
|
5462
5686
|
}
|
|
5463
|
-
const
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
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");
|
|
5467
5701
|
graph.assertActive();
|
|
5468
5702
|
if (!activated.success || activated.skipped?.includes(step.spaceId)) {
|
|
5469
5703
|
throw Object.assign(
|
|
@@ -6046,6 +6280,13 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6046
6280
|
if (!session) {
|
|
6047
6281
|
throw new Error("Owned space hosting requires an active session");
|
|
6048
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
|
+
}
|
|
6049
6290
|
if (this.confirmedHostedSpaceIds.has(spaceId) || this.hostedOwnedSpaceIds.has(spaceId)) {
|
|
6050
6291
|
return;
|
|
6051
6292
|
}
|
|
@@ -6064,7 +6305,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6064
6305
|
{ cause: activation }
|
|
6065
6306
|
);
|
|
6066
6307
|
}
|
|
6067
|
-
await this.assertMayCreateSpace();
|
|
6308
|
+
await this.assertMayCreateSpace(spaceId);
|
|
6068
6309
|
if (signal?.aborted) throw signal.reason;
|
|
6069
6310
|
const created = await this.hostOwnedSpaceOnce(spaceId, signal);
|
|
6070
6311
|
if (!created.success) {
|
|
@@ -6125,17 +6366,19 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6125
6366
|
{ cause: hosted }
|
|
6126
6367
|
);
|
|
6127
6368
|
}
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
if (!activation.success || activation.skipped?.includes(spaceId)) {
|
|
6133
|
-
throw Object.assign(
|
|
6134
|
-
new Error(
|
|
6135
|
-
`Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
|
|
6136
|
-
),
|
|
6137
|
-
{ cause: activation }
|
|
6369
|
+
if (!this.auth.registryRecordPresent) {
|
|
6370
|
+
const activation = await activateSessionWithHost2(
|
|
6371
|
+
host,
|
|
6372
|
+
session.delegationHeader
|
|
6138
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
|
+
}
|
|
6139
6382
|
}
|
|
6140
6383
|
if (this.auth?.tinyCloudSession === session) this.hostedOwnedSpaceIds.add(spaceId);
|
|
6141
6384
|
try {
|
|
@@ -6179,11 +6422,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6179
6422
|
* session's recap doesn't cover it and the registry has no record; the host
|
|
6180
6423
|
* SIWE is idempotent server-side. Must be called after {@link signIn}.
|
|
6181
6424
|
*
|
|
6182
|
-
*
|
|
6183
|
-
*
|
|
6184
|
-
*
|
|
6185
|
-
* verdict. {@link hostOwnedSpace} itself is an explicit action and is not
|
|
6186
|
-
* 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.
|
|
6187
6428
|
*
|
|
6188
6429
|
* @param name - The owned space name (e.g. `"secrets"`).
|
|
6189
6430
|
* @returns The hosted owned-space URI.
|
|
@@ -6193,27 +6434,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6193
6434
|
throw new Error("Not signed in. Call signIn() first.");
|
|
6194
6435
|
}
|
|
6195
6436
|
const spaceId = this.ownedSpaceId(name);
|
|
6437
|
+
await this.assertMayCreateSpace(spaceId);
|
|
6196
6438
|
if (this.hostedOwnedSpaceIds.has(spaceId) || await this.isOwnedSpaceRegistered(spaceId)) {
|
|
6197
6439
|
return spaceId;
|
|
6198
6440
|
}
|
|
6199
|
-
await this.assertMayCreateSpace();
|
|
6200
6441
|
return this.hostOwnedSpace(name);
|
|
6201
6442
|
}
|
|
6202
|
-
/**
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
* {@link ensurePublicSpace}. Only `registryPolicy: "fail-closed"` consults
|
|
6206
|
-
* the registry ({@link NodeUserAuthorization.assertMayCreateSpace}); the
|
|
6207
|
-
* default policy performs no lookup.
|
|
6208
|
-
*/
|
|
6209
|
-
async assertMayCreateSpace() {
|
|
6210
|
-
if (this.config.registryPolicy !== "fail-closed") return;
|
|
6211
|
-
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");
|
|
6212
6446
|
}
|
|
6213
6447
|
/** Registry-policy settings shared by every authorization this client creates. */
|
|
6214
6448
|
registryPolicyConfig() {
|
|
6215
|
-
const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound } = this.config;
|
|
6216
|
-
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 };
|
|
6217
6451
|
}
|
|
6218
6452
|
/**
|
|
6219
6453
|
* Submit the host delegation for an owned space, sharing one in-flight
|
|
@@ -6419,12 +6653,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6419
6653
|
}
|
|
6420
6654
|
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
6421
6655
|
}
|
|
6422
|
-
const
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
sessionData.chainId
|
|
6426
|
-
);
|
|
6427
|
-
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");
|
|
6428
6659
|
let restoredNodeFeatures;
|
|
6429
6660
|
let replicaProtocolMismatch = false;
|
|
6430
6661
|
if (this.config.replication?.enabled) {
|
|
@@ -6541,7 +6772,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6541
6772
|
this.sessionManager = stagedManager;
|
|
6542
6773
|
this.sessionKeyId = stagedKeyId;
|
|
6543
6774
|
this.sessionKeyJwk = stagedJwk;
|
|
6544
|
-
|
|
6775
|
+
this.config.host = stagedHost;
|
|
6545
6776
|
if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
|
|
6546
6777
|
this.storageFull.clear();
|
|
6547
6778
|
this._requestLog?.clear();
|
|
@@ -6608,6 +6839,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6608
6839
|
if (attempt === this.latestSessionAttempt) await this.endSession();
|
|
6609
6840
|
this.assertSessionEpoch(epoch, "Session restore");
|
|
6610
6841
|
}
|
|
6842
|
+
if (this.config.replication?.enabled) {
|
|
6843
|
+
if (stagedAddress !== void 0) this.refreshRegistryOwner(pkhDid2(stagedAddress, stagedChainId));
|
|
6844
|
+
this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
|
|
6845
|
+
}
|
|
6611
6846
|
}
|
|
6612
6847
|
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
6613
6848
|
if (!session?.siwe) return { cache: void 0, grants: [] };
|
|
@@ -6656,7 +6891,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6656
6891
|
};
|
|
6657
6892
|
}
|
|
6658
6893
|
stageRestoredServiceGraph(input) {
|
|
6659
|
-
const graph = this.createServiceGraphLifetime();
|
|
6894
|
+
const graph = this.createServiceGraphLifetime(input.serviceSession);
|
|
6660
6895
|
const core = this.auth ? new TinyCloud(this.auth, {
|
|
6661
6896
|
invokeAny: graph.invokeAny,
|
|
6662
6897
|
telemetry: this.config.telemetry,
|
|
@@ -6881,56 +7116,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6881
7116
|
spaceService.updateConfig({ sharingService });
|
|
6882
7117
|
return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
|
|
6883
7118
|
}
|
|
6884
|
-
/**
|
|
6885
|
-
* Resolve the host a restored session should target.
|
|
6886
|
-
*
|
|
6887
|
-
* Mirrors fresh sign-in host resolution but for the restore path:
|
|
6888
|
-
* an explicit/pinned host always wins, then the hosts the session was
|
|
6889
|
-
* persisted with, then a lazy registry/fallback resolution for sessions
|
|
6890
|
-
* that predate the persisted `tinycloudHosts` field. Returns `undefined`
|
|
6891
|
-
* only when there's nothing to resolve from (no explicit host, no
|
|
6892
|
-
* persisted hosts, and no address/chainId) — in which case the existing
|
|
6893
|
-
* `config.host` (default) is left in place.
|
|
6894
|
-
*
|
|
6895
|
-
* Resolution failures are surfaced, not swallowed: a genuinely broken
|
|
6896
|
-
* registry lookup throws rather than silently falling back to a wrong host.
|
|
6897
|
-
*/
|
|
6898
|
-
async resolveRestoredHost(persistedHosts, address, chainId) {
|
|
6899
|
-
if (this.explicitHost) {
|
|
6900
|
-
return this.explicitHost;
|
|
6901
|
-
}
|
|
6902
|
-
if (persistedHosts && persistedHosts.length > 0) {
|
|
6903
|
-
return persistedHosts[0];
|
|
6904
|
-
}
|
|
6905
|
-
if (address === void 0 || chainId === void 0) {
|
|
6906
|
-
return void 0;
|
|
6907
|
-
}
|
|
6908
|
-
if (this.config.registryPolicy === "fail-closed") {
|
|
6909
|
-
const resolved2 = await resolveFailClosedTinyCloudHosts2(address, {
|
|
6910
|
-
registryUrl: this.config.tinycloudRegistryUrl,
|
|
6911
|
-
registryChainId: this.config.registryChainId,
|
|
6912
|
-
newUserHost: this.config.newUserHost,
|
|
6913
|
-
sequenceStore: this.config.registrySequenceStore,
|
|
6914
|
-
onNodeNotFound: this.config.onNodeNotFound,
|
|
6915
|
-
autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
|
|
6916
|
-
localNodeUrl: this.config.localNodeUrl,
|
|
6917
|
-
localLinkName: this.config.localLinkName,
|
|
6918
|
-
expectedNodeDid: this.config.expectedNodeDid,
|
|
6919
|
-
localNodeIdentityStore: this.config.localNodeIdentityStore
|
|
6920
|
-
});
|
|
6921
|
-
return resolved2.hosts[0];
|
|
6922
|
-
}
|
|
6923
|
-
const resolved = await resolveTinyCloudHosts2(pkhDid2(address, chainId), {
|
|
6924
|
-
registryUrl: this.config.tinycloudRegistryUrl,
|
|
6925
|
-
fallbackHosts: this.config.tinycloudFallbackHosts,
|
|
6926
|
-
autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
|
|
6927
|
-
localNodeUrl: this.config.localNodeUrl,
|
|
6928
|
-
localLinkName: this.config.localLinkName,
|
|
6929
|
-
expectedNodeDid: this.config.expectedNodeDid,
|
|
6930
|
-
localNodeIdentityStore: this.config.localNodeIdentityStore
|
|
6931
|
-
});
|
|
6932
|
-
return resolved.hosts[0];
|
|
6933
|
-
}
|
|
6934
7119
|
/**
|
|
6935
7120
|
* Resolve the currently-active TinyCloudSession, preferring the auth
|
|
6936
7121
|
* layer's value (wallet mode) and falling back to the node-level
|
|
@@ -7405,10 +7590,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7405
7590
|
* second time. Only a value recorded for this exact host qualifies; older
|
|
7406
7591
|
* nodes that omit `nodeId` fall through to the fetch.
|
|
7407
7592
|
*/
|
|
7408
|
-
async fetchNodeId() {
|
|
7409
|
-
const cached = this.auth?.nodeIdForHost(
|
|
7593
|
+
async fetchNodeId(host = this.config.host) {
|
|
7594
|
+
const cached = this.auth?.nodeIdForHost(host);
|
|
7410
7595
|
if (cached) return cached;
|
|
7411
|
-
const response = await fetch(`${
|
|
7596
|
+
const response = await fetch(`${host}/info`);
|
|
7412
7597
|
if (!response.ok) {
|
|
7413
7598
|
throw await httpResponseError(response, "Failed to fetch node info");
|
|
7414
7599
|
}
|
|
@@ -7456,10 +7641,24 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7456
7641
|
)
|
|
7457
7642
|
};
|
|
7458
7643
|
}
|
|
7459
|
-
async
|
|
7460
|
-
const
|
|
7461
|
-
|
|
7462
|
-
);
|
|
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
|
+
}
|
|
7463
7662
|
if (response.status === 404) return null;
|
|
7464
7663
|
if (!response.ok) {
|
|
7465
7664
|
throw await httpResponseError(response, `Failed to fetch encryption network ${networkId}`);
|
|
@@ -7469,18 +7668,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7469
7668
|
}
|
|
7470
7669
|
createEncryptionService(binding) {
|
|
7471
7670
|
const graph = binding?.graph ?? this._serviceGraph;
|
|
7472
|
-
const host = binding?.host ?? this.config.host;
|
|
7473
7671
|
const session = binding?.session ?? this.requireEncryptionSession();
|
|
7474
7672
|
const did = binding?.did ?? this.did;
|
|
7475
7673
|
const address = binding?.address ?? this._address;
|
|
7476
7674
|
const chainId = binding?.chainId ?? this._chainId;
|
|
7477
7675
|
const crypto2 = this.createEncryptionCrypto();
|
|
7478
|
-
const descriptorCache = /* @__PURE__ */ new Map();
|
|
7479
7676
|
const transport = {
|
|
7480
|
-
postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
|
|
7677
|
+
postDecrypt: async ({ targetNode, networkId, authorization, canonicalBody, signal }) => {
|
|
7481
7678
|
graph.assertActive();
|
|
7679
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7680
|
+
if (destination.nodeDid !== targetNode) throw new NodeIdentityMismatchError(destination.url, destination.nodeDid, targetNode);
|
|
7482
7681
|
const response = await graph.fetch(
|
|
7483
|
-
`${
|
|
7682
|
+
`${destination.url}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
|
|
7484
7683
|
{
|
|
7485
7684
|
method: "POST",
|
|
7486
7685
|
headers: {
|
|
@@ -7508,6 +7707,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7508
7707
|
}
|
|
7509
7708
|
};
|
|
7510
7709
|
return new EncryptionService({
|
|
7710
|
+
resolveTargetNode: async (networkId) => (await this.encryptionDestination(networkId)).nodeDid,
|
|
7511
7711
|
crypto: crypto2,
|
|
7512
7712
|
signer: {
|
|
7513
7713
|
signDecryptInvocation: async (input) => {
|
|
@@ -7531,18 +7731,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7531
7731
|
node: {
|
|
7532
7732
|
fetchByNetworkId: async (networkId) => {
|
|
7533
7733
|
graph.assertActive();
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
pending = this.fetchEncryptionNetworkAt(host, networkId, graph.fetch).then((descriptor2) => {
|
|
7537
|
-
if (descriptor2 === null) descriptorCache.delete(networkId);
|
|
7538
|
-
return descriptor2;
|
|
7539
|
-
}).catch((error) => {
|
|
7540
|
-
descriptorCache.delete(networkId);
|
|
7541
|
-
throw error;
|
|
7542
|
-
});
|
|
7543
|
-
descriptorCache.set(networkId, pending);
|
|
7544
|
-
}
|
|
7545
|
-
const descriptor = await pending;
|
|
7734
|
+
const destination = await this.encryptionDestination(networkId);
|
|
7735
|
+
const descriptor = await this.fetchEncryptionNetworkAt(destination.url, networkId, graph.fetch, graph.signal);
|
|
7546
7736
|
graph.assertActive();
|
|
7547
7737
|
return descriptor;
|
|
7548
7738
|
}
|
|
@@ -7550,13 +7740,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7550
7740
|
wellKnown: {
|
|
7551
7741
|
fetchWellKnown: async (principal, discoveryKey) => {
|
|
7552
7742
|
graph.assertActive();
|
|
7743
|
+
const destination = await this.encryptionDestination(this.getDefaultEncryptionNetworkId());
|
|
7744
|
+
if (this.auth?.registryRecordPresent) return null;
|
|
7553
7745
|
if (!address || !didPrincipalMatches2(principal, did)) {
|
|
7554
7746
|
return null;
|
|
7555
7747
|
}
|
|
7556
7748
|
const publicSpaceId = makePublicSpaceId(address, chainId);
|
|
7557
7749
|
const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
|
|
7558
7750
|
const response = await graph.fetch(
|
|
7559
|
-
`${
|
|
7751
|
+
`${destination.url}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
|
|
7560
7752
|
{ method: "GET" }
|
|
7561
7753
|
);
|
|
7562
7754
|
graph.assertActive();
|
|
@@ -7926,7 +8118,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7926
8118
|
const signature = await this.signer.signMessage(prepared.siwe);
|
|
7927
8119
|
assertOwnerGraphActive();
|
|
7928
8120
|
const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature });
|
|
7929
|
-
const activation = await
|
|
8121
|
+
const activation = await this.activateSignedSession(delegationSession, prepared.siwe, host);
|
|
7930
8122
|
assertOwnerGraphActive();
|
|
7931
8123
|
if (!activation.success) {
|
|
7932
8124
|
throw Object.assign(
|
|
@@ -8245,15 +8437,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8245
8437
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
8246
8438
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
8247
8439
|
return this.fetchEncryptionNetworkAt(
|
|
8248
|
-
this.
|
|
8440
|
+
(await this.encryptionDestination(networkId)).url,
|
|
8249
8441
|
networkId,
|
|
8250
8442
|
globalThis.fetch.bind(globalThis)
|
|
8251
8443
|
);
|
|
8252
8444
|
}
|
|
8253
8445
|
async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
|
|
8254
|
-
const targetNode = await this.fetchNodeId();
|
|
8255
8446
|
const ownerDid = this.did;
|
|
8256
8447
|
const networkId = this.getDefaultEncryptionNetworkId(name);
|
|
8448
|
+
const destination = await this.encryptionDestination(networkId);
|
|
8449
|
+
const targetNode = destination.nodeDid;
|
|
8257
8450
|
const body = {
|
|
8258
8451
|
name,
|
|
8259
8452
|
ownerDid,
|
|
@@ -8276,7 +8469,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8276
8469
|
action: NETWORK_CREATE_ACTION2,
|
|
8277
8470
|
facts
|
|
8278
8471
|
});
|
|
8279
|
-
const response = await fetch(`${
|
|
8472
|
+
const response = await this._serviceGraph.fetch(`${destination.url}/encryption/networks`, {
|
|
8280
8473
|
method: "POST",
|
|
8281
8474
|
headers: {
|
|
8282
8475
|
Authorization: signed.authorization,
|
|
@@ -8616,6 +8809,97 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8616
8809
|
}
|
|
8617
8810
|
return { recap: exactVerifiedRecap(verified), expiresAt };
|
|
8618
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
|
+
}
|
|
8619
8903
|
/**
|
|
8620
8904
|
* Install a portable runtime permission delegation into this SDK instance so
|
|
8621
8905
|
* matching service calls and downstream `delegateTo()` calls can use it.
|
|
@@ -8635,16 +8919,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8635
8919
|
`Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
|
|
8636
8920
|
);
|
|
8637
8921
|
}
|
|
8638
|
-
const targetHost = delegation.host ?? this.config.host;
|
|
8639
8922
|
let activateResult;
|
|
8640
8923
|
try {
|
|
8641
|
-
activateResult = await
|
|
8642
|
-
targetHost,
|
|
8643
|
-
delegation.delegationHeader,
|
|
8644
|
-
graph.signal
|
|
8645
|
-
);
|
|
8924
|
+
activateResult = await this.activateRoutedDelegation(delegation, graph.signal);
|
|
8646
8925
|
} catch (cause) {
|
|
8647
8926
|
graph.assertActive();
|
|
8927
|
+
if (routingFailure3(cause)) throw cause;
|
|
8648
8928
|
throw Object.assign(
|
|
8649
8929
|
new Error("The TinyCloud host could not be reached to activate the delegation."),
|
|
8650
8930
|
{ code: "ACTIVATION_TRANSPORT_FAILED", cause }
|
|
@@ -8800,11 +9080,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8800
9080
|
...prepared,
|
|
8801
9081
|
signature
|
|
8802
9082
|
});
|
|
8803
|
-
const
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
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) => {
|
|
8808
9092
|
graph.assertActive();
|
|
8809
9093
|
throw error;
|
|
8810
9094
|
});
|
|
@@ -8820,14 +9104,6 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8820
9104
|
{ cause: activateResult }
|
|
8821
9105
|
);
|
|
8822
9106
|
}
|
|
8823
|
-
const delegation = this.runtimeDelegationFromSession(
|
|
8824
|
-
delegatedSession,
|
|
8825
|
-
delegatedEntries,
|
|
8826
|
-
spaceId,
|
|
8827
|
-
captured,
|
|
8828
|
-
expiresAt,
|
|
8829
|
-
{ siwe: prepared.siwe, signature }
|
|
8830
|
-
);
|
|
8831
9107
|
graph.assertActive();
|
|
8832
9108
|
assertCapturedKey?.();
|
|
8833
9109
|
this.runtimePermissionGrants.push({
|
|
@@ -8972,7 +9248,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8972
9248
|
if (!publicSpaceId) {
|
|
8973
9249
|
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
8974
9250
|
}
|
|
8975
|
-
await this.assertMayCreateSpace();
|
|
9251
|
+
await this.assertMayCreateSpace(publicSpaceId);
|
|
8976
9252
|
await this.auth.hostPublicSpace(publicSpaceId);
|
|
8977
9253
|
const kvActions = [KV3.PUT, KV3.GET, KV3.DEL, KV3.LIST, KV3.METADATA];
|
|
8978
9254
|
const abilities = { kv: { "": kvActions } };
|
|
@@ -8995,10 +9271,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8995
9271
|
...prepared,
|
|
8996
9272
|
signature
|
|
8997
9273
|
});
|
|
8998
|
-
const activateResult = await
|
|
8999
|
-
this.config.host,
|
|
9000
|
-
delegationSession.delegationHeader
|
|
9001
|
-
);
|
|
9274
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
9002
9275
|
if (!activateResult.success) {
|
|
9003
9276
|
throw Object.assign(
|
|
9004
9277
|
new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -9548,10 +9821,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9548
9821
|
if (onPrepared) {
|
|
9549
9822
|
await onPrepared(delegation);
|
|
9550
9823
|
}
|
|
9551
|
-
const activateResult = await
|
|
9552
|
-
this.config.host,
|
|
9553
|
-
delegationHeader
|
|
9554
|
-
);
|
|
9824
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9555
9825
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9556
9826
|
return delegation;
|
|
9557
9827
|
}
|
|
@@ -9585,10 +9855,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9585
9855
|
if (onPrepared) {
|
|
9586
9856
|
await onPrepared(delegation);
|
|
9587
9857
|
}
|
|
9588
|
-
const activateResult = await
|
|
9589
|
-
targetHost,
|
|
9590
|
-
delegationHeader
|
|
9591
|
-
);
|
|
9858
|
+
const activateResult = await this.activateRoutedDelegation(delegation);
|
|
9592
9859
|
assertDelegationActivated(activateResult, delegation, requireActivation);
|
|
9593
9860
|
return delegation;
|
|
9594
9861
|
}
|
|
@@ -10240,10 +10507,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10240
10507
|
...prepared,
|
|
10241
10508
|
signature
|
|
10242
10509
|
});
|
|
10243
|
-
const activateResult = await
|
|
10244
|
-
this.config.host,
|
|
10245
|
-
delegationSession.delegationHeader
|
|
10246
|
-
);
|
|
10510
|
+
const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
|
|
10247
10511
|
if (!activateResult.success) {
|
|
10248
10512
|
throw Object.assign(
|
|
10249
10513
|
new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10288,10 +10552,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10288
10552
|
...publicPrepared,
|
|
10289
10553
|
signature: publicSignature
|
|
10290
10554
|
});
|
|
10291
|
-
const publicActivateResult = await
|
|
10292
|
-
this.config.host,
|
|
10293
|
-
publicSession.delegationHeader
|
|
10294
|
-
);
|
|
10555
|
+
const publicActivateResult = await this.activateSignedSession(publicSession, publicPrepared.siwe);
|
|
10295
10556
|
if (publicActivateResult.success) {
|
|
10296
10557
|
result.publicDelegation = {
|
|
10297
10558
|
cid: publicSession.delegationCid,
|
|
@@ -10397,11 +10658,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10397
10658
|
...prepared,
|
|
10398
10659
|
signature
|
|
10399
10660
|
});
|
|
10400
|
-
const activateResult = await
|
|
10401
|
-
targetHost,
|
|
10402
|
-
invokerSession.delegationHeader,
|
|
10403
|
-
graph.signal
|
|
10404
|
-
).catch((error) => {
|
|
10661
|
+
const activateResult = await this.activateSignedSession(invokerSession, prepared.siwe, targetHost, graph.signal).catch((error) => {
|
|
10405
10662
|
graph.assertActive();
|
|
10406
10663
|
throw error;
|
|
10407
10664
|
});
|
|
@@ -10528,10 +10785,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
10528
10785
|
...prepared,
|
|
10529
10786
|
signature
|
|
10530
10787
|
});
|
|
10531
|
-
const activateResult = await
|
|
10532
|
-
targetHost,
|
|
10533
|
-
subDelegationSession.delegationHeader
|
|
10534
|
-
);
|
|
10788
|
+
const activateResult = await this.activateSignedSession(subDelegationSession, prepared.siwe, targetHost);
|
|
10535
10789
|
if (!activateResult.success) {
|
|
10536
10790
|
throw Object.assign(
|
|
10537
10791
|
new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
|
|
@@ -10634,7 +10888,7 @@ import {
|
|
|
10634
10888
|
SpaceService as SpaceService2,
|
|
10635
10889
|
SpaceErrorCodes,
|
|
10636
10890
|
createSpaceService,
|
|
10637
|
-
parseSpaceUri as
|
|
10891
|
+
parseSpaceUri as parseSpaceUri3,
|
|
10638
10892
|
buildSpaceUri,
|
|
10639
10893
|
makePublicSpaceId as makePublicSpaceId2,
|
|
10640
10894
|
Space
|
|
@@ -10671,7 +10925,10 @@ export {
|
|
|
10671
10925
|
KVService3 as KVService,
|
|
10672
10926
|
ManifestValidationError3 as ManifestValidationError,
|
|
10673
10927
|
MemorySessionStorage,
|
|
10928
|
+
NodeIdentityMismatchError2 as NodeIdentityMismatchError,
|
|
10929
|
+
NodeNotRespondingError3 as NodeNotRespondingError,
|
|
10674
10930
|
NodeUserAuthorization,
|
|
10931
|
+
PartialNodeWriteError3 as PartialNodeWriteError,
|
|
10675
10932
|
PermissionNotInManifestError2 as PermissionNotInManifestError,
|
|
10676
10933
|
PrefixedKVService,
|
|
10677
10934
|
ProtocolMismatchError2 as ProtocolMismatchError,
|
|
@@ -10738,7 +10995,7 @@ export {
|
|
|
10738
10995
|
parseExpiry2 as parseExpiry,
|
|
10739
10996
|
parsePkhDid,
|
|
10740
10997
|
parseSecretCatalogKey,
|
|
10741
|
-
|
|
10998
|
+
parseSpaceUri3 as parseSpaceUri,
|
|
10742
10999
|
pkhDid3 as pkhDid,
|
|
10743
11000
|
principalDid,
|
|
10744
11001
|
principalDidEquals4 as principalDidEquals,
|