@tinycloud/node-sdk 3.2.0-beta.2 → 3.2.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{core-DjLtgeVT.d.cts → WasmKeyProvider-DjfRu9aL.d.cts} +162 -2
- package/dist/{core-DjLtgeVT.d.ts → WasmKeyProvider-DjfRu9aL.d.ts} +162 -2
- package/dist/authority-mTQEgN56.d.cts +97 -0
- package/dist/authority-mTQEgN56.d.ts +97 -0
- package/dist/core.cjs +1049 -443
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +13 -3
- package/dist/core.d.ts +13 -3
- package/dist/core.js +725 -115
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +2348 -494
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +1996 -128
- package/dist/index.js.map +1 -1
- package/dist/replication/authority.cjs +278 -0
- package/dist/replication/authority.cjs.map +1 -0
- package/dist/replication/authority.d.cts +3 -0
- package/dist/replication/authority.d.ts +3 -0
- package/dist/replication/authority.js +257 -0
- package/dist/replication/authority.js.map +1 -0
- package/dist/replication/runtime.cjs +224 -0
- package/dist/replication/runtime.cjs.map +1 -0
- package/dist/replication/runtime.d.cts +3 -0
- package/dist/replication/runtime.d.ts +3 -0
- package/dist/replication/runtime.js +189 -0
- package/dist/replication/runtime.js.map +1 -0
- package/dist/runtime-DyYwHW8j.d.cts +64 -0
- package/dist/runtime-DyYwHW8j.d.ts +64 -0
- package/package.json +25 -3
package/dist/core.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
2
|
import { TinyCloud as TinyCloud2 } from "@tinycloud/sdk-core";
|
|
3
|
+
|
|
4
|
+
// src/replication/module-registry.ts
|
|
5
|
+
var nodeLoaders;
|
|
6
|
+
function registerReplicationLoaders(loaders) {
|
|
7
|
+
nodeLoaders = loaders;
|
|
8
|
+
}
|
|
9
|
+
function getNodeReplicationLoaders() {
|
|
10
|
+
if (nodeLoaders === void 0) {
|
|
11
|
+
throw new Error("Replication requires registering replication controller loaders.");
|
|
12
|
+
}
|
|
13
|
+
return nodeLoaders;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/core.ts
|
|
3
17
|
import {
|
|
4
18
|
SilentNotificationHandler as SilentNotificationHandler2,
|
|
5
19
|
AutoApproveSpaceCreationHandler as AutoApproveSpaceCreationHandler2,
|
|
@@ -16,7 +30,7 @@ import {
|
|
|
16
30
|
makePkhSpaceId as makePkhSpaceId2,
|
|
17
31
|
parsePkhDid,
|
|
18
32
|
pkhDid as pkhDid3,
|
|
19
|
-
principalDid,
|
|
33
|
+
principalDid as principalDid2,
|
|
20
34
|
principalDidEquals as principalDidEquals4,
|
|
21
35
|
parseCanonicalNetworkId,
|
|
22
36
|
TinyCloudDebugLogger,
|
|
@@ -247,7 +261,7 @@ import {
|
|
|
247
261
|
parseCanonicalRecapResource,
|
|
248
262
|
ACCOUNT_REGISTRY_SPACE,
|
|
249
263
|
ACCOUNT_MANIFEST_PERMISSIONS,
|
|
250
|
-
KV,
|
|
264
|
+
KV as KV2,
|
|
251
265
|
SQL,
|
|
252
266
|
DUCKDB,
|
|
253
267
|
CAPABILITIES,
|
|
@@ -255,6 +269,46 @@ import {
|
|
|
255
269
|
ENCRYPTION
|
|
256
270
|
} from "@tinycloud/sdk-core";
|
|
257
271
|
|
|
272
|
+
// src/replication/authority-sign-in.ts
|
|
273
|
+
import {
|
|
274
|
+
grantPathsForSelector,
|
|
275
|
+
KV,
|
|
276
|
+
isCapabilitySubset
|
|
277
|
+
} from "@tinycloud/sdk-core";
|
|
278
|
+
import { requiresSecretsOptIn } from "@tinycloud/sdk-services";
|
|
279
|
+
var KV_GET = KV.GET;
|
|
280
|
+
var KV_SYNC = KV.SYNC;
|
|
281
|
+
var KV_SERVICE = "tinycloud.kv";
|
|
282
|
+
function hasUnrestrictedGetCoverage(entries, spaceId, prefix) {
|
|
283
|
+
const requiredPaths = grantPathsForSelector(prefix);
|
|
284
|
+
return isCapabilitySubset(
|
|
285
|
+
requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, actions: [KV_GET] })),
|
|
286
|
+
[...entries]
|
|
287
|
+
).subset;
|
|
288
|
+
}
|
|
289
|
+
function augmentSignInEntriesWithReplication(input) {
|
|
290
|
+
const out = [];
|
|
291
|
+
const replication = input.replication;
|
|
292
|
+
if (replication === void 0) return out;
|
|
293
|
+
for (const prefix of replication.prefixes) {
|
|
294
|
+
if (requiresSecretsOptIn(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
for (const path of grantPathsForSelector(prefix)) {
|
|
301
|
+
out.push({
|
|
302
|
+
service: KV_SERVICE,
|
|
303
|
+
space: input.primarySpaceId,
|
|
304
|
+
path,
|
|
305
|
+
actions: [KV_GET, KV_SYNC]
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
|
|
258
312
|
// src/authorization/strategies.ts
|
|
259
313
|
import { createOpenKeyCallbackSigningStrategy } from "@tinycloud/sdk-core";
|
|
260
314
|
var defaultSignStrategy = { type: "auto-sign" };
|
|
@@ -330,7 +384,7 @@ var NodeUserAuthorization = class {
|
|
|
330
384
|
this.spacePrefix = config.spacePrefix ?? "default";
|
|
331
385
|
this.defaultActions = config.defaultActions ?? {
|
|
332
386
|
kv: {
|
|
333
|
-
"": [
|
|
387
|
+
"": [KV2.PUT, KV2.GET, KV2.DEL, KV2.LIST, KV2.METADATA]
|
|
334
388
|
},
|
|
335
389
|
sql: {
|
|
336
390
|
"": [SQL.READ, SQL.WRITE, SQL.SCHEMA, SQL.ADMIN, SQL.EXPORT]
|
|
@@ -368,6 +422,7 @@ var NodeUserAuthorization = class {
|
|
|
368
422
|
this.nonce = config.nonce;
|
|
369
423
|
this.siweConfig = config.siweConfig;
|
|
370
424
|
this.includeAccountRegistryPermissions = config.includeAccountRegistryPermissions ?? true;
|
|
425
|
+
this.replication = config.replication?.enabled === true ? config.replication : void 0;
|
|
371
426
|
this._manifest = config.manifest;
|
|
372
427
|
this._capabilityRequest = config.capabilityRequest;
|
|
373
428
|
this.sessionManager = config.sessionManager ?? this.wasm.createSessionManager();
|
|
@@ -576,6 +631,143 @@ var NodeUserAuthorization = class {
|
|
|
576
631
|
}
|
|
577
632
|
return makePkhSpaceId(address, chainId, space);
|
|
578
633
|
}
|
|
634
|
+
/**
|
|
635
|
+
* The plain-session primary abilities: `defaultActions` (the shared table)
|
|
636
|
+
* unless the primary space IS the account space, where account-registry
|
|
637
|
+
* permissions merge into a private clone so the shared table is never
|
|
638
|
+
* mutated. Shared between `resolveSignInCapabilities` and
|
|
639
|
+
* `signInPermissionInputs` so sign-in and `replicationSignInEntries`
|
|
640
|
+
* can't disagree (§4.6).
|
|
641
|
+
*/
|
|
642
|
+
primaryActionsForSignIn() {
|
|
643
|
+
const primaryActions = this.includeAccountRegistryPermissions && this.spacePrefix === ACCOUNT_REGISTRY_SPACE ? cloneAbilitiesMap(this.defaultActions) : this.defaultActions;
|
|
644
|
+
if (this.includeAccountRegistryPermissions && this.spacePrefix === ACCOUNT_REGISTRY_SPACE) {
|
|
645
|
+
const accountAbilities = resourceCapabilitiesToAbilitiesMap(
|
|
646
|
+
ACCOUNT_MANIFEST_PERMISSIONS
|
|
647
|
+
);
|
|
648
|
+
for (const [service, paths] of Object.entries(accountAbilities)) {
|
|
649
|
+
const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
|
|
650
|
+
for (const [path, actions] of Object.entries(paths)) {
|
|
651
|
+
const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
|
|
652
|
+
for (const action of actions) {
|
|
653
|
+
if (!existingActions.includes(action)) existingActions.push(action);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return primaryActions;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* The abilities a `spacePrefix: "secrets"` plain session actually requests
|
|
662
|
+
* for its primary space: the vault-secrets subtree only — the
|
|
663
|
+
* `defaultActions` root `get` is REPLACED, not merged (sign-in installs
|
|
664
|
+
* this map under the primary space id). Shared between
|
|
665
|
+
* `resolveSignInCapabilities` and `signInPermissionInputs` so the
|
|
666
|
+
* replication augmentation measures coverage against the effective
|
|
667
|
+
* pre-replication request, never the uninstalled `primaryActions` (§4.1).
|
|
668
|
+
*/
|
|
669
|
+
secretsPrimaryAbilities() {
|
|
670
|
+
return {
|
|
671
|
+
kv: {
|
|
672
|
+
"vault/secrets/": [
|
|
673
|
+
KV2.GET,
|
|
674
|
+
KV2.PUT,
|
|
675
|
+
KV2.DEL,
|
|
676
|
+
KV2.LIST,
|
|
677
|
+
KV2.METADATA
|
|
678
|
+
]
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* The effective sign-in request as `PermissionEntry`s plus the resolved
|
|
684
|
+
* primary space id — the inputs to the replication augmentation (§4.1).
|
|
685
|
+
* `resolveSignInCapabilities` merges the augmented entries into its
|
|
686
|
+
* abilities; `replicationSignInEntries()` returns them verbatim, so the
|
|
687
|
+
* entries the SIWE recap carries and the restore check sees are the same.
|
|
688
|
+
*/
|
|
689
|
+
signInPermissionInputs(address, chainId) {
|
|
690
|
+
const request = this.getCapabilityRequest();
|
|
691
|
+
if (request === void 0) {
|
|
692
|
+
const primarySpaceId2 = makePkhSpaceId(address, chainId, this.spacePrefix);
|
|
693
|
+
const effectivePrimary = this.spacePrefix === "secrets" ? this.secretsPrimaryAbilities() : this.primaryActionsForSignIn();
|
|
694
|
+
const kvPaths = effectivePrimary["kv"] ?? {};
|
|
695
|
+
return {
|
|
696
|
+
primarySpaceId: primarySpaceId2,
|
|
697
|
+
entries: Object.entries(kvPaths).map(([path, actions]) => ({
|
|
698
|
+
service: "tinycloud.kv",
|
|
699
|
+
space: primarySpaceId2,
|
|
700
|
+
path,
|
|
701
|
+
actions: [...actions]
|
|
702
|
+
}))
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const spaceResources = request.resources.filter(
|
|
706
|
+
(entry) => entry.service !== ENCRYPTION_PERMISSION_SERVICE
|
|
707
|
+
);
|
|
708
|
+
const primarySpaceName = spaceResources.find((entry) => entry.space !== "account")?.space ?? DEFAULT_MANIFEST_SPACE;
|
|
709
|
+
const primarySpaceId = this.resolveSpaceName(
|
|
710
|
+
primarySpaceName,
|
|
711
|
+
address,
|
|
712
|
+
chainId
|
|
713
|
+
);
|
|
714
|
+
return {
|
|
715
|
+
primarySpaceId,
|
|
716
|
+
entries: spaceResources.map((entry) => ({
|
|
717
|
+
service: entry.service,
|
|
718
|
+
space: this.resolveSpaceName(entry.space, address, chainId),
|
|
719
|
+
path: entry.path,
|
|
720
|
+
actions: [...entry.actions]
|
|
721
|
+
}))
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Merge replication augmentation entries into a space's abilities map
|
|
726
|
+
* without mutating shared tables (`defaultActions` may back the base map):
|
|
727
|
+
* the primary entry in `spaceAbilities` is replaced with a copy whose `kv`
|
|
728
|
+
* paths gain `get`/`sync`. Returns the merged map for `abilities`.
|
|
729
|
+
*/
|
|
730
|
+
mergeReplicationEntries(spaceAbilities, primarySpaceId, abilities, entries) {
|
|
731
|
+
var _a;
|
|
732
|
+
if (entries.length === 0) {
|
|
733
|
+
return abilities;
|
|
734
|
+
}
|
|
735
|
+
const base = spaceAbilities[primarySpaceId] ?? abilities;
|
|
736
|
+
const kv = {};
|
|
737
|
+
for (const [path, actions] of Object.entries(base["kv"] ?? {})) {
|
|
738
|
+
kv[path] = [...actions];
|
|
739
|
+
}
|
|
740
|
+
for (const entry of entries) {
|
|
741
|
+
const list = kv[_a = entry.path] ?? (kv[_a] = []);
|
|
742
|
+
for (const action of entry.actions) {
|
|
743
|
+
if (!list.includes(action)) list.push(action);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const merged = { ...base, kv };
|
|
747
|
+
spaceAbilities[primarySpaceId] = merged;
|
|
748
|
+
return merged;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* The replication entries this authorization's sign-in request carries
|
|
752
|
+
* for the signed-in or restored address (TC-858 §4.1/§4.6): the output of
|
|
753
|
+
* the same augmentation `resolveSignInCapabilities` runs — one
|
|
754
|
+
* `{tinycloud.kv, primarySpace, prefix, [get, sync]}` per configured
|
|
755
|
+
* prefix that already has an unrestricted covering `get` and passes the
|
|
756
|
+
* secrets opt-in gate. Empty when replication is off or no address is
|
|
757
|
+
* known. Pure.
|
|
758
|
+
*/
|
|
759
|
+
replicationSignInEntries() {
|
|
760
|
+
if (this.replication === void 0) return [];
|
|
761
|
+
const address = this._address ?? this._tinyCloudSession?.address;
|
|
762
|
+
const chainId = this._chainId ?? this._tinyCloudSession?.chainId;
|
|
763
|
+
if (address === void 0 || chainId === void 0) return [];
|
|
764
|
+
const { entries, primarySpaceId } = this.signInPermissionInputs(address, chainId);
|
|
765
|
+
return augmentSignInEntriesWithReplication({
|
|
766
|
+
entries,
|
|
767
|
+
primarySpaceId,
|
|
768
|
+
replication: this.replication
|
|
769
|
+
});
|
|
770
|
+
}
|
|
579
771
|
defaultEncryptionNetworkId(address, chainId) {
|
|
580
772
|
return `urn:tinycloud:encryption:${pkhDid(address, chainId)}:default`;
|
|
581
773
|
}
|
|
@@ -585,39 +777,25 @@ var NodeUserAuthorization = class {
|
|
|
585
777
|
const defaultNetworkId = this.defaultEncryptionNetworkId(address, chainId);
|
|
586
778
|
const primarySpaceId2 = makePkhSpaceId(address, chainId, this.spacePrefix);
|
|
587
779
|
const secretsSpaceId = makePkhSpaceId(address, chainId, "secrets");
|
|
588
|
-
const primaryActions = this.
|
|
780
|
+
const primaryActions = this.primaryActionsForSignIn();
|
|
589
781
|
const spaceAbilities2 = {
|
|
590
782
|
[primarySpaceId2]: primaryActions,
|
|
591
|
-
[secretsSpaceId]:
|
|
592
|
-
kv: {
|
|
593
|
-
"vault/secrets/": [
|
|
594
|
-
KV.GET,
|
|
595
|
-
KV.PUT,
|
|
596
|
-
KV.DEL,
|
|
597
|
-
KV.LIST,
|
|
598
|
-
KV.METADATA
|
|
599
|
-
]
|
|
600
|
-
}
|
|
601
|
-
}
|
|
783
|
+
[secretsSpaceId]: this.secretsPrimaryAbilities()
|
|
602
784
|
};
|
|
603
785
|
if (this.includeAccountRegistryPermissions && this.spacePrefix !== ACCOUNT_REGISTRY_SPACE) {
|
|
604
786
|
spaceAbilities2[makePkhSpaceId(address, chainId, ACCOUNT_REGISTRY_SPACE)] = resourceCapabilitiesToAbilitiesMap(ACCOUNT_MANIFEST_PERMISSIONS);
|
|
605
|
-
} else if (this.includeAccountRegistryPermissions) {
|
|
606
|
-
const accountAbilities = resourceCapabilitiesToAbilitiesMap(
|
|
607
|
-
ACCOUNT_MANIFEST_PERMISSIONS
|
|
608
|
-
);
|
|
609
|
-
for (const [service, paths] of Object.entries(accountAbilities)) {
|
|
610
|
-
const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
|
|
611
|
-
for (const [path, actions] of Object.entries(paths)) {
|
|
612
|
-
const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
|
|
613
|
-
for (const action of actions) {
|
|
614
|
-
if (!existingActions.includes(action)) existingActions.push(action);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
787
|
}
|
|
788
|
+
const mergedPrimary = this.mergeReplicationEntries(
|
|
789
|
+
spaceAbilities2,
|
|
790
|
+
primarySpaceId2,
|
|
791
|
+
primaryActions,
|
|
792
|
+
augmentSignInEntriesWithReplication({
|
|
793
|
+
...this.signInPermissionInputs(address, chainId),
|
|
794
|
+
replication: this.replication
|
|
795
|
+
})
|
|
796
|
+
);
|
|
619
797
|
return {
|
|
620
|
-
abilities:
|
|
798
|
+
abilities: mergedPrimary,
|
|
621
799
|
spaceId: primarySpaceId2,
|
|
622
800
|
spaceAbilities: spaceAbilities2,
|
|
623
801
|
rawAbilities: {
|
|
@@ -653,8 +831,17 @@ var NodeUserAuthorization = class {
|
|
|
653
831
|
for (const [space, abilities] of Object.entries(bySpace)) {
|
|
654
832
|
spaceAbilities[this.resolveSpaceName(space, address, chainId)] = abilities;
|
|
655
833
|
}
|
|
834
|
+
const mergedPrimaryAbilities = this.mergeReplicationEntries(
|
|
835
|
+
spaceAbilities,
|
|
836
|
+
primarySpaceId,
|
|
837
|
+
spaceAbilities[primarySpaceId] ?? resourceCapabilitiesToAbilitiesMap([]),
|
|
838
|
+
augmentSignInEntriesWithReplication({
|
|
839
|
+
...this.signInPermissionInputs(address, chainId),
|
|
840
|
+
replication: this.replication
|
|
841
|
+
})
|
|
842
|
+
);
|
|
656
843
|
return {
|
|
657
|
-
abilities:
|
|
844
|
+
abilities: mergedPrimaryAbilities,
|
|
658
845
|
spaceId: primarySpaceId,
|
|
659
846
|
spaceAbilities,
|
|
660
847
|
rawAbilities: Object.keys(rawAbilities).length > 0 ? rawAbilities : void 0
|
|
@@ -712,14 +899,14 @@ var NodeUserAuthorization = class {
|
|
|
712
899
|
* Create the space on the TinyCloud server (host delegation).
|
|
713
900
|
* This registers the user as the owner of the space.
|
|
714
901
|
*/
|
|
715
|
-
async hostSpace(targetSpaceId, purpose) {
|
|
902
|
+
async hostSpace(targetSpaceId, purpose, signal) {
|
|
716
903
|
if (!this._tinyCloudSession || !this._address || !this._chainId) {
|
|
717
904
|
throw new Error("Must be signed in to host space");
|
|
718
905
|
}
|
|
719
906
|
await this.ensureTinyCloudHosts();
|
|
720
907
|
const host = this.primaryTinyCloudHost;
|
|
721
908
|
const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
|
|
722
|
-
const peerId = await fetchPeerId(host, spaceId);
|
|
909
|
+
const peerId = await fetchPeerId(host, spaceId, signal);
|
|
723
910
|
const siwe = this.wasm.generateHostSIWEMessage({
|
|
724
911
|
address: this._address,
|
|
725
912
|
chainId: this._chainId,
|
|
@@ -728,9 +915,28 @@ var NodeUserAuthorization = class {
|
|
|
728
915
|
spaceId,
|
|
729
916
|
peerId
|
|
730
917
|
});
|
|
731
|
-
|
|
918
|
+
if (signal?.aborted) throw signal.reason;
|
|
919
|
+
const signing = this.signMessage(siwe, purpose);
|
|
920
|
+
let signature;
|
|
921
|
+
if (!signal) {
|
|
922
|
+
signature = await signing;
|
|
923
|
+
} else {
|
|
924
|
+
let rejectOnAbort;
|
|
925
|
+
const aborted = new Promise((_resolve, reject2) => {
|
|
926
|
+
rejectOnAbort = reject2;
|
|
927
|
+
});
|
|
928
|
+
const onAbort = () => rejectOnAbort(signal.reason);
|
|
929
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
930
|
+
if (signal.aborted) onAbort();
|
|
931
|
+
try {
|
|
932
|
+
signature = await Promise.race([signing, aborted]);
|
|
933
|
+
} finally {
|
|
934
|
+
signal.removeEventListener("abort", onAbort);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (signal?.aborted) throw signal.reason;
|
|
732
938
|
const headers = this.wasm.siweToDelegationHeaders({ siwe, signature });
|
|
733
|
-
return submitHostDelegation(host, headers);
|
|
939
|
+
return submitHostDelegation(host, headers, signal);
|
|
734
940
|
}
|
|
735
941
|
/**
|
|
736
942
|
* Create a specific space on the server via host delegation.
|
|
@@ -751,8 +957,8 @@ var NodeUserAuthorization = class {
|
|
|
751
957
|
* keeps its HTTP status (e.g. a typed 401/403) instead of collapsing to
|
|
752
958
|
* `false`.
|
|
753
959
|
*/
|
|
754
|
-
async hostOwnedSpaceResult(spaceId, purpose) {
|
|
755
|
-
return this.hostSpace(spaceId, purpose);
|
|
960
|
+
async hostOwnedSpaceResult(spaceId, purpose, signal) {
|
|
961
|
+
return this.hostSpace(spaceId, purpose, signal);
|
|
756
962
|
}
|
|
757
963
|
/**
|
|
758
964
|
* Ensure the user's space exists on the TinyCloud server.
|
|
@@ -2163,6 +2369,37 @@ function deserializeDelegation(data) {
|
|
|
2163
2369
|
};
|
|
2164
2370
|
}
|
|
2165
2371
|
|
|
2372
|
+
// src/replication/config.ts
|
|
2373
|
+
import { kvPrefixCovers } from "@tinycloud/sdk-services";
|
|
2374
|
+
function assertValidReplicationConfig(replication) {
|
|
2375
|
+
if (replication === void 0 || replication.enabled !== true) return;
|
|
2376
|
+
if (replication.storage === void 0 || replication.storage === null) {
|
|
2377
|
+
throw new TypeError("replication.enabled requires replication.storage");
|
|
2378
|
+
}
|
|
2379
|
+
if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {
|
|
2380
|
+
throw new TypeError("replication.prefixes must be a non-empty array");
|
|
2381
|
+
}
|
|
2382
|
+
for (const prefix of replication.prefixes) {
|
|
2383
|
+
if (typeof prefix !== "string" || prefix === "") {
|
|
2384
|
+
throw new TypeError("replication.prefixes must not contain an empty prefix");
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
for (const [index, prefix] of replication.prefixes.entries()) {
|
|
2388
|
+
for (const other of replication.prefixes.slice(index + 1)) {
|
|
2389
|
+
if (kvPrefixCovers(prefix, other) || kvPrefixCovers(other, prefix)) {
|
|
2390
|
+
throw new TypeError(
|
|
2391
|
+
`replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
if (prefix.split("/", 1)[0] === "vault" && replication.allowSecrets !== true) {
|
|
2396
|
+
throw new TypeError(
|
|
2397
|
+
`replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2166
2403
|
// src/TinyCloudNode.ts
|
|
2167
2404
|
import {
|
|
2168
2405
|
TinyCloud,
|
|
@@ -2201,12 +2438,12 @@ import {
|
|
|
2201
2438
|
canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
|
|
2202
2439
|
recapCaveatsEqual,
|
|
2203
2440
|
expandPermissionEntries as expandPermissionEntriesCore,
|
|
2204
|
-
isCapabilitySubset,
|
|
2441
|
+
isCapabilitySubset as isCapabilitySubset2,
|
|
2205
2442
|
isExplicitOnlyAction,
|
|
2206
2443
|
parseRecapCapabilities,
|
|
2207
2444
|
SERVICE_LONG_TO_SHORT,
|
|
2208
2445
|
SERVICE_SHORT_TO_LONG,
|
|
2209
|
-
KV as
|
|
2446
|
+
KV as KV3,
|
|
2210
2447
|
SQL as SQL2,
|
|
2211
2448
|
DUCKDB as DUCKDB2,
|
|
2212
2449
|
ENCRYPTION as ENCRYPTION2,
|
|
@@ -2216,6 +2453,7 @@ import {
|
|
|
2216
2453
|
verifyDidKeyEd25519Signature,
|
|
2217
2454
|
canonicalizeAddress as canonicalizeAddress2,
|
|
2218
2455
|
pkhDid as pkhDid2,
|
|
2456
|
+
principalDid,
|
|
2219
2457
|
resolveTinyCloudHosts as resolveTinyCloudHosts2,
|
|
2220
2458
|
publishLocationRecord,
|
|
2221
2459
|
principalDidEquals as principalDidEquals3,
|
|
@@ -2687,11 +2925,11 @@ function isSecretPayload(value) {
|
|
|
2687
2925
|
return typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && "createdAt" in value && typeof value.createdAt === "string" && "updatedAt" in value && typeof value.updatedAt === "string";
|
|
2688
2926
|
}
|
|
2689
2927
|
var ROOT_DELEGATION_ACTIONS = [
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2928
|
+
KV3.PUT,
|
|
2929
|
+
KV3.GET,
|
|
2930
|
+
KV3.DEL,
|
|
2931
|
+
KV3.LIST,
|
|
2932
|
+
KV3.METADATA,
|
|
2695
2933
|
SQL2.READ,
|
|
2696
2934
|
SQL2.WRITE,
|
|
2697
2935
|
SQL2.ADMIN,
|
|
@@ -3142,6 +3380,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3142
3380
|
this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
|
|
3143
3381
|
/** Serializes account-registry writes for the active node instance. */
|
|
3144
3382
|
this.accountRegistryTail = Promise.resolve();
|
|
3383
|
+
/** Bounds best-effort registry work on the sign-in critical path. */
|
|
3384
|
+
this.accountRegistryDeadlineMs = 4e3;
|
|
3145
3385
|
/**
|
|
3146
3386
|
* True when the last signIn() detected an interactive signer and skipped
|
|
3147
3387
|
* client-side bootstrap. Apps can read this to know whether bootstrap was
|
|
@@ -3220,11 +3460,46 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3220
3460
|
});
|
|
3221
3461
|
return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
|
|
3222
3462
|
};
|
|
3463
|
+
assertValidReplicationConfig(config.replication);
|
|
3223
3464
|
this.explicitHost = config.host;
|
|
3224
3465
|
this.config = {
|
|
3225
3466
|
...config,
|
|
3226
3467
|
host: config.host ?? DEFAULT_HOST
|
|
3227
3468
|
};
|
|
3469
|
+
if (this.config.replication?.enabled) {
|
|
3470
|
+
const runtime = getNodeReplicationLoaders().runtime().then(({ ReplicationRuntime }) => {
|
|
3471
|
+
const instance = new ReplicationRuntime(this.config.replication);
|
|
3472
|
+
this.replicationRuntimeInstance = instance;
|
|
3473
|
+
return instance;
|
|
3474
|
+
});
|
|
3475
|
+
this.replicationRuntime = runtime;
|
|
3476
|
+
this.replicationControl = {
|
|
3477
|
+
status: async () => {
|
|
3478
|
+
const status = await runtime.then((ready) => ready.control.status());
|
|
3479
|
+
const scopeError = this.replicationScopeError;
|
|
3480
|
+
if (!scopeError) return status;
|
|
3481
|
+
return this.config.replication.prefixes.map((prefix) => ({
|
|
3482
|
+
prefix,
|
|
3483
|
+
state: "unavailable",
|
|
3484
|
+
lastError: {
|
|
3485
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3486
|
+
code: "SESSION_SCOPE_MISMATCH",
|
|
3487
|
+
message: scopeError
|
|
3488
|
+
},
|
|
3489
|
+
pending: { inFlight: 0, committed: 0, ambiguous: 0 },
|
|
3490
|
+
pinned: [],
|
|
3491
|
+
lagMs: null
|
|
3492
|
+
}));
|
|
3493
|
+
},
|
|
3494
|
+
sync: async (input) => (await runtime).control.sync(input),
|
|
3495
|
+
purge: (input) => {
|
|
3496
|
+
const instance = this.replicationRuntimeInstance;
|
|
3497
|
+
return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
|
|
3498
|
+
},
|
|
3499
|
+
clearPending: async () => (await runtime).control.clearPending(),
|
|
3500
|
+
close: async () => (await runtime).control.close()
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3228
3503
|
if (config.wasmBindings) {
|
|
3229
3504
|
this.wasmBindings = config.wasmBindings;
|
|
3230
3505
|
} else if (_TinyCloudNode.nodeDefaults) {
|
|
@@ -3345,7 +3620,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3345
3620
|
siweConfig: config.siweConfig,
|
|
3346
3621
|
manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
|
|
3347
3622
|
capabilityRequest: useBootstrapSignInRequest ? config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
|
|
3348
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
|
|
3623
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions,
|
|
3624
|
+
// Sign-in needs only the entry inputs (prefixes + secrets opt-in);
|
|
3625
|
+
// storage and scheduling never leave TinyCloudNode.
|
|
3626
|
+
replication: config.replication?.enabled === true ? {
|
|
3627
|
+
enabled: true,
|
|
3628
|
+
prefixes: config.replication.prefixes,
|
|
3629
|
+
allowSecrets: config.replication.allowSecrets === true
|
|
3630
|
+
} : void 0
|
|
3349
3631
|
});
|
|
3350
3632
|
this.tc = new TinyCloud(this.auth, {
|
|
3351
3633
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -3596,6 +3878,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3596
3878
|
this.confirmedHostedSpaceIds.clear();
|
|
3597
3879
|
await this.tc.signIn(options);
|
|
3598
3880
|
this.syncResolvedHostFromAuth();
|
|
3881
|
+
this.replicationDelegateSession = void 0;
|
|
3882
|
+
this.replicationScopeError = void 0;
|
|
3883
|
+
if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
|
|
3599
3884
|
const oldGraph = this._serviceGraph;
|
|
3600
3885
|
this._serviceGraph = this.createServiceGraphLifetime();
|
|
3601
3886
|
oldGraph.retire();
|
|
@@ -3605,7 +3890,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3605
3890
|
this.sessionKeyId = signedInSession.sessionKey;
|
|
3606
3891
|
this.sessionKeyJwk = signedInSession.jwk;
|
|
3607
3892
|
}
|
|
3608
|
-
this.initializeServices();
|
|
3893
|
+
await this.initializeServices();
|
|
3609
3894
|
const primarySession = this.currentTinyCloudSession();
|
|
3610
3895
|
if (primarySession) {
|
|
3611
3896
|
this.registerPrimarySessionGrant(primarySession);
|
|
@@ -3617,6 +3902,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3617
3902
|
}
|
|
3618
3903
|
if (!bootstrapped) {
|
|
3619
3904
|
this.scheduleAccountRegistrySync();
|
|
3905
|
+
await this.accountRegistryTail;
|
|
3620
3906
|
}
|
|
3621
3907
|
this.notificationHandler.success("Successfully signed in");
|
|
3622
3908
|
}
|
|
@@ -4069,7 +4355,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4069
4355
|
provenance: "primary"
|
|
4070
4356
|
});
|
|
4071
4357
|
}
|
|
4072
|
-
async writeManifestRegistryRecords() {
|
|
4358
|
+
async writeManifestRegistryRecords(account, signal) {
|
|
4073
4359
|
const request = this.capabilityRequest;
|
|
4074
4360
|
if (!request || request.registryRecords.length === 0) {
|
|
4075
4361
|
return;
|
|
@@ -4078,8 +4364,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4078
4364
|
throw new Error("Manifest registry write requires wallet mode");
|
|
4079
4365
|
}
|
|
4080
4366
|
const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
|
|
4081
|
-
await this.ensureOwnedSpaceHostedById(accountSpaceId);
|
|
4082
|
-
|
|
4367
|
+
await this.ensureOwnedSpaceHostedById(accountSpaceId, signal);
|
|
4368
|
+
if (signal.aborted) throw signal.reason;
|
|
4369
|
+
const result = await account.applications.register(request.manifests);
|
|
4083
4370
|
if (!result.ok) {
|
|
4084
4371
|
throw Object.assign(
|
|
4085
4372
|
new Error(`Failed to write manifest registry records: ${result.error.message}`),
|
|
@@ -4087,34 +4374,113 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4087
4374
|
);
|
|
4088
4375
|
}
|
|
4089
4376
|
}
|
|
4377
|
+
createAccountRegistryServices(context, signal) {
|
|
4378
|
+
const session = context.session;
|
|
4379
|
+
if (!session) throw new Error("Account registry requires an active service session");
|
|
4380
|
+
const contexts = [context];
|
|
4381
|
+
const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
|
|
4382
|
+
const createScopedContext = (spaceId) => {
|
|
4383
|
+
const scoped = this._serviceGraph.track(new ServiceContext2({
|
|
4384
|
+
invoke: context.invoke,
|
|
4385
|
+
invokeAny: context.invokeAny,
|
|
4386
|
+
fetch: context.fetch,
|
|
4387
|
+
hosts: context.hosts,
|
|
4388
|
+
telemetry: this.config.telemetry
|
|
4389
|
+
}));
|
|
4390
|
+
scoped.setOperationAbortSignal(signal);
|
|
4391
|
+
scoped.setSession({ ...session, spaceId });
|
|
4392
|
+
contexts.push(scoped);
|
|
4393
|
+
return scoped;
|
|
4394
|
+
};
|
|
4395
|
+
const sql = new SQLService2({});
|
|
4396
|
+
const sqlContext = createScopedContext(accountSpaceId);
|
|
4397
|
+
sql.initialize(sqlContext);
|
|
4398
|
+
const spaces = new SpaceService({
|
|
4399
|
+
hosts: context.hosts,
|
|
4400
|
+
session,
|
|
4401
|
+
invoke: context.invoke,
|
|
4402
|
+
fetch: (url, init) => context.fetch(url, {
|
|
4403
|
+
...init,
|
|
4404
|
+
signal: init?.signal ? AbortSignal.any([init.signal, signal]) : signal
|
|
4405
|
+
}),
|
|
4406
|
+
userDid: this.did,
|
|
4407
|
+
createKVService: (spaceId) => {
|
|
4408
|
+
const kv = new KVService2({});
|
|
4409
|
+
kv.initialize(createScopedContext(spaceId));
|
|
4410
|
+
return kv;
|
|
4411
|
+
}
|
|
4412
|
+
});
|
|
4413
|
+
return {
|
|
4414
|
+
contexts,
|
|
4415
|
+
account: new AccountService({
|
|
4416
|
+
getDid: () => this.did,
|
|
4417
|
+
getHost: () => context.hosts[0] ?? this.config.host,
|
|
4418
|
+
getPrimarySpaceId: () => session.spaceId,
|
|
4419
|
+
getAccountSpaceId: () => accountSpaceId,
|
|
4420
|
+
getSpaces: () => spaces,
|
|
4421
|
+
getAccountDb: () => sql.db("account"),
|
|
4422
|
+
ensureAccountSpaceHosted: () => this.ensureOwnedSpaceHostedById(accountSpaceId, signal)
|
|
4423
|
+
})
|
|
4424
|
+
};
|
|
4425
|
+
}
|
|
4090
4426
|
scheduleAccountRegistrySync() {
|
|
4091
4427
|
const session = this.currentTinyCloudSession();
|
|
4092
4428
|
if (!session || this.pendingAccountRegistrySync?.session === session) {
|
|
4093
4429
|
return;
|
|
4094
4430
|
}
|
|
4431
|
+
const controller = new AbortController();
|
|
4432
|
+
const deadline = setTimeout(
|
|
4433
|
+
() => controller.abort(new DOMException("Account registry deadline exceeded", "TimeoutError")),
|
|
4434
|
+
this.accountRegistryDeadlineMs
|
|
4435
|
+
);
|
|
4095
4436
|
const promise = this.enqueueAccountRegistryOperation(session, async () => {
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4437
|
+
const sharedContext = this._serviceContext;
|
|
4438
|
+
if (!sharedContext) return;
|
|
4439
|
+
const context = this._serviceGraph.track(new ServiceContext2({
|
|
4440
|
+
invoke: sharedContext.invoke,
|
|
4441
|
+
invokeAny: sharedContext.invokeAny,
|
|
4442
|
+
fetch: sharedContext.fetch,
|
|
4443
|
+
hosts: sharedContext.hosts,
|
|
4444
|
+
telemetry: this.config.telemetry
|
|
4445
|
+
}));
|
|
4446
|
+
context.setOperationAbortSignal(controller.signal);
|
|
4447
|
+
const current = this.currentTinyCloudSession();
|
|
4448
|
+
if (!current) return;
|
|
4449
|
+
context.setSession({
|
|
4450
|
+
delegationHeader: current.delegationHeader,
|
|
4451
|
+
delegationCid: current.delegationCid,
|
|
4452
|
+
spaceId: current.spaceId,
|
|
4453
|
+
verificationMethod: current.verificationMethod,
|
|
4454
|
+
jwk: current.jwk
|
|
4455
|
+
});
|
|
4456
|
+
const registry = this.createAccountRegistryServices(context, controller.signal);
|
|
4457
|
+
try {
|
|
4458
|
+
await this.withAccountRegistryRetry(async () => {
|
|
4459
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
4460
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
4461
|
+
await registry.account.index.ensure();
|
|
4462
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
4463
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
4464
|
+
await this.writeManifestRegistryRecords(registry.account, controller.signal);
|
|
4465
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
4466
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
4467
|
+
if (this.currentSessionCanListSpaces()) {
|
|
4468
|
+
const result = await registry.account.spaces.syncAccessible();
|
|
4469
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
4470
|
+
if (!result.ok) {
|
|
4471
|
+
throw Object.assign(
|
|
4472
|
+
new Error(`Failed to sync account spaces: ${result.error.message}`),
|
|
4473
|
+
{ cause: result.error }
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4115
4476
|
}
|
|
4477
|
+
}, controller.signal);
|
|
4478
|
+
} finally {
|
|
4479
|
+
for (const operationContext of registry.contexts) {
|
|
4480
|
+
operationContext.retire();
|
|
4116
4481
|
}
|
|
4117
|
-
|
|
4482
|
+
clearTimeout(deadline);
|
|
4483
|
+
}
|
|
4118
4484
|
});
|
|
4119
4485
|
this.pendingAccountRegistrySync = { session, promise };
|
|
4120
4486
|
const clearPendingSync = () => {
|
|
@@ -4123,6 +4489,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4123
4489
|
}
|
|
4124
4490
|
};
|
|
4125
4491
|
void promise.then(clearPendingSync, clearPendingSync);
|
|
4492
|
+
void promise.then(
|
|
4493
|
+
() => clearTimeout(deadline),
|
|
4494
|
+
() => clearTimeout(deadline)
|
|
4495
|
+
);
|
|
4126
4496
|
}
|
|
4127
4497
|
enqueueAccountRegistryOperation(session, task) {
|
|
4128
4498
|
const operation = this.accountRegistryTail.then(async () => {
|
|
@@ -4158,14 +4528,22 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4158
4528
|
(operation) => operation.service === "space" && this.actionContains(operation.action, "tinycloud.space/list")
|
|
4159
4529
|
);
|
|
4160
4530
|
}
|
|
4161
|
-
async withAccountRegistryRetry(task) {
|
|
4531
|
+
async withAccountRegistryRetry(task, signal) {
|
|
4162
4532
|
const delays = [250, 1e3, 3e3];
|
|
4163
4533
|
let lastError;
|
|
4164
4534
|
for (let attempt = 0; attempt < delays.length; attempt += 1) {
|
|
4535
|
+
if (signal.aborted) {
|
|
4536
|
+
console.warn("TinyCloud account registry sync stopped: deadline exceeded", signal.reason);
|
|
4537
|
+
return;
|
|
4538
|
+
}
|
|
4165
4539
|
try {
|
|
4166
4540
|
await task();
|
|
4167
4541
|
return;
|
|
4168
4542
|
} catch (error) {
|
|
4543
|
+
if (signal.aborted) {
|
|
4544
|
+
console.warn("TinyCloud account registry sync stopped: deadline exceeded", error);
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
4169
4547
|
const verdict = authorizationVerdictOf(error);
|
|
4170
4548
|
const message = error instanceof Error ? error.message : String(error);
|
|
4171
4549
|
const isAuthorizationVerdict = verdict === void 0 ? /Unauthorized Action|\b(?:401|403)\b/.test(message) : verdict !== "other";
|
|
@@ -4177,12 +4555,28 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4177
4555
|
return;
|
|
4178
4556
|
}
|
|
4179
4557
|
if (isStorageFullError(error) || isStorageFullError(error?.cause) || message.includes(STORAGE_FULL_MESSAGE) || message.includes(STORAGE_WRITE_TOO_LARGE_MESSAGE)) {
|
|
4180
|
-
console.warn(
|
|
4558
|
+
console.warn(
|
|
4559
|
+
"TinyCloud account registry sync stopped: storage is full",
|
|
4560
|
+
error
|
|
4561
|
+
);
|
|
4181
4562
|
return;
|
|
4182
4563
|
}
|
|
4183
4564
|
lastError = error;
|
|
4184
4565
|
if (attempt < delays.length - 1) {
|
|
4185
|
-
await new Promise((resolve) =>
|
|
4566
|
+
await new Promise((resolve, reject2) => {
|
|
4567
|
+
const timer = setTimeout(() => {
|
|
4568
|
+
signal.removeEventListener("abort", onAbort);
|
|
4569
|
+
resolve();
|
|
4570
|
+
}, delays[attempt]);
|
|
4571
|
+
const onAbort = () => {
|
|
4572
|
+
clearTimeout(timer);
|
|
4573
|
+
signal.removeEventListener("abort", onAbort);
|
|
4574
|
+
reject2(signal.reason);
|
|
4575
|
+
};
|
|
4576
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4577
|
+
}).catch((error2) => {
|
|
4578
|
+
if (!signal.aborted) throw error2;
|
|
4579
|
+
});
|
|
4186
4580
|
}
|
|
4187
4581
|
}
|
|
4188
4582
|
}
|
|
@@ -4232,7 +4626,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4232
4626
|
* self-revealing: the next write to the space fails immediately and loudly
|
|
4233
4627
|
* with `404 Space not found`.
|
|
4234
4628
|
*/
|
|
4235
|
-
async ensureOwnedSpaceHostedById(spaceId) {
|
|
4629
|
+
async ensureOwnedSpaceHostedById(spaceId, signal) {
|
|
4236
4630
|
if (!this.auth) {
|
|
4237
4631
|
throw new Error("Owned space hosting requires wallet mode");
|
|
4238
4632
|
}
|
|
@@ -4247,7 +4641,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4247
4641
|
if (!host) {
|
|
4248
4642
|
throw new Error("Owned space hosting requires a TinyCloud host");
|
|
4249
4643
|
}
|
|
4250
|
-
const activation = await activateSessionWithHost2(host, session.delegationHeader);
|
|
4644
|
+
const activation = await activateSessionWithHost2(host, session.delegationHeader, signal);
|
|
4251
4645
|
if (activation.success && !activation.skipped?.includes(spaceId)) {
|
|
4252
4646
|
this.confirmedHostedSpaceIds.add(spaceId);
|
|
4253
4647
|
return;
|
|
@@ -4258,15 +4652,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4258
4652
|
{ cause: activation }
|
|
4259
4653
|
);
|
|
4260
4654
|
}
|
|
4261
|
-
const
|
|
4655
|
+
const authorization = this.auth;
|
|
4656
|
+
const created = signal ? await authorization.hostOwnedSpaceResult(spaceId, void 0, signal) : await authorization.hostOwnedSpaceResult(spaceId);
|
|
4262
4657
|
if (!created.success) {
|
|
4263
4658
|
throw Object.assign(
|
|
4264
4659
|
new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
|
|
4265
4660
|
{ cause: created }
|
|
4266
4661
|
);
|
|
4267
4662
|
}
|
|
4268
|
-
|
|
4269
|
-
const retry = await activateSessionWithHost2(host, session.delegationHeader);
|
|
4663
|
+
if (signal?.aborted) throw signal.reason;
|
|
4664
|
+
const retry = await activateSessionWithHost2(host, session.delegationHeader, signal);
|
|
4270
4665
|
if (!retry.success || retry.skipped?.includes(spaceId)) {
|
|
4271
4666
|
throw Object.assign(
|
|
4272
4667
|
new Error(
|
|
@@ -4544,6 +4939,34 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4544
4939
|
const stagedHost = resolvedHost ?? this.config.host;
|
|
4545
4940
|
const stagedAddress = restoredAddress;
|
|
4546
4941
|
const stagedChainId = sessionData.chainId ?? 1;
|
|
4942
|
+
let replicationPrincipal;
|
|
4943
|
+
let compactReplicationSession;
|
|
4944
|
+
let replicationScopeError;
|
|
4945
|
+
if (this.config.replication?.enabled) {
|
|
4946
|
+
if (stagedAddress !== void 0) {
|
|
4947
|
+
replicationPrincipal = pkhDid2(stagedAddress, stagedChainId);
|
|
4948
|
+
} else {
|
|
4949
|
+
const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
|
|
4950
|
+
const signedScope = replicationScopeFromSignedSession({
|
|
4951
|
+
delegationHeader: sessionData.delegationHeader,
|
|
4952
|
+
delegationCid: sessionData.delegationCid,
|
|
4953
|
+
verificationMethod: restoredVerificationMethod,
|
|
4954
|
+
persistedSpace: sessionData.spaceId
|
|
4955
|
+
});
|
|
4956
|
+
if (!signedScope) {
|
|
4957
|
+
replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
|
|
4958
|
+
} else {
|
|
4959
|
+
replicationPrincipal = signedScope.principal;
|
|
4960
|
+
compactReplicationSession = {
|
|
4961
|
+
delegationHeader: sessionData.delegationHeader,
|
|
4962
|
+
delegationCid: sessionData.delegationCid,
|
|
4963
|
+
spaceId: signedScope.space,
|
|
4964
|
+
verificationMethod: restoredVerificationMethod,
|
|
4965
|
+
jwk: stagedJwk
|
|
4966
|
+
};
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4547
4970
|
const stagedNodeDid = stagedAddress ? pkhDid2(stagedAddress, stagedChainId) : canonicalVerificationMethod;
|
|
4548
4971
|
const serviceSession = {
|
|
4549
4972
|
delegationHeader: sessionData.delegationHeader,
|
|
@@ -4622,7 +5045,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4622
5045
|
} else {
|
|
4623
5046
|
this._restoredTcSession = stagedTcSession;
|
|
4624
5047
|
}
|
|
5048
|
+
this.replicationDelegateSession = compactReplicationSession;
|
|
5049
|
+
this.replicationScopeError = replicationScopeError;
|
|
5050
|
+
if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
|
|
4625
5051
|
oldGraph.retire();
|
|
5052
|
+
if (replicationScopeError) {
|
|
5053
|
+
stagedGraph.serviceContext.emit("replication.state", {
|
|
5054
|
+
state: "unavailable",
|
|
5055
|
+
code: "SESSION_SCOPE_MISMATCH",
|
|
5056
|
+
detail: replicationScopeError
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
await this.bindReplicationRuntime(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
|
|
4626
5060
|
oldCore?.retireServices?.();
|
|
4627
5061
|
}
|
|
4628
5062
|
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
@@ -4776,6 +5210,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4776
5210
|
}));
|
|
4777
5211
|
context.setSession(config.session);
|
|
4778
5212
|
service.initialize(context);
|
|
5213
|
+
this.attachReplicationKV(config.session.spaceId, service);
|
|
4779
5214
|
return service;
|
|
4780
5215
|
}
|
|
4781
5216
|
});
|
|
@@ -4816,6 +5251,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4816
5251
|
}));
|
|
4817
5252
|
context.setSession({ ...input.serviceSession, spaceId });
|
|
4818
5253
|
scopedKv.initialize(context);
|
|
5254
|
+
this.attachReplicationKV(spaceId, scopedKv);
|
|
4819
5255
|
return scopedKv;
|
|
4820
5256
|
},
|
|
4821
5257
|
createVaultService: (spaceId) => {
|
|
@@ -4830,6 +5266,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4830
5266
|
}));
|
|
4831
5267
|
context.setSession({ ...input.serviceSession, spaceId });
|
|
4832
5268
|
scopedKv.initialize(context);
|
|
5269
|
+
this.attachReplicationKV(spaceId, scopedKv);
|
|
4833
5270
|
const scopedVault = this.createVaultService(spaceId, scopedKv, encryption, {
|
|
4834
5271
|
host: input.host,
|
|
4835
5272
|
did: input.nodeDid,
|
|
@@ -5110,7 +5547,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5110
5547
|
siweConfig: this.config.siweConfig,
|
|
5111
5548
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
5112
5549
|
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
5113
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
5550
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions,
|
|
5551
|
+
replication: this.config.replication?.enabled === true ? {
|
|
5552
|
+
enabled: true,
|
|
5553
|
+
prefixes: this.config.replication.prefixes,
|
|
5554
|
+
allowSecrets: this.config.replication.allowSecrets === true
|
|
5555
|
+
} : void 0
|
|
5114
5556
|
});
|
|
5115
5557
|
this.tc = new TinyCloud(this.auth, {
|
|
5116
5558
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -5163,7 +5605,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5163
5605
|
siweConfig: this.config.siweConfig,
|
|
5164
5606
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
5165
5607
|
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
5166
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
5608
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions,
|
|
5609
|
+
replication: this.config.replication?.enabled === true ? {
|
|
5610
|
+
enabled: true,
|
|
5611
|
+
prefixes: this.config.replication.prefixes,
|
|
5612
|
+
allowSecrets: this.config.replication.allowSecrets === true
|
|
5613
|
+
} : void 0
|
|
5167
5614
|
});
|
|
5168
5615
|
this.tc = new TinyCloud(this.auth, {
|
|
5169
5616
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -5175,7 +5622,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5175
5622
|
* Initialize the service context and KV service after sign-in.
|
|
5176
5623
|
* @internal
|
|
5177
5624
|
*/
|
|
5178
|
-
initializeServices() {
|
|
5625
|
+
async initializeServices() {
|
|
5179
5626
|
const session = this.currentTinyCloudSession();
|
|
5180
5627
|
if (!session) {
|
|
5181
5628
|
return;
|
|
@@ -5218,12 +5665,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5218
5665
|
jwk: session.jwk
|
|
5219
5666
|
};
|
|
5220
5667
|
this._serviceContext.setSession(serviceSession);
|
|
5668
|
+
await this.bindReplicationRuntime(this._serviceContext, serviceSession, this._kv);
|
|
5221
5669
|
this.tc.serviceContext.setSession(serviceSession);
|
|
5222
5670
|
this._vault = this.createVaultService(session.spaceId, this._kv);
|
|
5223
5671
|
this._vault.initialize(this._serviceContext);
|
|
5224
5672
|
this._serviceContext.registerService("vault", this._vault);
|
|
5225
5673
|
this.initializeV2Services(serviceSession);
|
|
5226
5674
|
}
|
|
5675
|
+
attachReplicationKV(spaceId, kvService) {
|
|
5676
|
+
if (!this.replicationRuntime) return;
|
|
5677
|
+
const attached = this.replicationRuntime.then((runtime) => runtime.attach(spaceId, kvService));
|
|
5678
|
+
kvService.setReadThroughReady(attached);
|
|
5679
|
+
}
|
|
5227
5680
|
createSpaceScopedKVService(spaceId) {
|
|
5228
5681
|
const kvService = new KVService2({});
|
|
5229
5682
|
if (this._serviceContext) {
|
|
@@ -5239,6 +5692,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5239
5692
|
spaceScopedContext.setSession({ ...session, spaceId });
|
|
5240
5693
|
}
|
|
5241
5694
|
kvService.initialize(spaceScopedContext);
|
|
5695
|
+
this.attachReplicationKV(spaceId, kvService);
|
|
5242
5696
|
}
|
|
5243
5697
|
return kvService;
|
|
5244
5698
|
}
|
|
@@ -5701,6 +6155,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5701
6155
|
}));
|
|
5702
6156
|
context.setSession(config.session);
|
|
5703
6157
|
service.initialize(context);
|
|
6158
|
+
this.attachReplicationKV(config.session.spaceId, service);
|
|
5704
6159
|
return service;
|
|
5705
6160
|
},
|
|
5706
6161
|
onRootDelegationNeeded: this.signer ? async (params) => {
|
|
@@ -6025,6 +6480,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6025
6480
|
}));
|
|
6026
6481
|
spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
|
|
6027
6482
|
kv.initialize(spaceScopedContext);
|
|
6483
|
+
this.attachReplicationKV(spaceId, kv);
|
|
6028
6484
|
return kv;
|
|
6029
6485
|
}
|
|
6030
6486
|
/**
|
|
@@ -6746,7 +7202,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6746
7202
|
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
6747
7203
|
}
|
|
6748
7204
|
await this.auth.hostPublicSpace(publicSpaceId);
|
|
6749
|
-
const kvActions = [
|
|
7205
|
+
const kvActions = [KV3.PUT, KV3.GET, KV3.DEL, KV3.LIST, KV3.METADATA];
|
|
6750
7206
|
const abilities = { kv: { "": kvActions } };
|
|
6751
7207
|
const now = /* @__PURE__ */ new Date();
|
|
6752
7208
|
const expiryMs = EXPIRY3.EPHEMERAL_MS;
|
|
@@ -6987,31 +7443,31 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6987
7443
|
);
|
|
6988
7444
|
return { delegation: delegation2, prompted: true };
|
|
6989
7445
|
}
|
|
6990
|
-
const
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
)
|
|
6998
|
-
|
|
6999
|
-
const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
|
|
7000
|
-
if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
|
|
7001
|
-
throw new SessionExpiredError(runtimeGrant.expiresAt);
|
|
7002
|
-
}
|
|
7003
|
-
const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
|
|
7004
|
-
const delegation2 = await this.createDelegationViaRuntimeGrant(
|
|
7005
|
-
did,
|
|
7006
|
-
expandedEntries,
|
|
7007
|
-
runtimeExpiration,
|
|
7008
|
-
runtimeGrant,
|
|
7009
|
-
runtimeOperations,
|
|
7010
|
-
onPrepared
|
|
7011
|
-
);
|
|
7012
|
-
return { delegation: delegation2, prompted: false };
|
|
7446
|
+
const plan = this.planDerivation(
|
|
7447
|
+
expandedEntries,
|
|
7448
|
+
session,
|
|
7449
|
+
effectiveExpiration,
|
|
7450
|
+
Date.now()
|
|
7451
|
+
);
|
|
7452
|
+
if ("refused" in plan) {
|
|
7453
|
+
if (plan.refused === "SESSION_EXPIRING") {
|
|
7454
|
+
throw new SessionExpiredError(plan.expiredAt ?? sessionExpiry ?? /* @__PURE__ */ new Date(0));
|
|
7013
7455
|
}
|
|
7014
|
-
|
|
7456
|
+
if (plan.refused === "CAVEATED_AUTHORITY") {
|
|
7457
|
+
throw new CaveatedDelegationUnsupportedError(plan.caveated ?? []);
|
|
7458
|
+
}
|
|
7459
|
+
throw new PermissionNotInManifestError(plan.missing ?? [], plan.granted ?? []);
|
|
7460
|
+
}
|
|
7461
|
+
if (plan.path === "runtime") {
|
|
7462
|
+
const delegation2 = await this.createDelegationViaRuntimeGrant(
|
|
7463
|
+
did,
|
|
7464
|
+
expandedEntries,
|
|
7465
|
+
plan.effectiveExpiration,
|
|
7466
|
+
plan.grant,
|
|
7467
|
+
plan.operations,
|
|
7468
|
+
onPrepared
|
|
7469
|
+
);
|
|
7470
|
+
return { delegation: delegation2, prompted: false };
|
|
7015
7471
|
}
|
|
7016
7472
|
const delegation = await this.createDelegationViaWasmPath(
|
|
7017
7473
|
did,
|
|
@@ -7022,6 +7478,149 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7022
7478
|
);
|
|
7023
7479
|
return { delegation, prompted: false };
|
|
7024
7480
|
}
|
|
7481
|
+
/**
|
|
7482
|
+
* What `delegateTo` would select for `entries`, without signing or any
|
|
7483
|
+
* network call (TC-858 plan v3 §4.3): the session path when the signed
|
|
7484
|
+
* recap covers every entry, the runtime-grant path otherwise, or a
|
|
7485
|
+
* refusal. A refusal of `CAVEATED_AUTHORITY` means coverage exists only
|
|
7486
|
+
* through caveated branches, which the action-only WASM child-delegation
|
|
7487
|
+
* boundary cannot preserve — replication never derives authority through
|
|
7488
|
+
* them. `expiredAt`/`missing`/`granted`/`caveated` carry the payload
|
|
7489
|
+
* `delegateTo` turns back into its typed errors.
|
|
7490
|
+
* @internal
|
|
7491
|
+
*/
|
|
7492
|
+
planDerivation(expandedEntries, session, effectiveExpiration, nowMs) {
|
|
7493
|
+
const caveatedRequest = expandedEntries.filter(
|
|
7494
|
+
(entry) => !recapCaveatsEqual(entry.caveats, void 0)
|
|
7495
|
+
);
|
|
7496
|
+
if (caveatedRequest.length > 0) {
|
|
7497
|
+
return { refused: "CAVEATED_AUTHORITY", caveated: caveatedRequest };
|
|
7498
|
+
}
|
|
7499
|
+
const granted = this.projectSignedRecapCapabilities(session.siwe);
|
|
7500
|
+
const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
|
|
7501
|
+
if (subset) {
|
|
7502
|
+
return {
|
|
7503
|
+
path: "session",
|
|
7504
|
+
parentCid: session.delegationCid,
|
|
7505
|
+
expiresAt: effectiveExpiration.getTime(),
|
|
7506
|
+
effectiveExpiration
|
|
7507
|
+
};
|
|
7508
|
+
}
|
|
7509
|
+
const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
|
|
7510
|
+
const runtimeGrant = this.findGrantForOperations(
|
|
7511
|
+
runtimeOperations,
|
|
7512
|
+
{ excludePrimary: true }
|
|
7513
|
+
);
|
|
7514
|
+
if (runtimeGrant) {
|
|
7515
|
+
const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
|
|
7516
|
+
if (runtimeGrant.expiresAt.getTime() <= nowMs + marginMs) {
|
|
7517
|
+
return { refused: "SESSION_EXPIRING", expiredAt: runtimeGrant.expiresAt };
|
|
7518
|
+
}
|
|
7519
|
+
const caveated = this.caveatedRuntimeGrantEntries(
|
|
7520
|
+
expandedEntries,
|
|
7521
|
+
runtimeOperations,
|
|
7522
|
+
runtimeGrant
|
|
7523
|
+
);
|
|
7524
|
+
if (caveated.length > 0) {
|
|
7525
|
+
return { refused: "CAVEATED_AUTHORITY", caveated };
|
|
7526
|
+
}
|
|
7527
|
+
const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
|
|
7528
|
+
return {
|
|
7529
|
+
path: "runtime",
|
|
7530
|
+
parentCid: runtimeGrant.session.delegationCid,
|
|
7531
|
+
expiresAt: runtimeExpiration.getTime(),
|
|
7532
|
+
effectiveExpiration: runtimeExpiration,
|
|
7533
|
+
grant: runtimeGrant,
|
|
7534
|
+
operations: runtimeOperations
|
|
7535
|
+
};
|
|
7536
|
+
}
|
|
7537
|
+
const relaxedGranted = granted.map((entry) => ({ ...entry, caveats: void 0 }));
|
|
7538
|
+
const relaxedRequested = expandedEntries.map((entry) => ({ ...entry, caveats: void 0 }));
|
|
7539
|
+
const relaxed = this.signedCapabilitySubset(relaxedRequested, relaxedGranted);
|
|
7540
|
+
if (relaxed.subset) {
|
|
7541
|
+
return {
|
|
7542
|
+
refused: "CAVEATED_AUTHORITY",
|
|
7543
|
+
caveated: missing.map((entry) => ({ ...entry, caveats: [{ via: "session-recap" }] }))
|
|
7544
|
+
};
|
|
7545
|
+
}
|
|
7546
|
+
return { refused: "NOT_COVERED", missing, granted };
|
|
7547
|
+
}
|
|
7548
|
+
/**
|
|
7549
|
+
* The plan `delegateTo(device, entries)` would follow, as a pure lookup:
|
|
7550
|
+
* no WASM sign and no `POST /delegate`. Backs
|
|
7551
|
+
* `ReplicationAuthority.plan` (and `mint` inside it) so a predicted
|
|
7552
|
+
* parent and expiry can never disagree with what a mint would use (§4.3,
|
|
7553
|
+
* §4.4). Session expiry inside the 60 s margin refuses with
|
|
7554
|
+
* `SESSION_EXPIRING`; the same check delegateTo performs.
|
|
7555
|
+
* @internal
|
|
7556
|
+
*/
|
|
7557
|
+
planDelegation(entries, options) {
|
|
7558
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
7559
|
+
throw new Error("planDelegation requires a non-empty permissions array");
|
|
7560
|
+
}
|
|
7561
|
+
const session = this.currentTinyCloudSession();
|
|
7562
|
+
if (!session) {
|
|
7563
|
+
return { refused: "SESSION_EXPIRING", expiredAt: /* @__PURE__ */ new Date(0) };
|
|
7564
|
+
}
|
|
7565
|
+
const nowMs = Date.now();
|
|
7566
|
+
const sessionExpiry = extractSiweExpiration(session.siwe);
|
|
7567
|
+
if (sessionExpiry !== void 0 && sessionExpiry.getTime() <= nowMs + _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS) {
|
|
7568
|
+
return { refused: "SESSION_EXPIRING", expiredAt: sessionExpiry };
|
|
7569
|
+
}
|
|
7570
|
+
const expandedEntries = this.expandPermissionEntries(entries);
|
|
7571
|
+
const expiryMs = resolveExpiryMs(options?.expiry);
|
|
7572
|
+
const expirationTime = new Date(nowMs + expiryMs);
|
|
7573
|
+
const effectiveExpiration = sessionExpiry !== void 0 && sessionExpiry < expirationTime ? sessionExpiry : expirationTime;
|
|
7574
|
+
return this.planDerivation(expandedEntries, session, effectiveExpiration, nowMs);
|
|
7575
|
+
}
|
|
7576
|
+
/**
|
|
7577
|
+
* The replication entries this node's sign-in request carries for the
|
|
7578
|
+
* current replication config and the signed-in or restored address: the
|
|
7579
|
+
* output of the same augmentation step `resolveSignInCapabilities` runs,
|
|
7580
|
+
* with the unrestricted-get and secrets gates applied (§4.1, §4.6).
|
|
7581
|
+
* Empty when replication is off or no address is known. Pure.
|
|
7582
|
+
*/
|
|
7583
|
+
replicationSignInEntries() {
|
|
7584
|
+
if (this.config.replication?.enabled !== true) return [];
|
|
7585
|
+
const auth = this.auth;
|
|
7586
|
+
if (auth === void 0 || typeof auth.replicationSignInEntries !== "function") {
|
|
7587
|
+
return [];
|
|
7588
|
+
}
|
|
7589
|
+
return auth.replicationSignInEntries();
|
|
7590
|
+
}
|
|
7591
|
+
async replicationAuthority() {
|
|
7592
|
+
const { createReplicationAuthority } = await getNodeReplicationLoaders().authority();
|
|
7593
|
+
return createReplicationAuthority({
|
|
7594
|
+
replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
|
|
7595
|
+
siweExpiration: (siwe) => extractSiweExpiration(siwe),
|
|
7596
|
+
planDelegation: (entries, options) => this.planDelegation(entries, options),
|
|
7597
|
+
mintDelegation: async (deviceDid, entries) => {
|
|
7598
|
+
const result = await this.delegateTo(principalDid(deviceDid), entries);
|
|
7599
|
+
return {
|
|
7600
|
+
ucan: result.delegation.delegationHeader.Authorization,
|
|
7601
|
+
expiresAt: result.delegation.expiry.getTime()
|
|
7602
|
+
};
|
|
7603
|
+
}
|
|
7604
|
+
});
|
|
7605
|
+
}
|
|
7606
|
+
async bindReplicationRuntime(context, session, kv, principal) {
|
|
7607
|
+
if (!this.replicationRuntime) return;
|
|
7608
|
+
const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : pkhDid2(this._address, this._chainId));
|
|
7609
|
+
if (identityPrincipal === void 0) return;
|
|
7610
|
+
const runtime = await this.replicationRuntime;
|
|
7611
|
+
const authority = await this.replicationAuthority();
|
|
7612
|
+
await runtime.bind({
|
|
7613
|
+
context,
|
|
7614
|
+
session,
|
|
7615
|
+
principal: identityPrincipal,
|
|
7616
|
+
authority,
|
|
7617
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
7618
|
+
});
|
|
7619
|
+
}
|
|
7620
|
+
/** Stable control facade for this node's optional replication runtime. */
|
|
7621
|
+
get replication() {
|
|
7622
|
+
return this.replicationControl;
|
|
7623
|
+
}
|
|
7025
7624
|
/**
|
|
7026
7625
|
* Materialize one manifest-declared delegation using the current session key.
|
|
7027
7626
|
* Delivery is intentionally out of band; callers decide how to transmit the
|
|
@@ -7244,6 +7843,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7244
7843
|
* it only accepts action maps, so reject the selected constrained branch.
|
|
7245
7844
|
*/
|
|
7246
7845
|
assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant) {
|
|
7846
|
+
const caveated = this.caveatedRuntimeGrantEntries(entries, requestedOperations, grant);
|
|
7847
|
+
if (caveated.length > 0) {
|
|
7848
|
+
throw new CaveatedDelegationUnsupportedError(caveated);
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
/**
|
|
7852
|
+
* The entries of `grant` that cover a requested operation while carrying
|
|
7853
|
+
* caveats — the branches {@link assertRuntimeGrantCaveatsPreservable}
|
|
7854
|
+
* rejects and {@link planDerivation} classifies as `CAVEATED_AUTHORITY`.
|
|
7855
|
+
* Shared so the pure plan and the mint path can't disagree.
|
|
7856
|
+
*/
|
|
7857
|
+
caveatedRuntimeGrantEntries(entries, requestedOperations, grant) {
|
|
7247
7858
|
let operationIndex = 0;
|
|
7248
7859
|
const caveated = [];
|
|
7249
7860
|
for (const entry of entries) {
|
|
@@ -7262,9 +7873,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7262
7873
|
}
|
|
7263
7874
|
}
|
|
7264
7875
|
}
|
|
7265
|
-
|
|
7266
|
-
throw new CaveatedDelegationUnsupportedError(caveated);
|
|
7267
|
-
}
|
|
7876
|
+
return caveated;
|
|
7268
7877
|
}
|
|
7269
7878
|
/** Reject caveated parent branches before action-only child signing. */
|
|
7270
7879
|
assertPortableDelegationCaveatsPreservable(delegation) {
|
|
@@ -7369,7 +7978,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7369
7978
|
const missing = requested.filter((entry) => {
|
|
7370
7979
|
const raw = this.isEncryptionPermissionEntry(entry) || this.isRawRevocationPermissionEntry(entry);
|
|
7371
7980
|
const comparable = raw && this.isEncryptionPermissionEntry(entry) ? { ...entry, space: "encryption" } : entry;
|
|
7372
|
-
return !
|
|
7981
|
+
return !isCapabilitySubset2([comparable], raw ? rawGranted : spaceGranted).subset;
|
|
7373
7982
|
});
|
|
7374
7983
|
return { subset: missing.length === 0, missing };
|
|
7375
7984
|
}
|
|
@@ -7841,7 +8450,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7841
8450
|
session.chainId
|
|
7842
8451
|
);
|
|
7843
8452
|
const publicAbilities = {
|
|
7844
|
-
kv: { "": [
|
|
8453
|
+
kv: { "": [KV3.GET, KV3.PUT, KV3.METADATA] }
|
|
7845
8454
|
};
|
|
7846
8455
|
const publicPrepared = this.wasmBindings.prepareSession({
|
|
7847
8456
|
abilities: publicAbilities,
|
|
@@ -7869,7 +8478,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7869
8478
|
delegationHeader: publicSession.delegationHeader,
|
|
7870
8479
|
spaceId: publicSpaceId,
|
|
7871
8480
|
path: "",
|
|
7872
|
-
actions: [
|
|
8481
|
+
actions: [KV3.GET, KV3.PUT, KV3.METADATA],
|
|
7873
8482
|
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
7874
8483
|
expiry: expirationTime,
|
|
7875
8484
|
delegateDID: params.delegateDID,
|
|
@@ -8143,7 +8752,7 @@ import {
|
|
|
8143
8752
|
resolveManifest as resolveManifest2,
|
|
8144
8753
|
validateManifest,
|
|
8145
8754
|
loadManifest,
|
|
8146
|
-
isCapabilitySubset as
|
|
8755
|
+
isCapabilitySubset as isCapabilitySubset3,
|
|
8147
8756
|
expandActionShortNames,
|
|
8148
8757
|
expandPermissionEntries as expandPermissionEntries2,
|
|
8149
8758
|
expandPermissionEntry,
|
|
@@ -8188,7 +8797,7 @@ import {
|
|
|
8188
8797
|
SpaceService as SpaceService2,
|
|
8189
8798
|
SpaceErrorCodes,
|
|
8190
8799
|
createSpaceService,
|
|
8191
|
-
parseSpaceUri,
|
|
8800
|
+
parseSpaceUri as parseSpaceUri2,
|
|
8192
8801
|
buildSpaceUri,
|
|
8193
8802
|
makePublicSpaceId as makePublicSpaceId2,
|
|
8194
8803
|
Space
|
|
@@ -8279,7 +8888,7 @@ export {
|
|
|
8279
8888
|
expandPermissionEntry,
|
|
8280
8889
|
getTinyCloudDebugLogs,
|
|
8281
8890
|
installTinyCloudDebugGlobals,
|
|
8282
|
-
|
|
8891
|
+
isCapabilitySubset3 as isCapabilitySubset,
|
|
8283
8892
|
isEvmAddress,
|
|
8284
8893
|
loadManifest,
|
|
8285
8894
|
makePkhSpaceId2 as makePkhSpaceId,
|
|
@@ -8288,10 +8897,11 @@ export {
|
|
|
8288
8897
|
parseExpiry2 as parseExpiry,
|
|
8289
8898
|
parsePkhDid,
|
|
8290
8899
|
parseSecretCatalogKey,
|
|
8291
|
-
parseSpaceUri,
|
|
8900
|
+
parseSpaceUri2 as parseSpaceUri,
|
|
8292
8901
|
pkhDid3 as pkhDid,
|
|
8293
|
-
principalDid,
|
|
8902
|
+
principalDid2 as principalDid,
|
|
8294
8903
|
principalDidEquals4 as principalDidEquals,
|
|
8904
|
+
registerReplicationLoaders,
|
|
8295
8905
|
resolveManifest2 as resolveManifest,
|
|
8296
8906
|
resolveSecretListPrefix2 as resolveSecretListPrefix,
|
|
8297
8907
|
resolveSecretPath3 as resolveSecretPath,
|