@tinycloud/node-sdk 3.2.6-beta.6 → 3.3.0-beta.11
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/{WasmKeyProvider-DEdSyQ0I.d.cts → TinyCloudNode-CcNh20QX.d.cts} +181 -286
- package/dist/{WasmKeyProvider-DEdSyQ0I.d.ts → TinyCloudNode-CcNh20QX.d.ts} +181 -286
- package/dist/WasmKeyProvider-5Ee7fcAw.d.cts +217 -0
- package/dist/WasmKeyProvider-5Ee7fcAw.d.ts +217 -0
- package/dist/authority-Cwotwza3.d.cts +118 -0
- package/dist/authority-D_u9a7Wl.d.ts +118 -0
- package/dist/core.cjs +401 -168
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +37 -4
- package/dist/core.d.ts +37 -4
- package/dist/core.js +408 -176
- package/dist/core.js.map +1 -1
- package/dist/gated-network-BEF2XSTC.d.cts +16 -0
- package/dist/gated-network-BEF2XSTC.d.ts +16 -0
- package/dist/index.cjs +2735 -545
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -89
- package/dist/index.d.ts +35 -89
- package/dist/index.js +2730 -545
- package/dist/index.js.map +1 -1
- package/dist/replication/authority.cjs +153 -15
- package/dist/replication/authority.cjs.map +1 -1
- package/dist/replication/authority.d.cts +4 -1
- package/dist/replication/authority.d.ts +4 -1
- package/dist/replication/authority.js +147 -14
- package/dist/replication/authority.js.map +1 -1
- package/dist/replication/gated-network.cjs +90 -0
- package/dist/replication/gated-network.cjs.map +1 -0
- package/dist/replication/gated-network.d.cts +2 -0
- package/dist/replication/gated-network.d.ts +2 -0
- package/dist/replication/gated-network.js +65 -0
- package/dist/replication/gated-network.js.map +1 -0
- package/dist/replication/runtime.cjs +84 -6
- package/dist/replication/runtime.cjs.map +1 -1
- package/dist/replication/runtime.d.cts +2 -1
- package/dist/replication/runtime.d.ts +2 -1
- package/dist/replication/runtime.js +84 -6
- package/dist/replication/runtime.js.map +1 -1
- package/dist/{runtime-B5P5SRpC.d.ts → runtime-D1o9Eybt.d.cts} +14 -3
- package/dist/{runtime-B5P5SRpC.d.cts → runtime-v62FzQHz.d.ts} +14 -3
- package/package.json +14 -4
- package/dist/authority-DDGtccBK.d.cts +0 -96
- package/dist/authority-DDGtccBK.d.ts +0 -96
package/dist/core.cjs
CHANGED
|
@@ -106,6 +106,7 @@ __export(core_exports, {
|
|
|
106
106
|
expandActionShortNames: () => import_sdk_core13.expandActionShortNames,
|
|
107
107
|
expandPermissionEntries: () => import_sdk_core13.expandPermissionEntries,
|
|
108
108
|
expandPermissionEntry: () => import_sdk_core13.expandPermissionEntry,
|
|
109
|
+
getNodeReplicationLoaders: () => getNodeReplicationLoaders,
|
|
109
110
|
getTinyCloudDebugLogs: () => import_sdk_core12.getTinyCloudDebugLogs,
|
|
110
111
|
installTinyCloudDebugGlobals: () => import_sdk_core12.installTinyCloudDebugGlobals,
|
|
111
112
|
isCapabilitySubset: () => import_sdk_core13.isCapabilitySubset,
|
|
@@ -526,6 +527,7 @@ var NodeUserAuthorization = class {
|
|
|
526
527
|
this.siweConfig = config.siweConfig;
|
|
527
528
|
this.includeAccountRegistryPermissions = config.includeAccountRegistryPermissions ?? true;
|
|
528
529
|
this.replication = config.replication?.enabled === true ? config.replication : void 0;
|
|
530
|
+
this.establishReplicaStamp = config.establishReplicaStamp;
|
|
529
531
|
this._manifest = config.manifest;
|
|
530
532
|
this._capabilityRequest = config.capabilityRequest;
|
|
531
533
|
this.sessionManager = config.sessionManager ?? this.wasm.createSessionManager();
|
|
@@ -1295,6 +1297,27 @@ var NodeUserAuthorization = class {
|
|
|
1295
1297
|
signature
|
|
1296
1298
|
};
|
|
1297
1299
|
}
|
|
1300
|
+
persistedData(session, expiresAt, createdAt, replicaStamp) {
|
|
1301
|
+
return {
|
|
1302
|
+
address: session.address,
|
|
1303
|
+
chainId: session.chainId,
|
|
1304
|
+
sessionKey: JSON.stringify(session.jwk),
|
|
1305
|
+
siwe: session.siwe,
|
|
1306
|
+
signature: session.signature,
|
|
1307
|
+
tinycloudSession: {
|
|
1308
|
+
delegationHeader: session.delegationHeader,
|
|
1309
|
+
delegationCid: session.delegationCid,
|
|
1310
|
+
spaceId: session.spaceId,
|
|
1311
|
+
spaces: session.spaces,
|
|
1312
|
+
verificationMethod: session.verificationMethod
|
|
1313
|
+
},
|
|
1314
|
+
expiresAt,
|
|
1315
|
+
createdAt,
|
|
1316
|
+
version: "1.0",
|
|
1317
|
+
tinycloudHosts: this.tinycloudHosts,
|
|
1318
|
+
...replicaStamp ? { replicaStamp } : {}
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1298
1321
|
/**
|
|
1299
1322
|
* Sign in and create a new session.
|
|
1300
1323
|
*
|
|
@@ -1374,24 +1397,13 @@ var NodeUserAuthorization = class {
|
|
|
1374
1397
|
siwe: prepared.siwe,
|
|
1375
1398
|
signature
|
|
1376
1399
|
};
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
delegationHeader: session.delegationHeader,
|
|
1385
|
-
delegationCid: session.delegationCid,
|
|
1386
|
-
spaceId,
|
|
1387
|
-
spaces: spacesMetadata,
|
|
1388
|
-
verificationMethod: this.sessionManager.getDID(keyId)
|
|
1389
|
-
},
|
|
1390
|
-
expiresAt: expirationTime.toISOString(),
|
|
1391
|
-
createdAt: now.toISOString(),
|
|
1392
|
-
version: "1.0",
|
|
1393
|
-
tinycloudHosts: this.tinycloudHosts
|
|
1394
|
-
};
|
|
1400
|
+
const replicaStamp = await this.establishReplicaStamp?.(tinyCloudSession);
|
|
1401
|
+
const persistedData = this.persistedData(
|
|
1402
|
+
tinyCloudSession,
|
|
1403
|
+
expirationTime.toISOString(),
|
|
1404
|
+
now.toISOString(),
|
|
1405
|
+
replicaStamp
|
|
1406
|
+
);
|
|
1395
1407
|
await this.enqueuePersistence(async () => {
|
|
1396
1408
|
await this.sessionStorage.save(address, persistedData);
|
|
1397
1409
|
if (generation !== this.sessionGeneration) {
|
|
@@ -1545,28 +1557,12 @@ var NodeUserAuthorization = class {
|
|
|
1545
1557
|
siwe: prepared.siwe,
|
|
1546
1558
|
signature
|
|
1547
1559
|
};
|
|
1548
|
-
const expirationMatch = prepared.siwe
|
|
1549
|
-
const issuedAtMatch = prepared.siwe
|
|
1560
|
+
const expirationMatch = prepared.siwe?.match(/Expiration Time: (.+)/);
|
|
1561
|
+
const issuedAtMatch = prepared.siwe?.match(/Issued At: (.+)/);
|
|
1550
1562
|
const expiresAt = expirationMatch?.[1] ?? new Date(Date.now() + this.sessionExpirationMs).toISOString();
|
|
1551
1563
|
const createdAt = issuedAtMatch?.[1] ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
chainId,
|
|
1555
|
-
sessionKey: JSON.stringify(jwk),
|
|
1556
|
-
siwe: prepared.siwe,
|
|
1557
|
-
signature,
|
|
1558
|
-
tinycloudSession: {
|
|
1559
|
-
delegationHeader: session.delegationHeader,
|
|
1560
|
-
delegationCid: session.delegationCid,
|
|
1561
|
-
spaceId: prepared.spaceId,
|
|
1562
|
-
spaces: spacesMetadata,
|
|
1563
|
-
verificationMethod: this.sessionManager.getDID(keyId)
|
|
1564
|
-
},
|
|
1565
|
-
expiresAt,
|
|
1566
|
-
createdAt,
|
|
1567
|
-
version: "1.0",
|
|
1568
|
-
tinycloudHosts: this.tinycloudHosts
|
|
1569
|
-
};
|
|
1564
|
+
const replicaStamp = await this.establishReplicaStamp?.(tinyCloudSession);
|
|
1565
|
+
const persistedData = this.persistedData(tinyCloudSession, expiresAt, createdAt, replicaStamp);
|
|
1570
1566
|
await this.sessionStorage.save(address, persistedData);
|
|
1571
1567
|
this._session = clientSession;
|
|
1572
1568
|
this._tinyCloudSession = tinyCloudSession;
|
|
@@ -1629,15 +1625,36 @@ var NodeUserAuthorization = class {
|
|
|
1629
1625
|
const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
|
|
1630
1626
|
const presentation = this.buildPresentationEnvelope(options?.reason);
|
|
1631
1627
|
const hostHint = this.primaryTinyCloudHost;
|
|
1632
|
-
const
|
|
1628
|
+
const originalRequest = {
|
|
1633
1629
|
protocolVersion: 1,
|
|
1634
1630
|
siwe: prepared.siwe,
|
|
1635
1631
|
jwk,
|
|
1632
|
+
verificationMethod: prepared.verificationMethod,
|
|
1636
1633
|
host: hostHint,
|
|
1637
1634
|
keyId: options?.openkeyKeyId,
|
|
1638
1635
|
presentation
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1636
|
+
};
|
|
1637
|
+
let result;
|
|
1638
|
+
let signed;
|
|
1639
|
+
let signature;
|
|
1640
|
+
for (let attempt = 0; ; ) {
|
|
1641
|
+
try {
|
|
1642
|
+
result ?? (result = await authorizeFn(originalRequest));
|
|
1643
|
+
signed = await this.verifyOpenKeyResult(result, prepared);
|
|
1644
|
+
signature = result.signature;
|
|
1645
|
+
break;
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
if (!options?.retryResult) throw error;
|
|
1648
|
+
const next = await options.retryResult(
|
|
1649
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1650
|
+
originalRequest,
|
|
1651
|
+
++attempt
|
|
1652
|
+
);
|
|
1653
|
+
if (next === null) throw error;
|
|
1654
|
+
result = next;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return this.signInWithPreparedSession(signed, signature, keyId, jwk);
|
|
1641
1658
|
}
|
|
1642
1659
|
/**
|
|
1643
1660
|
* Build a `CapabilityPresentationEnvelopeV1` from `this._manifest`. The
|
|
@@ -1717,6 +1734,11 @@ var NodeUserAuthorization = class {
|
|
|
1717
1734
|
* (resource, action) pair in the returned SIWE.
|
|
1718
1735
|
*/
|
|
1719
1736
|
async signInWithOpenKeyResult(result, prepared, keyId, jwk) {
|
|
1737
|
+
const signed = await this.verifyOpenKeyResult(result, prepared);
|
|
1738
|
+
return this.signInWithPreparedSession(signed, result.signature, keyId, jwk);
|
|
1739
|
+
}
|
|
1740
|
+
/** Validate a candidate without activating a network session, so paste retries reuse one preparation. */
|
|
1741
|
+
async verifyOpenKeyResult(result, prepared) {
|
|
1720
1742
|
if (result.protocolVersion !== 1) {
|
|
1721
1743
|
throw new Error(`Unsupported OpenKey protocol version ${result.protocolVersion}`);
|
|
1722
1744
|
}
|
|
@@ -1961,17 +1983,12 @@ var NodeUserAuthorization = class {
|
|
|
1961
1983
|
}
|
|
1962
1984
|
}
|
|
1963
1985
|
void nonRequiredGrantedPairs;
|
|
1964
|
-
return
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
},
|
|
1971
|
-
result.signature,
|
|
1972
|
-
keyId,
|
|
1973
|
-
jwk
|
|
1974
|
-
);
|
|
1986
|
+
return {
|
|
1987
|
+
siwe: result.signedMessage,
|
|
1988
|
+
jwk: prepared.jwk,
|
|
1989
|
+
spaceId: prepared.spaceId,
|
|
1990
|
+
verificationMethod: prepared.verificationMethod
|
|
1991
|
+
};
|
|
1975
1992
|
}
|
|
1976
1993
|
/**
|
|
1977
1994
|
* Clear persisted session data.
|
|
@@ -2530,41 +2547,6 @@ function deserializeDelegation(data) {
|
|
|
2530
2547
|
|
|
2531
2548
|
// src/TinyCloudNode.ts
|
|
2532
2549
|
var import_siwe2 = require("siwe");
|
|
2533
|
-
|
|
2534
|
-
// src/replication/config.ts
|
|
2535
|
-
var import_prefix2 = require("@tinycloud/sdk-services/kv/replication/prefix");
|
|
2536
|
-
function assertValidReplicationConfig(replication) {
|
|
2537
|
-
if (replication === void 0 || replication.enabled !== true) return;
|
|
2538
|
-
if (replication.storage === void 0 || replication.storage === null) {
|
|
2539
|
-
throw new TypeError("replication.enabled requires replication.storage");
|
|
2540
|
-
}
|
|
2541
|
-
if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {
|
|
2542
|
-
throw new TypeError("replication.prefixes must be a non-empty array");
|
|
2543
|
-
}
|
|
2544
|
-
for (const prefix of replication.prefixes) {
|
|
2545
|
-
try {
|
|
2546
|
-
(0, import_prefix2.assertFolderPrefix)(prefix);
|
|
2547
|
-
} catch (error) {
|
|
2548
|
-
throw new TypeError(error instanceof Error ? error.message : String(error));
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
for (const [index, prefix] of replication.prefixes.entries()) {
|
|
2552
|
-
for (const other of replication.prefixes.slice(index + 1)) {
|
|
2553
|
-
if ((0, import_prefix2.kvPrefixCovers)(prefix, other) || (0, import_prefix2.kvPrefixCovers)(other, prefix)) {
|
|
2554
|
-
throw new TypeError(
|
|
2555
|
-
`replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
|
|
2556
|
-
);
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
if ((prefix === "/" || prefix.split("/", 1)[0] === "vault") && replication.allowSecrets !== true) {
|
|
2560
|
-
throw new TypeError(
|
|
2561
|
-
`replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
|
|
2562
|
-
);
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
// src/TinyCloudNode.ts
|
|
2568
2550
|
var import_sdk_core10 = require("@tinycloud/sdk-core");
|
|
2569
2551
|
var import_sdk_services2 = require("@tinycloud/sdk-services");
|
|
2570
2552
|
|
|
@@ -3365,6 +3347,7 @@ function ownerPermissionsToAbilities(permissions) {
|
|
|
3365
3347
|
}
|
|
3366
3348
|
return { abilities, rawAbilities };
|
|
3367
3349
|
}
|
|
3350
|
+
var TERMINAL_DELEGATION_REQUIRES_SESSION_KEY = "disableSubDelegation requires a session-key delegation";
|
|
3368
3351
|
function base64UrlEncode(bytes) {
|
|
3369
3352
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
3370
3353
|
let output = "";
|
|
@@ -3476,6 +3459,20 @@ function accountIndexWarningDiagnostic(error) {
|
|
|
3476
3459
|
}
|
|
3477
3460
|
return diagnostic;
|
|
3478
3461
|
}
|
|
3462
|
+
function replicaRestoreRefusal(error) {
|
|
3463
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "REPLICA_GATED" && "gate" in error) {
|
|
3464
|
+
switch (error.gate) {
|
|
3465
|
+
case "not_established":
|
|
3466
|
+
case "stamp_stale":
|
|
3467
|
+
case "principal_mismatch":
|
|
3468
|
+
case "purge_pending":
|
|
3469
|
+
case "logout_pending":
|
|
3470
|
+
case "gate_unavailable":
|
|
3471
|
+
return error.gate;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
return "gate_unavailable";
|
|
3475
|
+
}
|
|
3479
3476
|
var _TinyCloudNode = class _TinyCloudNode {
|
|
3480
3477
|
/**
|
|
3481
3478
|
* Create a new TinyCloudNode instance.
|
|
@@ -3505,6 +3502,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3505
3502
|
this.auth = null;
|
|
3506
3503
|
this.tc = null;
|
|
3507
3504
|
this._chainId = 1;
|
|
3505
|
+
/** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
|
|
3506
|
+
this.establishReplicaStamp = async (session) => {
|
|
3507
|
+
const replication = this.config.replication;
|
|
3508
|
+
if (!replication?.enabled) return void 0;
|
|
3509
|
+
const epoch = this.sessionEpoch;
|
|
3510
|
+
const attempt = this.latestSessionAttempt;
|
|
3511
|
+
try {
|
|
3512
|
+
const { verifiedOwnerConsentAnchor, establishReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
|
|
3513
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
|
|
3514
|
+
const anchor = verifiedOwnerConsentAnchor(session, this.wasmBindings, exactVerifiedRecap);
|
|
3515
|
+
if (anchor === null) {
|
|
3516
|
+
throw Object.assign(new Error("Replica sign-in requires a verified finite owner SIWE and exact-space KV get+sync."), {
|
|
3517
|
+
code: "REPLICA_GATED",
|
|
3518
|
+
gate: "not_established"
|
|
3519
|
+
});
|
|
3520
|
+
}
|
|
3521
|
+
const stamp = await establishReplicaSessionStamp(
|
|
3522
|
+
replication.storage,
|
|
3523
|
+
(0, import_sdk_core10.pkhDid)(session.address, session.chainId),
|
|
3524
|
+
anchor
|
|
3525
|
+
);
|
|
3526
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
|
|
3527
|
+
this.replicaSessionStamp = stamp;
|
|
3528
|
+
return stamp;
|
|
3529
|
+
} catch (error) {
|
|
3530
|
+
if (epoch === this.sessionEpoch && attempt === this.latestSessionAttempt) {
|
|
3531
|
+
this.replicaRestoreGate = replicaRestoreRefusal(error);
|
|
3532
|
+
}
|
|
3533
|
+
return void 0;
|
|
3534
|
+
}
|
|
3535
|
+
};
|
|
3508
3536
|
this._baseVaults = /* @__PURE__ */ new Map();
|
|
3509
3537
|
this._baseSecrets = /* @__PURE__ */ new Map();
|
|
3510
3538
|
this._secrets = /* @__PURE__ */ new Map();
|
|
@@ -3629,7 +3657,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3629
3657
|
});
|
|
3630
3658
|
return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
|
|
3631
3659
|
};
|
|
3632
|
-
|
|
3660
|
+
if (config.replication?.enabled) getNodeReplicationLoaders().validateConfig(config.replication);
|
|
3633
3661
|
this.explicitHost = config.host;
|
|
3634
3662
|
this.config = {
|
|
3635
3663
|
...config,
|
|
@@ -3644,6 +3672,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3644
3672
|
this.replicationRuntime = runtime;
|
|
3645
3673
|
this.replicationControl = {
|
|
3646
3674
|
status: async () => {
|
|
3675
|
+
const gate = this.replicaRestoreGate;
|
|
3676
|
+
if (gate) return this.config.replication.prefixes.map((prefix) => ({
|
|
3677
|
+
prefix,
|
|
3678
|
+
state: "gated",
|
|
3679
|
+
gate,
|
|
3680
|
+
errorCode: "REPLICA_GATED",
|
|
3681
|
+
pending: { inFlight: 0, committed: 0, ambiguous: 0 },
|
|
3682
|
+
pinned: [],
|
|
3683
|
+
lagMs: null
|
|
3684
|
+
}));
|
|
3647
3685
|
const status = await runtime.then((ready) => ready.control.status());
|
|
3648
3686
|
const scopeError = this.replicationScopeError;
|
|
3649
3687
|
if (!scopeError) return status;
|
|
@@ -3660,7 +3698,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3660
3698
|
lagMs: null
|
|
3661
3699
|
}));
|
|
3662
3700
|
},
|
|
3663
|
-
sync: async (input) =>
|
|
3701
|
+
sync: async (input) => {
|
|
3702
|
+
if (this.replicaRestoreGate) {
|
|
3703
|
+
throw Object.assign(new Error("Replica restore is gated."), {
|
|
3704
|
+
code: "REPLICA_GATED",
|
|
3705
|
+
gate: this.replicaRestoreGate
|
|
3706
|
+
});
|
|
3707
|
+
}
|
|
3708
|
+
return (await runtime).control.sync(input);
|
|
3709
|
+
},
|
|
3664
3710
|
purge: (input) => {
|
|
3665
3711
|
const instance = this.replicationRuntimeInstance;
|
|
3666
3712
|
return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
|
|
@@ -3876,6 +3922,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3876
3922
|
signer: this.signer,
|
|
3877
3923
|
signStrategy: config.signStrategy ?? { type: "auto-sign" },
|
|
3878
3924
|
wasmBindings: this.wasmBindings,
|
|
3925
|
+
...config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
|
|
3879
3926
|
sessionManager: this.sessionManager,
|
|
3880
3927
|
sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
|
|
3881
3928
|
domain: this.siweDomain,
|
|
@@ -4073,11 +4120,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4073
4120
|
return this.signer === null;
|
|
4074
4121
|
}
|
|
4075
4122
|
/**
|
|
4076
|
-
*
|
|
4077
|
-
* Available after signIn().
|
|
4123
|
+
* Primary space of the signed-in or restored network session, including compact sessions.
|
|
4078
4124
|
*/
|
|
4079
4125
|
get spaceId() {
|
|
4080
|
-
return this.auth?.tinyCloudSession?.spaceId;
|
|
4126
|
+
return this.auth?.tinyCloudSession?.spaceId ?? this._activeServiceSession?.spaceId;
|
|
4081
4127
|
}
|
|
4082
4128
|
/**
|
|
4083
4129
|
* Get the account space ID for this wallet identity.
|
|
@@ -4125,6 +4171,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4125
4171
|
get restorableSession() {
|
|
4126
4172
|
return this.currentTinyCloudSession();
|
|
4127
4173
|
}
|
|
4174
|
+
/** The active, verified replica stamp. Undefined when the replica is refused or the session has ended. */
|
|
4175
|
+
get replicaStamp() {
|
|
4176
|
+
const stamp = this.replicaSessionStamp;
|
|
4177
|
+
if (!stamp || this.replicaRestoreGate !== void 0 || !this.currentTinyCloudSession()) return void 0;
|
|
4178
|
+
return { incarnation: stamp.incarnation, lifecycleEpoch: stamp.lifecycleEpoch };
|
|
4179
|
+
}
|
|
4128
4180
|
/**
|
|
4129
4181
|
* Sign out and end the session on this instance.
|
|
4130
4182
|
*
|
|
@@ -4145,19 +4197,61 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4145
4197
|
}
|
|
4146
4198
|
async endSession() {
|
|
4147
4199
|
const leavingAddress = this.address ?? this._restoredTcSession?.address;
|
|
4200
|
+
const leavingChainId = this._chainId;
|
|
4201
|
+
const leavingPrincipal = this.replicaSessionPrincipal ?? (leavingAddress ? (0, import_sdk_core10.pkhDid)(leavingAddress, leavingChainId) : void 0);
|
|
4148
4202
|
const leavingAuth = this.auth;
|
|
4203
|
+
const leavingContext = this._serviceContext;
|
|
4204
|
+
let denied;
|
|
4149
4205
|
try {
|
|
4150
4206
|
this.teardownSession();
|
|
4151
4207
|
} finally {
|
|
4208
|
+
const denial = leavingPrincipal ? this.denyReplicaGate({ reason: "sign_out", principal: leavingPrincipal }, leavingContext) : Promise.resolve(void 0);
|
|
4152
4209
|
try {
|
|
4153
|
-
await
|
|
4154
|
-
if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
|
|
4210
|
+
denied = await denial;
|
|
4155
4211
|
} finally {
|
|
4156
|
-
|
|
4157
|
-
await
|
|
4212
|
+
try {
|
|
4213
|
+
await this.accountRegistryTail;
|
|
4214
|
+
await this.quiesceReplicaRuntime();
|
|
4215
|
+
} finally {
|
|
4216
|
+
if (leavingAddress) {
|
|
4217
|
+
await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
|
|
4218
|
+
}
|
|
4158
4219
|
}
|
|
4159
4220
|
}
|
|
4160
4221
|
}
|
|
4222
|
+
if (denied?.status === "unguarded") {
|
|
4223
|
+
throw new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`);
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
/**
|
|
4227
|
+
* Step 1 of replica sign-out. Never rejects: failures come back as `unguarded` so the session
|
|
4228
|
+
* is still cleared. Returns `undefined` when replication is off.
|
|
4229
|
+
*/
|
|
4230
|
+
async denyReplicaGate(input, context = this._serviceContext) {
|
|
4231
|
+
const replication = this.config.replication;
|
|
4232
|
+
if (!replication?.enabled) return void 0;
|
|
4233
|
+
try {
|
|
4234
|
+
const lifecycle = replication.storage.lifecycle;
|
|
4235
|
+
if (lifecycle?.kind !== (replication.storage.kind === "indexeddb" ? "indexeddb" : "file")) {
|
|
4236
|
+
return { status: "unguarded", code: "gate_unavailable" };
|
|
4237
|
+
}
|
|
4238
|
+
const result = await lifecycle.deny(input);
|
|
4239
|
+
if (result.status === "denied" || result.status === "deleted" || result.status === "marker") {
|
|
4240
|
+
try {
|
|
4241
|
+
(await getNodeReplicationLoaders().authority()).reportReplicaDenial(
|
|
4242
|
+
result,
|
|
4243
|
+
input.reason,
|
|
4244
|
+
replication.onEvent,
|
|
4245
|
+
context
|
|
4246
|
+
);
|
|
4247
|
+
} catch {
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
return result;
|
|
4251
|
+
} catch (error) {
|
|
4252
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "gate_unavailable";
|
|
4253
|
+
return { status: "unguarded", code };
|
|
4254
|
+
}
|
|
4161
4255
|
}
|
|
4162
4256
|
/**
|
|
4163
4257
|
* Synchronously drop every piece of in-memory session authority and
|
|
@@ -4176,15 +4270,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4176
4270
|
this._activeServiceSession = void 0;
|
|
4177
4271
|
oldGraph.retire();
|
|
4178
4272
|
oldCore?.retireServices();
|
|
4179
|
-
oldAuth?.resetSession(
|
|
4180
|
-
this.sessionManager,
|
|
4181
|
-
this.sessionKeyId,
|
|
4182
|
-
this.explicitHost ? [this.explicitHost] : void 0
|
|
4183
|
-
);
|
|
4184
4273
|
this.config.host = this.explicitHost ?? DEFAULT_HOST;
|
|
4185
4274
|
this._chainId = 1;
|
|
4186
4275
|
this._restoredNodeFeatures = void 0;
|
|
4187
|
-
this._activeServiceSession = void 0;
|
|
4188
4276
|
this._serviceContext = void 0;
|
|
4189
4277
|
this._kv = void 0;
|
|
4190
4278
|
this._sql = void 0;
|
|
@@ -4209,6 +4297,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4209
4297
|
this._bootstrapStatus = { skipped: false };
|
|
4210
4298
|
this.replicationDelegateSession = void 0;
|
|
4211
4299
|
this.replicationScopeError = void 0;
|
|
4300
|
+
this.replicaSessionPrincipal = void 0;
|
|
4301
|
+
this.replicaSessionStamp = void 0;
|
|
4302
|
+
this.replicaRestoreGate = void 0;
|
|
4212
4303
|
const sessionKey = createDefaultSessionKey(this.wasmBindings);
|
|
4213
4304
|
this.sessionManager = sessionKey.manager;
|
|
4214
4305
|
this.sessionKeyId = sessionKey.keyId;
|
|
@@ -4218,6 +4309,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4218
4309
|
sessionKey.keyId,
|
|
4219
4310
|
this.explicitHost ? [this.explicitHost] : void 0
|
|
4220
4311
|
);
|
|
4312
|
+
this.replicaSessionPrincipal = void 0;
|
|
4313
|
+
this.replicaSessionStamp = void 0;
|
|
4314
|
+
this.replicaRestoreGate = void 0;
|
|
4221
4315
|
this._serviceGraph = this.createServiceGraphLifetime();
|
|
4222
4316
|
this.auth = oldAuth;
|
|
4223
4317
|
this.tc = this.auth ? new import_sdk_core10.TinyCloud(this.auth, {
|
|
@@ -4247,11 +4341,25 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4247
4341
|
"Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
|
|
4248
4342
|
);
|
|
4249
4343
|
}
|
|
4344
|
+
await this.signInWith(() => this.tc.signIn(options));
|
|
4345
|
+
}
|
|
4346
|
+
/**
|
|
4347
|
+
* Fresh OpenKey sign-in. The callback receives the SDK's original SIWE and
|
|
4348
|
+
* session JWK before approval; the signed result is verified against those
|
|
4349
|
+
* bytes before the network session or replica stamp is committed.
|
|
4350
|
+
*/
|
|
4351
|
+
async signInWithOpenKey(authorizeFn, options) {
|
|
4352
|
+
if (!this.signer || !this.tc || !this.auth) {
|
|
4353
|
+
throw new Error("Cannot signInWithOpenKey() in session-only mode. Provide a signer in config.");
|
|
4354
|
+
}
|
|
4355
|
+
await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
|
|
4356
|
+
}
|
|
4357
|
+
async signInWith(performSignIn) {
|
|
4250
4358
|
const epoch = this.sessionEpoch;
|
|
4251
4359
|
const attempt = ++this.latestSessionAttempt;
|
|
4252
4360
|
await this.signOutInFlight;
|
|
4253
4361
|
try {
|
|
4254
|
-
await this.establishSignIn(epoch,
|
|
4362
|
+
await this.establishSignIn(epoch, performSignIn);
|
|
4255
4363
|
} catch (error) {
|
|
4256
4364
|
if (epoch !== this.sessionEpoch && attempt === this.latestSessionAttempt) {
|
|
4257
4365
|
await this.endSession();
|
|
@@ -4259,7 +4367,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4259
4367
|
throw error;
|
|
4260
4368
|
}
|
|
4261
4369
|
}
|
|
4262
|
-
async establishSignIn(epoch,
|
|
4370
|
+
async establishSignIn(epoch, performSignIn) {
|
|
4263
4371
|
if (!this.signer || !this.tc) {
|
|
4264
4372
|
throw new Error("Not signed in. Call signIn() first.");
|
|
4265
4373
|
}
|
|
@@ -4269,6 +4377,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4269
4377
|
const address = (0, import_sdk_core10.canonicalizeAddress)(await this.signer.getAddress());
|
|
4270
4378
|
const chainId = await this.signer.getChainId();
|
|
4271
4379
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4380
|
+
const nextPrincipal = (0, import_sdk_core10.pkhDid)(address, chainId);
|
|
4381
|
+
const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? (0, import_sdk_core10.pkhDid)(this.address, this._chainId) : void 0);
|
|
4382
|
+
if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
|
|
4383
|
+
const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
|
|
4384
|
+
this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
|
|
4385
|
+
}
|
|
4272
4386
|
if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
|
|
4273
4387
|
this._address = address;
|
|
4274
4388
|
this._chainId = chainId;
|
|
@@ -4286,7 +4400,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4286
4400
|
this.runtimePermissionGrants = [];
|
|
4287
4401
|
this.confirmedHostedSpaceIds.clear();
|
|
4288
4402
|
this.hostedOwnedSpaceIds.clear();
|
|
4289
|
-
|
|
4403
|
+
const signInAttempt = this.latestSessionAttempt;
|
|
4404
|
+
const previousReplicaGate = this.replicaRestoreGate;
|
|
4405
|
+
const previousReplicaStamp = this.replicaSessionStamp;
|
|
4406
|
+
this.replicaRestoreGate = void 0;
|
|
4407
|
+
this.replicaSessionStamp = void 0;
|
|
4408
|
+
try {
|
|
4409
|
+
await performSignIn();
|
|
4410
|
+
} catch (error) {
|
|
4411
|
+
if (epoch === this.sessionEpoch && signInAttempt === this.latestSessionAttempt) {
|
|
4412
|
+
this.replicaRestoreGate = previousReplicaGate;
|
|
4413
|
+
this.replicaSessionStamp = previousReplicaStamp;
|
|
4414
|
+
}
|
|
4415
|
+
throw error;
|
|
4416
|
+
}
|
|
4290
4417
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4291
4418
|
this.cancelOwnedSpaceHosts();
|
|
4292
4419
|
this.syncResolvedHostFromAuth();
|
|
@@ -4294,8 +4421,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4294
4421
|
this.hostedOwnedSpaceIds.add(spaceId);
|
|
4295
4422
|
}
|
|
4296
4423
|
this.replicationDelegateSession = void 0;
|
|
4424
|
+
this.replicaSessionPrincipal = (0, import_sdk_core10.pkhDid)(address, chainId);
|
|
4297
4425
|
this.replicationScopeError = void 0;
|
|
4298
|
-
|
|
4426
|
+
await this.quiesceReplicaRuntime();
|
|
4299
4427
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4300
4428
|
const oldGraph = this._serviceGraph;
|
|
4301
4429
|
this._serviceGraph = this.createServiceGraphLifetime();
|
|
@@ -4882,6 +5010,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4882
5010
|
if (this.ownStorageFull) {
|
|
4883
5011
|
return;
|
|
4884
5012
|
}
|
|
5013
|
+
if (!this.currentSessionCoversAccountRegistry()) {
|
|
5014
|
+
return;
|
|
5015
|
+
}
|
|
4885
5016
|
const controller = new AbortController();
|
|
4886
5017
|
const deadline = setTimeout(
|
|
4887
5018
|
() => controller.abort(new DOMException("Account registry deadline exceeded", "TimeoutError")),
|
|
@@ -4996,6 +5127,27 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4996
5127
|
(operation) => operation.service === "space" && this.actionContains(operation.action, "tinycloud.space/list")
|
|
4997
5128
|
);
|
|
4998
5129
|
}
|
|
5130
|
+
/**
|
|
5131
|
+
* Whether the current primary session's recap covers the account index read
|
|
5132
|
+
* (`tinycloud.sql/read` on the owned account space's `account` database),
|
|
5133
|
+
* the first call of every registry sync. As in
|
|
5134
|
+
* {@link currentSessionCanListSpaces}, a session with no parseable recap
|
|
5135
|
+
* keeps today's behavior.
|
|
5136
|
+
*/
|
|
5137
|
+
currentSessionCoversAccountRegistry() {
|
|
5138
|
+
const session = this.currentTinyCloudSession();
|
|
5139
|
+
const operations = session ? this.recapOperationsFromSession(session) : [];
|
|
5140
|
+
if (operations.length === 0) {
|
|
5141
|
+
return true;
|
|
5142
|
+
}
|
|
5143
|
+
const indexRead = {
|
|
5144
|
+
spaceId: this.ownedSpaceId(import_sdk_core10.ACCOUNT_REGISTRY_SPACE),
|
|
5145
|
+
service: "sql",
|
|
5146
|
+
path: "account",
|
|
5147
|
+
action: "tinycloud.sql/read"
|
|
5148
|
+
};
|
|
5149
|
+
return operations.some((granted) => this.operationCovers(granted, indexRead));
|
|
5150
|
+
}
|
|
4999
5151
|
async withAccountRegistryRetry(task, signal) {
|
|
5000
5152
|
const delays = [250, 1e3, 3e3];
|
|
5001
5153
|
const stopped = (detail) => {
|
|
@@ -5462,11 +5614,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5462
5614
|
);
|
|
5463
5615
|
const stagedHost = resolvedHost ?? this.config.host;
|
|
5464
5616
|
let restoredNodeFeatures;
|
|
5617
|
+
let replicaProtocolMismatch = false;
|
|
5465
5618
|
if (this.config.replication?.enabled) {
|
|
5466
5619
|
try {
|
|
5467
5620
|
restoredNodeFeatures = (await (0, import_sdk_core10.checkNodeInfo)(stagedHost, this.wasmBindings.protocolVersion())).features;
|
|
5468
5621
|
} catch (error) {
|
|
5469
|
-
|
|
5622
|
+
replicaProtocolMismatch = error instanceof import_sdk_core10.ProtocolMismatchError;
|
|
5470
5623
|
}
|
|
5471
5624
|
}
|
|
5472
5625
|
const stagedAddress = restoredAddress;
|
|
@@ -5474,28 +5627,55 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5474
5627
|
let replicationPrincipal;
|
|
5475
5628
|
let compactReplicationSession;
|
|
5476
5629
|
let replicationScopeError;
|
|
5630
|
+
let replicationScopeUnavailable = false;
|
|
5477
5631
|
if (this.config.replication?.enabled) {
|
|
5478
5632
|
if (stagedAddress !== void 0) {
|
|
5479
5633
|
replicationPrincipal = (0, import_sdk_core10.pkhDid)(stagedAddress, stagedChainId);
|
|
5480
5634
|
} else {
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
delegationCid: sessionData.delegationCid,
|
|
5485
|
-
verificationMethod: restoredVerificationMethod,
|
|
5486
|
-
persistedSpace: sessionData.spaceId
|
|
5487
|
-
});
|
|
5488
|
-
if (!signedScope) {
|
|
5489
|
-
replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
|
|
5490
|
-
} else {
|
|
5491
|
-
replicationPrincipal = signedScope.principal;
|
|
5492
|
-
compactReplicationSession = {
|
|
5635
|
+
try {
|
|
5636
|
+
const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
|
|
5637
|
+
const signedScope = replicationScopeFromSignedSession({
|
|
5493
5638
|
delegationHeader: sessionData.delegationHeader,
|
|
5494
5639
|
delegationCid: sessionData.delegationCid,
|
|
5495
|
-
spaceId: signedScope.space,
|
|
5496
5640
|
verificationMethod: restoredVerificationMethod,
|
|
5497
|
-
|
|
5498
|
-
};
|
|
5641
|
+
persistedSpace: sessionData.spaceId
|
|
5642
|
+
});
|
|
5643
|
+
if (!signedScope) {
|
|
5644
|
+
replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
|
|
5645
|
+
} else {
|
|
5646
|
+
replicationPrincipal = signedScope.principal;
|
|
5647
|
+
compactReplicationSession = {
|
|
5648
|
+
delegationHeader: sessionData.delegationHeader,
|
|
5649
|
+
delegationCid: sessionData.delegationCid,
|
|
5650
|
+
spaceId: signedScope.space,
|
|
5651
|
+
verificationMethod: restoredVerificationMethod,
|
|
5652
|
+
jwk: stagedJwk
|
|
5653
|
+
};
|
|
5654
|
+
}
|
|
5655
|
+
} catch {
|
|
5656
|
+
replicationScopeUnavailable = true;
|
|
5657
|
+
replicationScopeError = "Replica scope could not be verified for this network session.";
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
let replicaRestoreGate;
|
|
5662
|
+
let restoredReplicaStamp;
|
|
5663
|
+
const replication = this.config.replication;
|
|
5664
|
+
if (replication?.enabled) {
|
|
5665
|
+
if (replicaProtocolMismatch) {
|
|
5666
|
+
replicaRestoreGate = "gate_unavailable";
|
|
5667
|
+
} else if (replicationPrincipal === void 0) {
|
|
5668
|
+
replicaRestoreGate = replicationScopeUnavailable ? "gate_unavailable" : "principal_mismatch";
|
|
5669
|
+
} else {
|
|
5670
|
+
try {
|
|
5671
|
+
const { restoreReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
|
|
5672
|
+
restoredReplicaStamp = await restoreReplicaSessionStamp(
|
|
5673
|
+
replication.storage,
|
|
5674
|
+
replicationPrincipal,
|
|
5675
|
+
sessionData.replicaStamp ?? void 0
|
|
5676
|
+
);
|
|
5677
|
+
} catch (error) {
|
|
5678
|
+
replicaRestoreGate = replicaRestoreRefusal(error);
|
|
5499
5679
|
}
|
|
5500
5680
|
}
|
|
5501
5681
|
}
|
|
@@ -5588,7 +5768,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5588
5768
|
}
|
|
5589
5769
|
this.replicationDelegateSession = compactReplicationSession;
|
|
5590
5770
|
this.replicationScopeError = replicationScopeError;
|
|
5591
|
-
|
|
5771
|
+
this.replicaRestoreGate = replicaRestoreGate;
|
|
5772
|
+
this.replicaSessionPrincipal = replicationPrincipal;
|
|
5773
|
+
this.replicaSessionStamp = restoredReplicaStamp;
|
|
5774
|
+
await this.quiesceReplicaRuntime();
|
|
5592
5775
|
if (epoch !== this.sessionEpoch) {
|
|
5593
5776
|
oldGraph.retire();
|
|
5594
5777
|
oldCore?.retireServices?.();
|
|
@@ -5603,7 +5786,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5603
5786
|
detail: replicationScopeError
|
|
5604
5787
|
});
|
|
5605
5788
|
}
|
|
5606
|
-
await this.
|
|
5789
|
+
await this.bindReplicaOrGate(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
|
|
5607
5790
|
oldCore?.retireServices?.();
|
|
5608
5791
|
if (epoch !== this.sessionEpoch) {
|
|
5609
5792
|
if (attempt === this.latestSessionAttempt) await this.endSession();
|
|
@@ -5736,6 +5919,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5736
5919
|
session: input.serviceSession,
|
|
5737
5920
|
invoke: graph.invoke,
|
|
5738
5921
|
invokeAny: graph.invokeAny,
|
|
5922
|
+
signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
|
|
5739
5923
|
fetch: graph.fetch
|
|
5740
5924
|
});
|
|
5741
5925
|
const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
|
|
@@ -6079,6 +6263,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6079
6263
|
signer: this.signer,
|
|
6080
6264
|
signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
|
|
6081
6265
|
wasmBindings: this.wasmBindings,
|
|
6266
|
+
...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
|
|
6082
6267
|
sessionManager: this.sessionManager,
|
|
6083
6268
|
sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
|
|
6084
6269
|
domain: this.siweDomain,
|
|
@@ -6137,6 +6322,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6137
6322
|
signer: this.signer,
|
|
6138
6323
|
signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
|
|
6139
6324
|
wasmBindings: this.wasmBindings,
|
|
6325
|
+
...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
|
|
6140
6326
|
sessionManager: this.sessionManager,
|
|
6141
6327
|
sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
|
|
6142
6328
|
domain: this.siweDomain,
|
|
@@ -6217,7 +6403,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6217
6403
|
jwk: session.jwk
|
|
6218
6404
|
};
|
|
6219
6405
|
this._serviceContext.setSession(serviceSession);
|
|
6220
|
-
await this.
|
|
6406
|
+
await this.bindReplicaOrGate(this._serviceContext, serviceSession, this._kv);
|
|
6221
6407
|
this.tc.serviceContext.setSession(serviceSession);
|
|
6222
6408
|
this._vault = this.createVaultService(session.spaceId, this._kv);
|
|
6223
6409
|
this._vault.initialize(this._serviceContext);
|
|
@@ -6611,6 +6797,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6611
6797
|
session: serviceSession,
|
|
6612
6798
|
invoke: graph.invoke,
|
|
6613
6799
|
invokeAny: graph.invokeAny,
|
|
6800
|
+
signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
|
|
6614
6801
|
fetch: graph.fetch
|
|
6615
6802
|
});
|
|
6616
6803
|
this._spaceService = new import_sdk_core10.SpaceService({
|
|
@@ -6746,11 +6933,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6746
6933
|
* Both SharingService (single-entry) and
|
|
6747
6934
|
* {@link TinyCloudNode.delegateTo} (multi-entry) drive this through
|
|
6748
6935
|
* the same code path so there's exactly one place that touches the
|
|
6749
|
-
* WASM boundary.
|
|
6936
|
+
* WASM boundary. `terminal` adds the node's terminal marker, which the
|
|
6937
|
+
* WASM builder cannot emit.
|
|
6750
6938
|
*
|
|
6751
6939
|
* @internal
|
|
6752
6940
|
*/
|
|
6753
|
-
createDelegationWrapper(params) {
|
|
6941
|
+
createDelegationWrapper(params, terminal = false) {
|
|
6754
6942
|
const wasmSession = {
|
|
6755
6943
|
delegationHeader: params.session.delegationHeader,
|
|
6756
6944
|
delegationCid: params.session.delegationCid,
|
|
@@ -6770,8 +6958,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6770
6958
|
params.notBeforeSecs
|
|
6771
6959
|
);
|
|
6772
6960
|
return {
|
|
6773
|
-
delegation: result.delegation,
|
|
6774
|
-
cid: result.cid,
|
|
6961
|
+
...terminal ? (0, import_sdk_core10.markDelegationTerminal)(result.delegation, params.session.jwk) : { delegation: result.delegation, cid: result.cid },
|
|
6775
6962
|
// Rust serde `rename_all = "camelCase"` emits `delegateDid`
|
|
6776
6963
|
// (lowercase d); the TypeScript interface uses `delegateDID`
|
|
6777
6964
|
// (historical, matches Delegation.delegateDID). Normalize here.
|
|
@@ -6913,7 +7100,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6913
7100
|
};
|
|
6914
7101
|
const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core10.canonicalizeEncryptionJson)(body)))));
|
|
6915
7102
|
const request = { ...body, requestBodyDigest };
|
|
6916
|
-
const authorization = authorizationHeader(this.
|
|
7103
|
+
const authorization = authorizationHeader(this.wasmBindings.invoke(serviceSession, "kv", input.resourcePath, "tinycloud.kv/get"));
|
|
6917
7104
|
const response = await fetch(`${this.config.host}/policy/v3/deliveries/authorize`, {
|
|
6918
7105
|
method: "POST",
|
|
6919
7106
|
headers: { accept: "application/json", "content-type": "application/json", authorization },
|
|
@@ -7228,6 +7415,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7228
7415
|
}
|
|
7229
7416
|
};
|
|
7230
7417
|
}
|
|
7418
|
+
if (result.status === "aad_mismatch") return { status: "read_failed" };
|
|
7231
7419
|
return { status: result.status };
|
|
7232
7420
|
}
|
|
7233
7421
|
if (!isSecretPayload(result.entry.value)) {
|
|
@@ -7903,7 +8091,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7903
8091
|
return this.delegationManager.create(params);
|
|
7904
8092
|
}
|
|
7905
8093
|
/**
|
|
7906
|
-
* Revoke a delegation
|
|
8094
|
+
* Revoke a delegation with the owner wallet when available. Session-only
|
|
8095
|
+
* clients continue to use their delegated revocation authority.
|
|
7907
8096
|
*
|
|
7908
8097
|
* @param cid - The CID of the delegation to revoke
|
|
7909
8098
|
* @returns Result indicating success or failure
|
|
@@ -8019,6 +8208,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8019
8208
|
effectiveExpiration = sessionExpiry;
|
|
8020
8209
|
}
|
|
8021
8210
|
if (options?.forceWalletSign) {
|
|
8211
|
+
if (options.disableSubDelegation) {
|
|
8212
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
8213
|
+
}
|
|
8022
8214
|
if (expandedEntries.length > 1) {
|
|
8023
8215
|
throw new Error(
|
|
8024
8216
|
"delegateTo with forceWalletSign=true supports at most one PermissionEntry. Multi-entry requests must go through the session-key UCAN path (drop forceWalletSign) or the legacy createDelegation method."
|
|
@@ -8054,7 +8246,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8054
8246
|
plan.effectiveExpiration,
|
|
8055
8247
|
plan.grant,
|
|
8056
8248
|
plan.operations,
|
|
8057
|
-
onPrepared
|
|
8249
|
+
onPrepared,
|
|
8250
|
+
options?.disableSubDelegation === true
|
|
8058
8251
|
);
|
|
8059
8252
|
return { delegation: delegation2, prompted: false };
|
|
8060
8253
|
}
|
|
@@ -8063,7 +8256,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8063
8256
|
expandedEntries,
|
|
8064
8257
|
effectiveExpiration,
|
|
8065
8258
|
session,
|
|
8066
|
-
onPrepared
|
|
8259
|
+
onPrepared,
|
|
8260
|
+
options?.disableSubDelegation === true
|
|
8067
8261
|
);
|
|
8068
8262
|
return { delegation, prompted: false };
|
|
8069
8263
|
}
|
|
@@ -8178,34 +8372,67 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8178
8372
|
return auth.replicationSignInEntries();
|
|
8179
8373
|
}
|
|
8180
8374
|
async replicationAuthority() {
|
|
8181
|
-
const { createReplicationAuthority } = await getNodeReplicationLoaders().authority();
|
|
8375
|
+
const { createReplicationAuthority, mintNodeReplicationGrant, verifiedOwnerConsentAnchor } = await getNodeReplicationLoaders().authority();
|
|
8182
8376
|
return createReplicationAuthority({
|
|
8183
8377
|
replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
|
|
8378
|
+
verifiedConsentAnchor: (session) => {
|
|
8379
|
+
const current = this.currentTinyCloudSession();
|
|
8380
|
+
return session === current && current !== void 0 ? verifiedOwnerConsentAnchor(current, this.wasmBindings, exactVerifiedRecap) : null;
|
|
8381
|
+
},
|
|
8184
8382
|
siweExpiration: (siwe) => extractSiweExpiration(siwe),
|
|
8185
8383
|
planDelegation: (entries, options) => this.planDelegation(entries, options),
|
|
8186
|
-
mintDelegation:
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8384
|
+
mintDelegation: (deviceDid, entries, options) => mintNodeReplicationGrant(
|
|
8385
|
+
deviceDid,
|
|
8386
|
+
entries,
|
|
8387
|
+
options,
|
|
8388
|
+
options?.renewalOnly ? this.currentTinyCloudSession() : void 0,
|
|
8389
|
+
this
|
|
8390
|
+
)
|
|
8193
8391
|
});
|
|
8194
8392
|
}
|
|
8195
|
-
async
|
|
8393
|
+
async quiesceReplicaRuntime() {
|
|
8196
8394
|
if (!this.replicationRuntime) return;
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8395
|
+
try {
|
|
8396
|
+
await (await this.replicationRuntime).unbind();
|
|
8397
|
+
} catch {
|
|
8398
|
+
this.replicaRestoreGate = "gate_unavailable";
|
|
8399
|
+
}
|
|
8400
|
+
}
|
|
8401
|
+
async bindReplicaOrGate(context, session, kv, principal) {
|
|
8402
|
+
if (!this.replicationRuntime) return;
|
|
8403
|
+
const gate = this.replicaRestoreGate;
|
|
8404
|
+
const epoch = this.sessionEpoch;
|
|
8405
|
+
const attempt = this.latestSessionAttempt;
|
|
8406
|
+
try {
|
|
8407
|
+
const runtime = await this.replicationRuntime;
|
|
8408
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context || gate !== this.replicaRestoreGate) return;
|
|
8409
|
+
if (gate) {
|
|
8410
|
+
await runtime.bindGatedNetworkOnly({
|
|
8411
|
+
context,
|
|
8412
|
+
session,
|
|
8413
|
+
gate,
|
|
8414
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
8415
|
+
}, getNodeReplicationLoaders().gatedNetwork);
|
|
8416
|
+
return;
|
|
8417
|
+
}
|
|
8418
|
+
const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : (0, import_sdk_core10.pkhDid)(this._address, this._chainId));
|
|
8419
|
+
if (identityPrincipal === void 0) return;
|
|
8420
|
+
const authority = await this.replicationAuthority();
|
|
8421
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
|
|
8422
|
+
await runtime.bind({
|
|
8423
|
+
context,
|
|
8424
|
+
session,
|
|
8425
|
+
principal: identityPrincipal,
|
|
8426
|
+
stamp: this.replicaSessionStamp,
|
|
8427
|
+
authority,
|
|
8428
|
+
nodeFeatures: this.replicationNodeFeatures,
|
|
8429
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
8430
|
+
});
|
|
8431
|
+
} catch {
|
|
8432
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
|
|
8433
|
+
this.replicaRestoreGate = "gate_unavailable";
|
|
8434
|
+
await this.quiesceReplicaRuntime();
|
|
8435
|
+
}
|
|
8209
8436
|
}
|
|
8210
8437
|
/** Stable control facade for this node's optional replication runtime. */
|
|
8211
8438
|
get replication() {
|
|
@@ -8261,7 +8488,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8261
8488
|
*
|
|
8262
8489
|
* @internal
|
|
8263
8490
|
*/
|
|
8264
|
-
async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared) {
|
|
8491
|
+
async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared, disableSubDelegation = false) {
|
|
8265
8492
|
if (entries.length === 0) {
|
|
8266
8493
|
throw new Error(
|
|
8267
8494
|
"createDelegationViaWasmPath requires a non-empty entries array"
|
|
@@ -8321,7 +8548,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8321
8548
|
spaceId,
|
|
8322
8549
|
abilities,
|
|
8323
8550
|
expirationSecs
|
|
8324
|
-
});
|
|
8551
|
+
}, disableSubDelegation);
|
|
8325
8552
|
const primary = result.resources[0];
|
|
8326
8553
|
const delegationHeader = { Authorization: result.delegation };
|
|
8327
8554
|
const delegation = {
|
|
@@ -8331,7 +8558,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8331
8558
|
path: primary.path,
|
|
8332
8559
|
actions: primary.actions,
|
|
8333
8560
|
resources: result.resources,
|
|
8334
|
-
disableSubDelegation
|
|
8561
|
+
disableSubDelegation,
|
|
8335
8562
|
expiry: result.expiry,
|
|
8336
8563
|
delegateDID: did,
|
|
8337
8564
|
ownerAddress: session.address,
|
|
@@ -8353,7 +8580,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8353
8580
|
}
|
|
8354
8581
|
return delegation;
|
|
8355
8582
|
}
|
|
8356
|
-
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared) {
|
|
8583
|
+
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared, disableSubDelegation = false) {
|
|
8357
8584
|
this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
|
|
8358
8585
|
this.assertDelegationCaveatsPreservable(entries);
|
|
8359
8586
|
const result = this.createDelegationWrapper({
|
|
@@ -8362,7 +8589,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8362
8589
|
spaceId: grant.session.spaceId,
|
|
8363
8590
|
abilities: this.permissionsToAbilities(entries),
|
|
8364
8591
|
expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
|
|
8365
|
-
});
|
|
8592
|
+
}, disableSubDelegation);
|
|
8366
8593
|
const primary = result.resources[0];
|
|
8367
8594
|
const delegationHeader = { Authorization: result.delegation };
|
|
8368
8595
|
const targetHost = grant.delegation.host ?? this.config.host;
|
|
@@ -8373,7 +8600,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8373
8600
|
path: primary.path,
|
|
8374
8601
|
actions: primary.actions,
|
|
8375
8602
|
resources: result.resources,
|
|
8376
|
-
disableSubDelegation
|
|
8603
|
+
disableSubDelegation,
|
|
8377
8604
|
expiry: result.expiry,
|
|
8378
8605
|
delegateDID: did,
|
|
8379
8606
|
ownerAddress: grant.delegation.ownerAddress,
|
|
@@ -8957,11 +9184,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8957
9184
|
params.spaceIdOverride
|
|
8958
9185
|
);
|
|
8959
9186
|
try {
|
|
8960
|
-
const result = await this.delegateTo(
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
);
|
|
9187
|
+
const result = await this.delegateTo(resolvedDelegateDID, entries, {
|
|
9188
|
+
...params.expiryMs !== void 0 ? { expiry: params.expiryMs } : {},
|
|
9189
|
+
disableSubDelegation: params.disableSubDelegation
|
|
9190
|
+
});
|
|
8965
9191
|
return result.delegation;
|
|
8966
9192
|
} catch (err2) {
|
|
8967
9193
|
if (err2 instanceof import_sdk_core10.PermissionNotInManifestError) {
|
|
@@ -8982,6 +9208,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8982
9208
|
* @internal
|
|
8983
9209
|
*/
|
|
8984
9210
|
async createDelegationWalletPath(params) {
|
|
9211
|
+
if (params.disableSubDelegation) {
|
|
9212
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
9213
|
+
}
|
|
8985
9214
|
if (!this.signer) {
|
|
8986
9215
|
throw new Error("Cannot createDelegation() in session-only mode. Requires wallet mode.");
|
|
8987
9216
|
}
|
|
@@ -9037,7 +9266,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9037
9266
|
spaceId: params.spaceIdOverride ?? session.spaceId,
|
|
9038
9267
|
path: params.path,
|
|
9039
9268
|
actions: params.actions,
|
|
9040
|
-
disableSubDelegation:
|
|
9269
|
+
disableSubDelegation: false,
|
|
9041
9270
|
expiry: expirationTime,
|
|
9042
9271
|
delegateDID: params.delegateDID,
|
|
9043
9272
|
ownerAddress: session.address,
|
|
@@ -9080,7 +9309,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9080
9309
|
spaceId: publicSpaceId,
|
|
9081
9310
|
path: "",
|
|
9082
9311
|
actions: [import_sdk_core10.KV.GET, import_sdk_core10.KV.PUT, import_sdk_core10.KV.METADATA],
|
|
9083
|
-
disableSubDelegation:
|
|
9312
|
+
disableSubDelegation: false,
|
|
9084
9313
|
expiry: expirationTime,
|
|
9085
9314
|
delegateDID: params.delegateDID,
|
|
9086
9315
|
ownerAddress: session.address,
|
|
@@ -9247,6 +9476,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9247
9476
|
if (parentDelegation.disableSubDelegation) {
|
|
9248
9477
|
throw new Error("Parent delegation does not allow sub-delegation");
|
|
9249
9478
|
}
|
|
9479
|
+
if (params.disableSubDelegation) {
|
|
9480
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
9481
|
+
}
|
|
9250
9482
|
const parentResources = parentDelegation.resources ?? [{
|
|
9251
9483
|
service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
|
|
9252
9484
|
space: parentDelegation.spaceId,
|
|
@@ -9319,7 +9551,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9319
9551
|
spaceId: parentDelegation.spaceId,
|
|
9320
9552
|
path: requestedResources[0].path,
|
|
9321
9553
|
actions: requestedResources[0].actions,
|
|
9322
|
-
disableSubDelegation:
|
|
9554
|
+
disableSubDelegation: false,
|
|
9323
9555
|
expiry: actualExpiry,
|
|
9324
9556
|
delegateDID: params.delegateDID,
|
|
9325
9557
|
ownerAddress: parentDelegation.ownerAddress,
|
|
@@ -9436,6 +9668,7 @@ var import_sdk_core22 = require("@tinycloud/sdk-core");
|
|
|
9436
9668
|
expandActionShortNames,
|
|
9437
9669
|
expandPermissionEntries,
|
|
9438
9670
|
expandPermissionEntry,
|
|
9671
|
+
getNodeReplicationLoaders,
|
|
9439
9672
|
getTinyCloudDebugLogs,
|
|
9440
9673
|
installTinyCloudDebugGlobals,
|
|
9441
9674
|
isCapabilitySubset,
|