@tinycloud/node-sdk 3.2.6-beta.6 → 3.3.0-beta.10
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 +400 -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 +407 -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 +2734 -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 +2729 -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.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
makePkhSpaceId as makePkhSpaceId2,
|
|
31
31
|
parsePkhDid,
|
|
32
32
|
pkhDid as pkhDid3,
|
|
33
|
-
principalDid
|
|
33
|
+
principalDid,
|
|
34
34
|
principalDidEquals as principalDidEquals4,
|
|
35
35
|
parseCanonicalNetworkId,
|
|
36
36
|
TinyCloudDebugLogger,
|
|
@@ -454,6 +454,7 @@ var NodeUserAuthorization = class {
|
|
|
454
454
|
this.siweConfig = config.siweConfig;
|
|
455
455
|
this.includeAccountRegistryPermissions = config.includeAccountRegistryPermissions ?? true;
|
|
456
456
|
this.replication = config.replication?.enabled === true ? config.replication : void 0;
|
|
457
|
+
this.establishReplicaStamp = config.establishReplicaStamp;
|
|
457
458
|
this._manifest = config.manifest;
|
|
458
459
|
this._capabilityRequest = config.capabilityRequest;
|
|
459
460
|
this.sessionManager = config.sessionManager ?? this.wasm.createSessionManager();
|
|
@@ -1223,6 +1224,27 @@ var NodeUserAuthorization = class {
|
|
|
1223
1224
|
signature
|
|
1224
1225
|
};
|
|
1225
1226
|
}
|
|
1227
|
+
persistedData(session, expiresAt, createdAt, replicaStamp) {
|
|
1228
|
+
return {
|
|
1229
|
+
address: session.address,
|
|
1230
|
+
chainId: session.chainId,
|
|
1231
|
+
sessionKey: JSON.stringify(session.jwk),
|
|
1232
|
+
siwe: session.siwe,
|
|
1233
|
+
signature: session.signature,
|
|
1234
|
+
tinycloudSession: {
|
|
1235
|
+
delegationHeader: session.delegationHeader,
|
|
1236
|
+
delegationCid: session.delegationCid,
|
|
1237
|
+
spaceId: session.spaceId,
|
|
1238
|
+
spaces: session.spaces,
|
|
1239
|
+
verificationMethod: session.verificationMethod
|
|
1240
|
+
},
|
|
1241
|
+
expiresAt,
|
|
1242
|
+
createdAt,
|
|
1243
|
+
version: "1.0",
|
|
1244
|
+
tinycloudHosts: this.tinycloudHosts,
|
|
1245
|
+
...replicaStamp ? { replicaStamp } : {}
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1226
1248
|
/**
|
|
1227
1249
|
* Sign in and create a new session.
|
|
1228
1250
|
*
|
|
@@ -1302,24 +1324,13 @@ var NodeUserAuthorization = class {
|
|
|
1302
1324
|
siwe: prepared.siwe,
|
|
1303
1325
|
signature
|
|
1304
1326
|
};
|
|
1305
|
-
const
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
delegationHeader: session.delegationHeader,
|
|
1313
|
-
delegationCid: session.delegationCid,
|
|
1314
|
-
spaceId,
|
|
1315
|
-
spaces: spacesMetadata,
|
|
1316
|
-
verificationMethod: this.sessionManager.getDID(keyId)
|
|
1317
|
-
},
|
|
1318
|
-
expiresAt: expirationTime.toISOString(),
|
|
1319
|
-
createdAt: now.toISOString(),
|
|
1320
|
-
version: "1.0",
|
|
1321
|
-
tinycloudHosts: this.tinycloudHosts
|
|
1322
|
-
};
|
|
1327
|
+
const replicaStamp = await this.establishReplicaStamp?.(tinyCloudSession);
|
|
1328
|
+
const persistedData = this.persistedData(
|
|
1329
|
+
tinyCloudSession,
|
|
1330
|
+
expirationTime.toISOString(),
|
|
1331
|
+
now.toISOString(),
|
|
1332
|
+
replicaStamp
|
|
1333
|
+
);
|
|
1323
1334
|
await this.enqueuePersistence(async () => {
|
|
1324
1335
|
await this.sessionStorage.save(address, persistedData);
|
|
1325
1336
|
if (generation !== this.sessionGeneration) {
|
|
@@ -1473,28 +1484,12 @@ var NodeUserAuthorization = class {
|
|
|
1473
1484
|
siwe: prepared.siwe,
|
|
1474
1485
|
signature
|
|
1475
1486
|
};
|
|
1476
|
-
const expirationMatch = prepared.siwe
|
|
1477
|
-
const issuedAtMatch = prepared.siwe
|
|
1487
|
+
const expirationMatch = prepared.siwe?.match(/Expiration Time: (.+)/);
|
|
1488
|
+
const issuedAtMatch = prepared.siwe?.match(/Issued At: (.+)/);
|
|
1478
1489
|
const expiresAt = expirationMatch?.[1] ?? new Date(Date.now() + this.sessionExpirationMs).toISOString();
|
|
1479
1490
|
const createdAt = issuedAtMatch?.[1] ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1482
|
-
chainId,
|
|
1483
|
-
sessionKey: JSON.stringify(jwk),
|
|
1484
|
-
siwe: prepared.siwe,
|
|
1485
|
-
signature,
|
|
1486
|
-
tinycloudSession: {
|
|
1487
|
-
delegationHeader: session.delegationHeader,
|
|
1488
|
-
delegationCid: session.delegationCid,
|
|
1489
|
-
spaceId: prepared.spaceId,
|
|
1490
|
-
spaces: spacesMetadata,
|
|
1491
|
-
verificationMethod: this.sessionManager.getDID(keyId)
|
|
1492
|
-
},
|
|
1493
|
-
expiresAt,
|
|
1494
|
-
createdAt,
|
|
1495
|
-
version: "1.0",
|
|
1496
|
-
tinycloudHosts: this.tinycloudHosts
|
|
1497
|
-
};
|
|
1491
|
+
const replicaStamp = await this.establishReplicaStamp?.(tinyCloudSession);
|
|
1492
|
+
const persistedData = this.persistedData(tinyCloudSession, expiresAt, createdAt, replicaStamp);
|
|
1498
1493
|
await this.sessionStorage.save(address, persistedData);
|
|
1499
1494
|
this._session = clientSession;
|
|
1500
1495
|
this._tinyCloudSession = tinyCloudSession;
|
|
@@ -1557,15 +1552,36 @@ var NodeUserAuthorization = class {
|
|
|
1557
1552
|
const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
|
|
1558
1553
|
const presentation = this.buildPresentationEnvelope(options?.reason);
|
|
1559
1554
|
const hostHint = this.primaryTinyCloudHost;
|
|
1560
|
-
const
|
|
1555
|
+
const originalRequest = {
|
|
1561
1556
|
protocolVersion: 1,
|
|
1562
1557
|
siwe: prepared.siwe,
|
|
1563
1558
|
jwk,
|
|
1559
|
+
verificationMethod: prepared.verificationMethod,
|
|
1564
1560
|
host: hostHint,
|
|
1565
1561
|
keyId: options?.openkeyKeyId,
|
|
1566
1562
|
presentation
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1563
|
+
};
|
|
1564
|
+
let result;
|
|
1565
|
+
let signed;
|
|
1566
|
+
let signature;
|
|
1567
|
+
for (let attempt = 0; ; ) {
|
|
1568
|
+
try {
|
|
1569
|
+
result ?? (result = await authorizeFn(originalRequest));
|
|
1570
|
+
signed = await this.verifyOpenKeyResult(result, prepared);
|
|
1571
|
+
signature = result.signature;
|
|
1572
|
+
break;
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
if (!options?.retryResult) throw error;
|
|
1575
|
+
const next = await options.retryResult(
|
|
1576
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1577
|
+
originalRequest,
|
|
1578
|
+
++attempt
|
|
1579
|
+
);
|
|
1580
|
+
if (next === null) throw error;
|
|
1581
|
+
result = next;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return this.signInWithPreparedSession(signed, signature, keyId, jwk);
|
|
1569
1585
|
}
|
|
1570
1586
|
/**
|
|
1571
1587
|
* Build a `CapabilityPresentationEnvelopeV1` from `this._manifest`. The
|
|
@@ -1645,6 +1661,11 @@ var NodeUserAuthorization = class {
|
|
|
1645
1661
|
* (resource, action) pair in the returned SIWE.
|
|
1646
1662
|
*/
|
|
1647
1663
|
async signInWithOpenKeyResult(result, prepared, keyId, jwk) {
|
|
1664
|
+
const signed = await this.verifyOpenKeyResult(result, prepared);
|
|
1665
|
+
return this.signInWithPreparedSession(signed, result.signature, keyId, jwk);
|
|
1666
|
+
}
|
|
1667
|
+
/** Validate a candidate without activating a network session, so paste retries reuse one preparation. */
|
|
1668
|
+
async verifyOpenKeyResult(result, prepared) {
|
|
1648
1669
|
if (result.protocolVersion !== 1) {
|
|
1649
1670
|
throw new Error(`Unsupported OpenKey protocol version ${result.protocolVersion}`);
|
|
1650
1671
|
}
|
|
@@ -1889,17 +1910,12 @@ var NodeUserAuthorization = class {
|
|
|
1889
1910
|
}
|
|
1890
1911
|
}
|
|
1891
1912
|
void nonRequiredGrantedPairs;
|
|
1892
|
-
return
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
},
|
|
1899
|
-
result.signature,
|
|
1900
|
-
keyId,
|
|
1901
|
-
jwk
|
|
1902
|
-
);
|
|
1913
|
+
return {
|
|
1914
|
+
siwe: result.signedMessage,
|
|
1915
|
+
jwk: prepared.jwk,
|
|
1916
|
+
spaceId: prepared.spaceId,
|
|
1917
|
+
verificationMethod: prepared.verificationMethod
|
|
1918
|
+
};
|
|
1903
1919
|
}
|
|
1904
1920
|
/**
|
|
1905
1921
|
* Clear persisted session data.
|
|
@@ -2462,45 +2478,10 @@ function deserializeDelegation(data) {
|
|
|
2462
2478
|
|
|
2463
2479
|
// src/TinyCloudNode.ts
|
|
2464
2480
|
import { generateNonce, SiweMessage as SiweMessage3 } from "siwe";
|
|
2465
|
-
|
|
2466
|
-
// src/replication/config.ts
|
|
2467
|
-
import { assertFolderPrefix as assertPureFolderPrefix, kvPrefixCovers } from "@tinycloud/sdk-services/kv/replication/prefix";
|
|
2468
|
-
function assertValidReplicationConfig(replication) {
|
|
2469
|
-
if (replication === void 0 || replication.enabled !== true) return;
|
|
2470
|
-
if (replication.storage === void 0 || replication.storage === null) {
|
|
2471
|
-
throw new TypeError("replication.enabled requires replication.storage");
|
|
2472
|
-
}
|
|
2473
|
-
if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {
|
|
2474
|
-
throw new TypeError("replication.prefixes must be a non-empty array");
|
|
2475
|
-
}
|
|
2476
|
-
for (const prefix of replication.prefixes) {
|
|
2477
|
-
try {
|
|
2478
|
-
assertPureFolderPrefix(prefix);
|
|
2479
|
-
} catch (error) {
|
|
2480
|
-
throw new TypeError(error instanceof Error ? error.message : String(error));
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
for (const [index, prefix] of replication.prefixes.entries()) {
|
|
2484
|
-
for (const other of replication.prefixes.slice(index + 1)) {
|
|
2485
|
-
if (kvPrefixCovers(prefix, other) || kvPrefixCovers(other, prefix)) {
|
|
2486
|
-
throw new TypeError(
|
|
2487
|
-
`replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
|
|
2488
|
-
);
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
if ((prefix === "/" || prefix.split("/", 1)[0] === "vault") && replication.allowSecrets !== true) {
|
|
2492
|
-
throw new TypeError(
|
|
2493
|
-
`replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
|
|
2494
|
-
);
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
// src/TinyCloudNode.ts
|
|
2500
2481
|
import {
|
|
2501
2482
|
TinyCloud,
|
|
2502
2483
|
checkNodeInfo as checkNodeInfo2,
|
|
2503
|
-
|
|
2484
|
+
ProtocolMismatchError,
|
|
2504
2485
|
activateSessionWithHost as activateSessionWithHost2,
|
|
2505
2486
|
authorizationVerdictOf,
|
|
2506
2487
|
httpResponseError,
|
|
@@ -2551,7 +2532,6 @@ import {
|
|
|
2551
2532
|
verifyDidKeyEd25519Signature,
|
|
2552
2533
|
canonicalizeAddress as canonicalizeAddress2,
|
|
2553
2534
|
pkhDid as pkhDid2,
|
|
2554
|
-
principalDid,
|
|
2555
2535
|
resolveTinyCloudHosts as resolveTinyCloudHosts2,
|
|
2556
2536
|
publishLocationRecord,
|
|
2557
2537
|
principalDidEquals as principalDidEquals3,
|
|
@@ -2562,6 +2542,7 @@ import {
|
|
|
2562
2542
|
validateShareDeliveryAuthorizationV3Bytes,
|
|
2563
2543
|
verifyEip191MessageSignature,
|
|
2564
2544
|
signCompactUcanRootAuthorization,
|
|
2545
|
+
markDelegationTerminal,
|
|
2565
2546
|
isStorageFullError,
|
|
2566
2547
|
STORAGE_FULL_MESSAGE,
|
|
2567
2548
|
STORAGE_WRITE_TOO_LARGE_MESSAGE,
|
|
@@ -3388,6 +3369,7 @@ function ownerPermissionsToAbilities(permissions) {
|
|
|
3388
3369
|
}
|
|
3389
3370
|
return { abilities, rawAbilities };
|
|
3390
3371
|
}
|
|
3372
|
+
var TERMINAL_DELEGATION_REQUIRES_SESSION_KEY = "disableSubDelegation requires a session-key delegation";
|
|
3391
3373
|
function base64UrlEncode(bytes) {
|
|
3392
3374
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
3393
3375
|
let output = "";
|
|
@@ -3499,6 +3481,20 @@ function accountIndexWarningDiagnostic(error) {
|
|
|
3499
3481
|
}
|
|
3500
3482
|
return diagnostic;
|
|
3501
3483
|
}
|
|
3484
|
+
function replicaRestoreRefusal(error) {
|
|
3485
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "REPLICA_GATED" && "gate" in error) {
|
|
3486
|
+
switch (error.gate) {
|
|
3487
|
+
case "not_established":
|
|
3488
|
+
case "stamp_stale":
|
|
3489
|
+
case "principal_mismatch":
|
|
3490
|
+
case "purge_pending":
|
|
3491
|
+
case "logout_pending":
|
|
3492
|
+
case "gate_unavailable":
|
|
3493
|
+
return error.gate;
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
return "gate_unavailable";
|
|
3497
|
+
}
|
|
3502
3498
|
var _TinyCloudNode = class _TinyCloudNode {
|
|
3503
3499
|
/**
|
|
3504
3500
|
* Create a new TinyCloudNode instance.
|
|
@@ -3528,6 +3524,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3528
3524
|
this.auth = null;
|
|
3529
3525
|
this.tc = null;
|
|
3530
3526
|
this._chainId = 1;
|
|
3527
|
+
/** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
|
|
3528
|
+
this.establishReplicaStamp = async (session) => {
|
|
3529
|
+
const replication = this.config.replication;
|
|
3530
|
+
if (!replication?.enabled) return void 0;
|
|
3531
|
+
const epoch = this.sessionEpoch;
|
|
3532
|
+
const attempt = this.latestSessionAttempt;
|
|
3533
|
+
try {
|
|
3534
|
+
const { verifiedOwnerConsentAnchor, establishReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
|
|
3535
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
|
|
3536
|
+
const anchor = verifiedOwnerConsentAnchor(session, this.wasmBindings, exactVerifiedRecap);
|
|
3537
|
+
if (anchor === null) {
|
|
3538
|
+
throw Object.assign(new Error("Replica sign-in requires a verified finite owner SIWE and exact-space KV get+sync."), {
|
|
3539
|
+
code: "REPLICA_GATED",
|
|
3540
|
+
gate: "not_established"
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
const stamp = await establishReplicaSessionStamp(
|
|
3544
|
+
replication.storage,
|
|
3545
|
+
pkhDid2(session.address, session.chainId),
|
|
3546
|
+
anchor
|
|
3547
|
+
);
|
|
3548
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
|
|
3549
|
+
this.replicaSessionStamp = stamp;
|
|
3550
|
+
return stamp;
|
|
3551
|
+
} catch (error) {
|
|
3552
|
+
if (epoch === this.sessionEpoch && attempt === this.latestSessionAttempt) {
|
|
3553
|
+
this.replicaRestoreGate = replicaRestoreRefusal(error);
|
|
3554
|
+
}
|
|
3555
|
+
return void 0;
|
|
3556
|
+
}
|
|
3557
|
+
};
|
|
3531
3558
|
this._baseVaults = /* @__PURE__ */ new Map();
|
|
3532
3559
|
this._baseSecrets = /* @__PURE__ */ new Map();
|
|
3533
3560
|
this._secrets = /* @__PURE__ */ new Map();
|
|
@@ -3652,7 +3679,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3652
3679
|
});
|
|
3653
3680
|
return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
|
|
3654
3681
|
};
|
|
3655
|
-
|
|
3682
|
+
if (config.replication?.enabled) getNodeReplicationLoaders().validateConfig(config.replication);
|
|
3656
3683
|
this.explicitHost = config.host;
|
|
3657
3684
|
this.config = {
|
|
3658
3685
|
...config,
|
|
@@ -3667,6 +3694,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3667
3694
|
this.replicationRuntime = runtime;
|
|
3668
3695
|
this.replicationControl = {
|
|
3669
3696
|
status: async () => {
|
|
3697
|
+
const gate = this.replicaRestoreGate;
|
|
3698
|
+
if (gate) return this.config.replication.prefixes.map((prefix) => ({
|
|
3699
|
+
prefix,
|
|
3700
|
+
state: "gated",
|
|
3701
|
+
gate,
|
|
3702
|
+
errorCode: "REPLICA_GATED",
|
|
3703
|
+
pending: { inFlight: 0, committed: 0, ambiguous: 0 },
|
|
3704
|
+
pinned: [],
|
|
3705
|
+
lagMs: null
|
|
3706
|
+
}));
|
|
3670
3707
|
const status = await runtime.then((ready) => ready.control.status());
|
|
3671
3708
|
const scopeError = this.replicationScopeError;
|
|
3672
3709
|
if (!scopeError) return status;
|
|
@@ -3683,7 +3720,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3683
3720
|
lagMs: null
|
|
3684
3721
|
}));
|
|
3685
3722
|
},
|
|
3686
|
-
sync: async (input) =>
|
|
3723
|
+
sync: async (input) => {
|
|
3724
|
+
if (this.replicaRestoreGate) {
|
|
3725
|
+
throw Object.assign(new Error("Replica restore is gated."), {
|
|
3726
|
+
code: "REPLICA_GATED",
|
|
3727
|
+
gate: this.replicaRestoreGate
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3730
|
+
return (await runtime).control.sync(input);
|
|
3731
|
+
},
|
|
3687
3732
|
purge: (input) => {
|
|
3688
3733
|
const instance = this.replicationRuntimeInstance;
|
|
3689
3734
|
return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
|
|
@@ -3899,6 +3944,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3899
3944
|
signer: this.signer,
|
|
3900
3945
|
signStrategy: config.signStrategy ?? { type: "auto-sign" },
|
|
3901
3946
|
wasmBindings: this.wasmBindings,
|
|
3947
|
+
...config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
|
|
3902
3948
|
sessionManager: this.sessionManager,
|
|
3903
3949
|
sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
|
|
3904
3950
|
domain: this.siweDomain,
|
|
@@ -4096,11 +4142,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4096
4142
|
return this.signer === null;
|
|
4097
4143
|
}
|
|
4098
4144
|
/**
|
|
4099
|
-
*
|
|
4100
|
-
* Available after signIn().
|
|
4145
|
+
* Primary space of the signed-in or restored network session, including compact sessions.
|
|
4101
4146
|
*/
|
|
4102
4147
|
get spaceId() {
|
|
4103
|
-
return this.auth?.tinyCloudSession?.spaceId;
|
|
4148
|
+
return this.auth?.tinyCloudSession?.spaceId ?? this._activeServiceSession?.spaceId;
|
|
4104
4149
|
}
|
|
4105
4150
|
/**
|
|
4106
4151
|
* Get the account space ID for this wallet identity.
|
|
@@ -4148,6 +4193,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4148
4193
|
get restorableSession() {
|
|
4149
4194
|
return this.currentTinyCloudSession();
|
|
4150
4195
|
}
|
|
4196
|
+
/** The active, verified replica stamp. Undefined when the replica is refused or the session has ended. */
|
|
4197
|
+
get replicaStamp() {
|
|
4198
|
+
const stamp = this.replicaSessionStamp;
|
|
4199
|
+
if (!stamp || this.replicaRestoreGate !== void 0 || !this.currentTinyCloudSession()) return void 0;
|
|
4200
|
+
return { incarnation: stamp.incarnation, lifecycleEpoch: stamp.lifecycleEpoch };
|
|
4201
|
+
}
|
|
4151
4202
|
/**
|
|
4152
4203
|
* Sign out and end the session on this instance.
|
|
4153
4204
|
*
|
|
@@ -4168,19 +4219,61 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4168
4219
|
}
|
|
4169
4220
|
async endSession() {
|
|
4170
4221
|
const leavingAddress = this.address ?? this._restoredTcSession?.address;
|
|
4222
|
+
const leavingChainId = this._chainId;
|
|
4223
|
+
const leavingPrincipal = this.replicaSessionPrincipal ?? (leavingAddress ? pkhDid2(leavingAddress, leavingChainId) : void 0);
|
|
4171
4224
|
const leavingAuth = this.auth;
|
|
4225
|
+
const leavingContext = this._serviceContext;
|
|
4226
|
+
let denied;
|
|
4172
4227
|
try {
|
|
4173
4228
|
this.teardownSession();
|
|
4174
4229
|
} finally {
|
|
4230
|
+
const denial = leavingPrincipal ? this.denyReplicaGate({ reason: "sign_out", principal: leavingPrincipal }, leavingContext) : Promise.resolve(void 0);
|
|
4175
4231
|
try {
|
|
4176
|
-
await
|
|
4177
|
-
if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
|
|
4232
|
+
denied = await denial;
|
|
4178
4233
|
} finally {
|
|
4179
|
-
|
|
4180
|
-
await
|
|
4234
|
+
try {
|
|
4235
|
+
await this.accountRegistryTail;
|
|
4236
|
+
await this.quiesceReplicaRuntime();
|
|
4237
|
+
} finally {
|
|
4238
|
+
if (leavingAddress) {
|
|
4239
|
+
await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
|
|
4240
|
+
}
|
|
4181
4241
|
}
|
|
4182
4242
|
}
|
|
4183
4243
|
}
|
|
4244
|
+
if (denied?.status === "unguarded") {
|
|
4245
|
+
throw new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`);
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Step 1 of replica sign-out. Never rejects: failures come back as `unguarded` so the session
|
|
4250
|
+
* is still cleared. Returns `undefined` when replication is off.
|
|
4251
|
+
*/
|
|
4252
|
+
async denyReplicaGate(input, context = this._serviceContext) {
|
|
4253
|
+
const replication = this.config.replication;
|
|
4254
|
+
if (!replication?.enabled) return void 0;
|
|
4255
|
+
try {
|
|
4256
|
+
const lifecycle = replication.storage.lifecycle;
|
|
4257
|
+
if (lifecycle?.kind !== (replication.storage.kind === "indexeddb" ? "indexeddb" : "file")) {
|
|
4258
|
+
return { status: "unguarded", code: "gate_unavailable" };
|
|
4259
|
+
}
|
|
4260
|
+
const result = await lifecycle.deny(input);
|
|
4261
|
+
if (result.status === "denied" || result.status === "deleted" || result.status === "marker") {
|
|
4262
|
+
try {
|
|
4263
|
+
(await getNodeReplicationLoaders().authority()).reportReplicaDenial(
|
|
4264
|
+
result,
|
|
4265
|
+
input.reason,
|
|
4266
|
+
replication.onEvent,
|
|
4267
|
+
context
|
|
4268
|
+
);
|
|
4269
|
+
} catch {
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
return result;
|
|
4273
|
+
} catch (error) {
|
|
4274
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "gate_unavailable";
|
|
4275
|
+
return { status: "unguarded", code };
|
|
4276
|
+
}
|
|
4184
4277
|
}
|
|
4185
4278
|
/**
|
|
4186
4279
|
* Synchronously drop every piece of in-memory session authority and
|
|
@@ -4199,15 +4292,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4199
4292
|
this._activeServiceSession = void 0;
|
|
4200
4293
|
oldGraph.retire();
|
|
4201
4294
|
oldCore?.retireServices();
|
|
4202
|
-
oldAuth?.resetSession(
|
|
4203
|
-
this.sessionManager,
|
|
4204
|
-
this.sessionKeyId,
|
|
4205
|
-
this.explicitHost ? [this.explicitHost] : void 0
|
|
4206
|
-
);
|
|
4207
4295
|
this.config.host = this.explicitHost ?? DEFAULT_HOST;
|
|
4208
4296
|
this._chainId = 1;
|
|
4209
4297
|
this._restoredNodeFeatures = void 0;
|
|
4210
|
-
this._activeServiceSession = void 0;
|
|
4211
4298
|
this._serviceContext = void 0;
|
|
4212
4299
|
this._kv = void 0;
|
|
4213
4300
|
this._sql = void 0;
|
|
@@ -4232,6 +4319,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4232
4319
|
this._bootstrapStatus = { skipped: false };
|
|
4233
4320
|
this.replicationDelegateSession = void 0;
|
|
4234
4321
|
this.replicationScopeError = void 0;
|
|
4322
|
+
this.replicaSessionPrincipal = void 0;
|
|
4323
|
+
this.replicaSessionStamp = void 0;
|
|
4324
|
+
this.replicaRestoreGate = void 0;
|
|
4235
4325
|
const sessionKey = createDefaultSessionKey(this.wasmBindings);
|
|
4236
4326
|
this.sessionManager = sessionKey.manager;
|
|
4237
4327
|
this.sessionKeyId = sessionKey.keyId;
|
|
@@ -4241,6 +4331,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4241
4331
|
sessionKey.keyId,
|
|
4242
4332
|
this.explicitHost ? [this.explicitHost] : void 0
|
|
4243
4333
|
);
|
|
4334
|
+
this.replicaSessionPrincipal = void 0;
|
|
4335
|
+
this.replicaSessionStamp = void 0;
|
|
4336
|
+
this.replicaRestoreGate = void 0;
|
|
4244
4337
|
this._serviceGraph = this.createServiceGraphLifetime();
|
|
4245
4338
|
this.auth = oldAuth;
|
|
4246
4339
|
this.tc = this.auth ? new TinyCloud(this.auth, {
|
|
@@ -4270,11 +4363,25 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4270
4363
|
"Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
|
|
4271
4364
|
);
|
|
4272
4365
|
}
|
|
4366
|
+
await this.signInWith(() => this.tc.signIn(options));
|
|
4367
|
+
}
|
|
4368
|
+
/**
|
|
4369
|
+
* Fresh OpenKey sign-in. The callback receives the SDK's original SIWE and
|
|
4370
|
+
* session JWK before approval; the signed result is verified against those
|
|
4371
|
+
* bytes before the network session or replica stamp is committed.
|
|
4372
|
+
*/
|
|
4373
|
+
async signInWithOpenKey(authorizeFn, options) {
|
|
4374
|
+
if (!this.signer || !this.tc || !this.auth) {
|
|
4375
|
+
throw new Error("Cannot signInWithOpenKey() in session-only mode. Provide a signer in config.");
|
|
4376
|
+
}
|
|
4377
|
+
await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
|
|
4378
|
+
}
|
|
4379
|
+
async signInWith(performSignIn) {
|
|
4273
4380
|
const epoch = this.sessionEpoch;
|
|
4274
4381
|
const attempt = ++this.latestSessionAttempt;
|
|
4275
4382
|
await this.signOutInFlight;
|
|
4276
4383
|
try {
|
|
4277
|
-
await this.establishSignIn(epoch,
|
|
4384
|
+
await this.establishSignIn(epoch, performSignIn);
|
|
4278
4385
|
} catch (error) {
|
|
4279
4386
|
if (epoch !== this.sessionEpoch && attempt === this.latestSessionAttempt) {
|
|
4280
4387
|
await this.endSession();
|
|
@@ -4282,7 +4389,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4282
4389
|
throw error;
|
|
4283
4390
|
}
|
|
4284
4391
|
}
|
|
4285
|
-
async establishSignIn(epoch,
|
|
4392
|
+
async establishSignIn(epoch, performSignIn) {
|
|
4286
4393
|
if (!this.signer || !this.tc) {
|
|
4287
4394
|
throw new Error("Not signed in. Call signIn() first.");
|
|
4288
4395
|
}
|
|
@@ -4292,6 +4399,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4292
4399
|
const address = canonicalizeAddress2(await this.signer.getAddress());
|
|
4293
4400
|
const chainId = await this.signer.getChainId();
|
|
4294
4401
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4402
|
+
const nextPrincipal = pkhDid2(address, chainId);
|
|
4403
|
+
const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? pkhDid2(this.address, this._chainId) : void 0);
|
|
4404
|
+
if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
|
|
4405
|
+
const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
|
|
4406
|
+
this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
|
|
4407
|
+
}
|
|
4295
4408
|
if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
|
|
4296
4409
|
this._address = address;
|
|
4297
4410
|
this._chainId = chainId;
|
|
@@ -4309,7 +4422,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4309
4422
|
this.runtimePermissionGrants = [];
|
|
4310
4423
|
this.confirmedHostedSpaceIds.clear();
|
|
4311
4424
|
this.hostedOwnedSpaceIds.clear();
|
|
4312
|
-
|
|
4425
|
+
const signInAttempt = this.latestSessionAttempt;
|
|
4426
|
+
const previousReplicaGate = this.replicaRestoreGate;
|
|
4427
|
+
const previousReplicaStamp = this.replicaSessionStamp;
|
|
4428
|
+
this.replicaRestoreGate = void 0;
|
|
4429
|
+
this.replicaSessionStamp = void 0;
|
|
4430
|
+
try {
|
|
4431
|
+
await performSignIn();
|
|
4432
|
+
} catch (error) {
|
|
4433
|
+
if (epoch === this.sessionEpoch && signInAttempt === this.latestSessionAttempt) {
|
|
4434
|
+
this.replicaRestoreGate = previousReplicaGate;
|
|
4435
|
+
this.replicaSessionStamp = previousReplicaStamp;
|
|
4436
|
+
}
|
|
4437
|
+
throw error;
|
|
4438
|
+
}
|
|
4313
4439
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4314
4440
|
this.cancelOwnedSpaceHosts();
|
|
4315
4441
|
this.syncResolvedHostFromAuth();
|
|
@@ -4317,8 +4443,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4317
4443
|
this.hostedOwnedSpaceIds.add(spaceId);
|
|
4318
4444
|
}
|
|
4319
4445
|
this.replicationDelegateSession = void 0;
|
|
4446
|
+
this.replicaSessionPrincipal = pkhDid2(address, chainId);
|
|
4320
4447
|
this.replicationScopeError = void 0;
|
|
4321
|
-
|
|
4448
|
+
await this.quiesceReplicaRuntime();
|
|
4322
4449
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4323
4450
|
const oldGraph = this._serviceGraph;
|
|
4324
4451
|
this._serviceGraph = this.createServiceGraphLifetime();
|
|
@@ -4905,6 +5032,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4905
5032
|
if (this.ownStorageFull) {
|
|
4906
5033
|
return;
|
|
4907
5034
|
}
|
|
5035
|
+
if (!this.currentSessionCoversAccountRegistry()) {
|
|
5036
|
+
return;
|
|
5037
|
+
}
|
|
4908
5038
|
const controller = new AbortController();
|
|
4909
5039
|
const deadline = setTimeout(
|
|
4910
5040
|
() => controller.abort(new DOMException("Account registry deadline exceeded", "TimeoutError")),
|
|
@@ -5019,6 +5149,27 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5019
5149
|
(operation) => operation.service === "space" && this.actionContains(operation.action, "tinycloud.space/list")
|
|
5020
5150
|
);
|
|
5021
5151
|
}
|
|
5152
|
+
/**
|
|
5153
|
+
* Whether the current primary session's recap covers the account index read
|
|
5154
|
+
* (`tinycloud.sql/read` on the owned account space's `account` database),
|
|
5155
|
+
* the first call of every registry sync. As in
|
|
5156
|
+
* {@link currentSessionCanListSpaces}, a session with no parseable recap
|
|
5157
|
+
* keeps today's behavior.
|
|
5158
|
+
*/
|
|
5159
|
+
currentSessionCoversAccountRegistry() {
|
|
5160
|
+
const session = this.currentTinyCloudSession();
|
|
5161
|
+
const operations = session ? this.recapOperationsFromSession(session) : [];
|
|
5162
|
+
if (operations.length === 0) {
|
|
5163
|
+
return true;
|
|
5164
|
+
}
|
|
5165
|
+
const indexRead = {
|
|
5166
|
+
spaceId: this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2),
|
|
5167
|
+
service: "sql",
|
|
5168
|
+
path: "account",
|
|
5169
|
+
action: "tinycloud.sql/read"
|
|
5170
|
+
};
|
|
5171
|
+
return operations.some((granted) => this.operationCovers(granted, indexRead));
|
|
5172
|
+
}
|
|
5022
5173
|
async withAccountRegistryRetry(task, signal) {
|
|
5023
5174
|
const delays = [250, 1e3, 3e3];
|
|
5024
5175
|
const stopped = (detail) => {
|
|
@@ -5485,11 +5636,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5485
5636
|
);
|
|
5486
5637
|
const stagedHost = resolvedHost ?? this.config.host;
|
|
5487
5638
|
let restoredNodeFeatures;
|
|
5639
|
+
let replicaProtocolMismatch = false;
|
|
5488
5640
|
if (this.config.replication?.enabled) {
|
|
5489
5641
|
try {
|
|
5490
5642
|
restoredNodeFeatures = (await checkNodeInfo2(stagedHost, this.wasmBindings.protocolVersion())).features;
|
|
5491
5643
|
} catch (error) {
|
|
5492
|
-
|
|
5644
|
+
replicaProtocolMismatch = error instanceof ProtocolMismatchError;
|
|
5493
5645
|
}
|
|
5494
5646
|
}
|
|
5495
5647
|
const stagedAddress = restoredAddress;
|
|
@@ -5497,28 +5649,55 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5497
5649
|
let replicationPrincipal;
|
|
5498
5650
|
let compactReplicationSession;
|
|
5499
5651
|
let replicationScopeError;
|
|
5652
|
+
let replicationScopeUnavailable = false;
|
|
5500
5653
|
if (this.config.replication?.enabled) {
|
|
5501
5654
|
if (stagedAddress !== void 0) {
|
|
5502
5655
|
replicationPrincipal = pkhDid2(stagedAddress, stagedChainId);
|
|
5503
5656
|
} else {
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
delegationCid: sessionData.delegationCid,
|
|
5508
|
-
verificationMethod: restoredVerificationMethod,
|
|
5509
|
-
persistedSpace: sessionData.spaceId
|
|
5510
|
-
});
|
|
5511
|
-
if (!signedScope) {
|
|
5512
|
-
replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
|
|
5513
|
-
} else {
|
|
5514
|
-
replicationPrincipal = signedScope.principal;
|
|
5515
|
-
compactReplicationSession = {
|
|
5657
|
+
try {
|
|
5658
|
+
const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
|
|
5659
|
+
const signedScope = replicationScopeFromSignedSession({
|
|
5516
5660
|
delegationHeader: sessionData.delegationHeader,
|
|
5517
5661
|
delegationCid: sessionData.delegationCid,
|
|
5518
|
-
spaceId: signedScope.space,
|
|
5519
5662
|
verificationMethod: restoredVerificationMethod,
|
|
5520
|
-
|
|
5521
|
-
};
|
|
5663
|
+
persistedSpace: sessionData.spaceId
|
|
5664
|
+
});
|
|
5665
|
+
if (!signedScope) {
|
|
5666
|
+
replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
|
|
5667
|
+
} else {
|
|
5668
|
+
replicationPrincipal = signedScope.principal;
|
|
5669
|
+
compactReplicationSession = {
|
|
5670
|
+
delegationHeader: sessionData.delegationHeader,
|
|
5671
|
+
delegationCid: sessionData.delegationCid,
|
|
5672
|
+
spaceId: signedScope.space,
|
|
5673
|
+
verificationMethod: restoredVerificationMethod,
|
|
5674
|
+
jwk: stagedJwk
|
|
5675
|
+
};
|
|
5676
|
+
}
|
|
5677
|
+
} catch {
|
|
5678
|
+
replicationScopeUnavailable = true;
|
|
5679
|
+
replicationScopeError = "Replica scope could not be verified for this network session.";
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
let replicaRestoreGate;
|
|
5684
|
+
let restoredReplicaStamp;
|
|
5685
|
+
const replication = this.config.replication;
|
|
5686
|
+
if (replication?.enabled) {
|
|
5687
|
+
if (replicaProtocolMismatch) {
|
|
5688
|
+
replicaRestoreGate = "gate_unavailable";
|
|
5689
|
+
} else if (replicationPrincipal === void 0) {
|
|
5690
|
+
replicaRestoreGate = replicationScopeUnavailable ? "gate_unavailable" : "principal_mismatch";
|
|
5691
|
+
} else {
|
|
5692
|
+
try {
|
|
5693
|
+
const { restoreReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
|
|
5694
|
+
restoredReplicaStamp = await restoreReplicaSessionStamp(
|
|
5695
|
+
replication.storage,
|
|
5696
|
+
replicationPrincipal,
|
|
5697
|
+
sessionData.replicaStamp ?? void 0
|
|
5698
|
+
);
|
|
5699
|
+
} catch (error) {
|
|
5700
|
+
replicaRestoreGate = replicaRestoreRefusal(error);
|
|
5522
5701
|
}
|
|
5523
5702
|
}
|
|
5524
5703
|
}
|
|
@@ -5611,7 +5790,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5611
5790
|
}
|
|
5612
5791
|
this.replicationDelegateSession = compactReplicationSession;
|
|
5613
5792
|
this.replicationScopeError = replicationScopeError;
|
|
5614
|
-
|
|
5793
|
+
this.replicaRestoreGate = replicaRestoreGate;
|
|
5794
|
+
this.replicaSessionPrincipal = replicationPrincipal;
|
|
5795
|
+
this.replicaSessionStamp = restoredReplicaStamp;
|
|
5796
|
+
await this.quiesceReplicaRuntime();
|
|
5615
5797
|
if (epoch !== this.sessionEpoch) {
|
|
5616
5798
|
oldGraph.retire();
|
|
5617
5799
|
oldCore?.retireServices?.();
|
|
@@ -5626,7 +5808,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5626
5808
|
detail: replicationScopeError
|
|
5627
5809
|
});
|
|
5628
5810
|
}
|
|
5629
|
-
await this.
|
|
5811
|
+
await this.bindReplicaOrGate(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
|
|
5630
5812
|
oldCore?.retireServices?.();
|
|
5631
5813
|
if (epoch !== this.sessionEpoch) {
|
|
5632
5814
|
if (attempt === this.latestSessionAttempt) await this.endSession();
|
|
@@ -5759,6 +5941,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5759
5941
|
session: input.serviceSession,
|
|
5760
5942
|
invoke: graph.invoke,
|
|
5761
5943
|
invokeAny: graph.invokeAny,
|
|
5944
|
+
signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
|
|
5762
5945
|
fetch: graph.fetch
|
|
5763
5946
|
});
|
|
5764
5947
|
const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
|
|
@@ -6102,6 +6285,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6102
6285
|
signer: this.signer,
|
|
6103
6286
|
signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
|
|
6104
6287
|
wasmBindings: this.wasmBindings,
|
|
6288
|
+
...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
|
|
6105
6289
|
sessionManager: this.sessionManager,
|
|
6106
6290
|
sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
|
|
6107
6291
|
domain: this.siweDomain,
|
|
@@ -6160,6 +6344,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6160
6344
|
signer: this.signer,
|
|
6161
6345
|
signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
|
|
6162
6346
|
wasmBindings: this.wasmBindings,
|
|
6347
|
+
...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
|
|
6163
6348
|
sessionManager: this.sessionManager,
|
|
6164
6349
|
sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
|
|
6165
6350
|
domain: this.siweDomain,
|
|
@@ -6240,7 +6425,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6240
6425
|
jwk: session.jwk
|
|
6241
6426
|
};
|
|
6242
6427
|
this._serviceContext.setSession(serviceSession);
|
|
6243
|
-
await this.
|
|
6428
|
+
await this.bindReplicaOrGate(this._serviceContext, serviceSession, this._kv);
|
|
6244
6429
|
this.tc.serviceContext.setSession(serviceSession);
|
|
6245
6430
|
this._vault = this.createVaultService(session.spaceId, this._kv);
|
|
6246
6431
|
this._vault.initialize(this._serviceContext);
|
|
@@ -6634,6 +6819,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6634
6819
|
session: serviceSession,
|
|
6635
6820
|
invoke: graph.invoke,
|
|
6636
6821
|
invokeAny: graph.invokeAny,
|
|
6822
|
+
signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
|
|
6637
6823
|
fetch: graph.fetch
|
|
6638
6824
|
});
|
|
6639
6825
|
this._spaceService = new SpaceService({
|
|
@@ -6769,11 +6955,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6769
6955
|
* Both SharingService (single-entry) and
|
|
6770
6956
|
* {@link TinyCloudNode.delegateTo} (multi-entry) drive this through
|
|
6771
6957
|
* the same code path so there's exactly one place that touches the
|
|
6772
|
-
* WASM boundary.
|
|
6958
|
+
* WASM boundary. `terminal` adds the node's terminal marker, which the
|
|
6959
|
+
* WASM builder cannot emit.
|
|
6773
6960
|
*
|
|
6774
6961
|
* @internal
|
|
6775
6962
|
*/
|
|
6776
|
-
createDelegationWrapper(params) {
|
|
6963
|
+
createDelegationWrapper(params, terminal = false) {
|
|
6777
6964
|
const wasmSession = {
|
|
6778
6965
|
delegationHeader: params.session.delegationHeader,
|
|
6779
6966
|
delegationCid: params.session.delegationCid,
|
|
@@ -6793,8 +6980,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6793
6980
|
params.notBeforeSecs
|
|
6794
6981
|
);
|
|
6795
6982
|
return {
|
|
6796
|
-
delegation: result.delegation,
|
|
6797
|
-
cid: result.cid,
|
|
6983
|
+
...terminal ? markDelegationTerminal(result.delegation, params.session.jwk) : { delegation: result.delegation, cid: result.cid },
|
|
6798
6984
|
// Rust serde `rename_all = "camelCase"` emits `delegateDid`
|
|
6799
6985
|
// (lowercase d); the TypeScript interface uses `delegateDID`
|
|
6800
6986
|
// (historical, matches Delegation.delegateDID). Normalize here.
|
|
@@ -6936,7 +7122,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6936
7122
|
};
|
|
6937
7123
|
const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalizeEncryptionJson(body)))));
|
|
6938
7124
|
const request = { ...body, requestBodyDigest };
|
|
6939
|
-
const authorization = authorizationHeader(this.
|
|
7125
|
+
const authorization = authorizationHeader(this.wasmBindings.invoke(serviceSession, "kv", input.resourcePath, "tinycloud.kv/get"));
|
|
6940
7126
|
const response = await fetch(`${this.config.host}/policy/v3/deliveries/authorize`, {
|
|
6941
7127
|
method: "POST",
|
|
6942
7128
|
headers: { accept: "application/json", "content-type": "application/json", authorization },
|
|
@@ -7926,7 +8112,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7926
8112
|
return this.delegationManager.create(params);
|
|
7927
8113
|
}
|
|
7928
8114
|
/**
|
|
7929
|
-
* Revoke a delegation
|
|
8115
|
+
* Revoke a delegation with the owner wallet when available. Session-only
|
|
8116
|
+
* clients continue to use their delegated revocation authority.
|
|
7930
8117
|
*
|
|
7931
8118
|
* @param cid - The CID of the delegation to revoke
|
|
7932
8119
|
* @returns Result indicating success or failure
|
|
@@ -8042,6 +8229,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8042
8229
|
effectiveExpiration = sessionExpiry;
|
|
8043
8230
|
}
|
|
8044
8231
|
if (options?.forceWalletSign) {
|
|
8232
|
+
if (options.disableSubDelegation) {
|
|
8233
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
8234
|
+
}
|
|
8045
8235
|
if (expandedEntries.length > 1) {
|
|
8046
8236
|
throw new Error(
|
|
8047
8237
|
"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."
|
|
@@ -8077,7 +8267,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8077
8267
|
plan.effectiveExpiration,
|
|
8078
8268
|
plan.grant,
|
|
8079
8269
|
plan.operations,
|
|
8080
|
-
onPrepared
|
|
8270
|
+
onPrepared,
|
|
8271
|
+
options?.disableSubDelegation === true
|
|
8081
8272
|
);
|
|
8082
8273
|
return { delegation: delegation2, prompted: false };
|
|
8083
8274
|
}
|
|
@@ -8086,7 +8277,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8086
8277
|
expandedEntries,
|
|
8087
8278
|
effectiveExpiration,
|
|
8088
8279
|
session,
|
|
8089
|
-
onPrepared
|
|
8280
|
+
onPrepared,
|
|
8281
|
+
options?.disableSubDelegation === true
|
|
8090
8282
|
);
|
|
8091
8283
|
return { delegation, prompted: false };
|
|
8092
8284
|
}
|
|
@@ -8201,34 +8393,67 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8201
8393
|
return auth.replicationSignInEntries();
|
|
8202
8394
|
}
|
|
8203
8395
|
async replicationAuthority() {
|
|
8204
|
-
const { createReplicationAuthority } = await getNodeReplicationLoaders().authority();
|
|
8396
|
+
const { createReplicationAuthority, mintNodeReplicationGrant, verifiedOwnerConsentAnchor } = await getNodeReplicationLoaders().authority();
|
|
8205
8397
|
return createReplicationAuthority({
|
|
8206
8398
|
replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
|
|
8399
|
+
verifiedConsentAnchor: (session) => {
|
|
8400
|
+
const current = this.currentTinyCloudSession();
|
|
8401
|
+
return session === current && current !== void 0 ? verifiedOwnerConsentAnchor(current, this.wasmBindings, exactVerifiedRecap) : null;
|
|
8402
|
+
},
|
|
8207
8403
|
siweExpiration: (siwe) => extractSiweExpiration(siwe),
|
|
8208
8404
|
planDelegation: (entries, options) => this.planDelegation(entries, options),
|
|
8209
|
-
mintDelegation:
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8405
|
+
mintDelegation: (deviceDid, entries, options) => mintNodeReplicationGrant(
|
|
8406
|
+
deviceDid,
|
|
8407
|
+
entries,
|
|
8408
|
+
options,
|
|
8409
|
+
options?.renewalOnly ? this.currentTinyCloudSession() : void 0,
|
|
8410
|
+
this
|
|
8411
|
+
)
|
|
8216
8412
|
});
|
|
8217
8413
|
}
|
|
8218
|
-
async
|
|
8414
|
+
async quiesceReplicaRuntime() {
|
|
8219
8415
|
if (!this.replicationRuntime) return;
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8416
|
+
try {
|
|
8417
|
+
await (await this.replicationRuntime).unbind();
|
|
8418
|
+
} catch {
|
|
8419
|
+
this.replicaRestoreGate = "gate_unavailable";
|
|
8420
|
+
}
|
|
8421
|
+
}
|
|
8422
|
+
async bindReplicaOrGate(context, session, kv, principal) {
|
|
8423
|
+
if (!this.replicationRuntime) return;
|
|
8424
|
+
const gate = this.replicaRestoreGate;
|
|
8425
|
+
const epoch = this.sessionEpoch;
|
|
8426
|
+
const attempt = this.latestSessionAttempt;
|
|
8427
|
+
try {
|
|
8428
|
+
const runtime = await this.replicationRuntime;
|
|
8429
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context || gate !== this.replicaRestoreGate) return;
|
|
8430
|
+
if (gate) {
|
|
8431
|
+
await runtime.bindGatedNetworkOnly({
|
|
8432
|
+
context,
|
|
8433
|
+
session,
|
|
8434
|
+
gate,
|
|
8435
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
8436
|
+
}, getNodeReplicationLoaders().gatedNetwork);
|
|
8437
|
+
return;
|
|
8438
|
+
}
|
|
8439
|
+
const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : pkhDid2(this._address, this._chainId));
|
|
8440
|
+
if (identityPrincipal === void 0) return;
|
|
8441
|
+
const authority = await this.replicationAuthority();
|
|
8442
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
|
|
8443
|
+
await runtime.bind({
|
|
8444
|
+
context,
|
|
8445
|
+
session,
|
|
8446
|
+
principal: identityPrincipal,
|
|
8447
|
+
stamp: this.replicaSessionStamp,
|
|
8448
|
+
authority,
|
|
8449
|
+
nodeFeatures: this.replicationNodeFeatures,
|
|
8450
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
8451
|
+
});
|
|
8452
|
+
} catch {
|
|
8453
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
|
|
8454
|
+
this.replicaRestoreGate = "gate_unavailable";
|
|
8455
|
+
await this.quiesceReplicaRuntime();
|
|
8456
|
+
}
|
|
8232
8457
|
}
|
|
8233
8458
|
/** Stable control facade for this node's optional replication runtime. */
|
|
8234
8459
|
get replication() {
|
|
@@ -8284,7 +8509,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8284
8509
|
*
|
|
8285
8510
|
* @internal
|
|
8286
8511
|
*/
|
|
8287
|
-
async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared) {
|
|
8512
|
+
async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared, disableSubDelegation = false) {
|
|
8288
8513
|
if (entries.length === 0) {
|
|
8289
8514
|
throw new Error(
|
|
8290
8515
|
"createDelegationViaWasmPath requires a non-empty entries array"
|
|
@@ -8344,7 +8569,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8344
8569
|
spaceId,
|
|
8345
8570
|
abilities,
|
|
8346
8571
|
expirationSecs
|
|
8347
|
-
});
|
|
8572
|
+
}, disableSubDelegation);
|
|
8348
8573
|
const primary = result.resources[0];
|
|
8349
8574
|
const delegationHeader = { Authorization: result.delegation };
|
|
8350
8575
|
const delegation = {
|
|
@@ -8354,7 +8579,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8354
8579
|
path: primary.path,
|
|
8355
8580
|
actions: primary.actions,
|
|
8356
8581
|
resources: result.resources,
|
|
8357
|
-
disableSubDelegation
|
|
8582
|
+
disableSubDelegation,
|
|
8358
8583
|
expiry: result.expiry,
|
|
8359
8584
|
delegateDID: did,
|
|
8360
8585
|
ownerAddress: session.address,
|
|
@@ -8376,7 +8601,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8376
8601
|
}
|
|
8377
8602
|
return delegation;
|
|
8378
8603
|
}
|
|
8379
|
-
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared) {
|
|
8604
|
+
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared, disableSubDelegation = false) {
|
|
8380
8605
|
this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
|
|
8381
8606
|
this.assertDelegationCaveatsPreservable(entries);
|
|
8382
8607
|
const result = this.createDelegationWrapper({
|
|
@@ -8385,7 +8610,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8385
8610
|
spaceId: grant.session.spaceId,
|
|
8386
8611
|
abilities: this.permissionsToAbilities(entries),
|
|
8387
8612
|
expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
|
|
8388
|
-
});
|
|
8613
|
+
}, disableSubDelegation);
|
|
8389
8614
|
const primary = result.resources[0];
|
|
8390
8615
|
const delegationHeader = { Authorization: result.delegation };
|
|
8391
8616
|
const targetHost = grant.delegation.host ?? this.config.host;
|
|
@@ -8396,7 +8621,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8396
8621
|
path: primary.path,
|
|
8397
8622
|
actions: primary.actions,
|
|
8398
8623
|
resources: result.resources,
|
|
8399
|
-
disableSubDelegation
|
|
8624
|
+
disableSubDelegation,
|
|
8400
8625
|
expiry: result.expiry,
|
|
8401
8626
|
delegateDID: did,
|
|
8402
8627
|
ownerAddress: grant.delegation.ownerAddress,
|
|
@@ -8980,11 +9205,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8980
9205
|
params.spaceIdOverride
|
|
8981
9206
|
);
|
|
8982
9207
|
try {
|
|
8983
|
-
const result = await this.delegateTo(
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
);
|
|
9208
|
+
const result = await this.delegateTo(resolvedDelegateDID, entries, {
|
|
9209
|
+
...params.expiryMs !== void 0 ? { expiry: params.expiryMs } : {},
|
|
9210
|
+
disableSubDelegation: params.disableSubDelegation
|
|
9211
|
+
});
|
|
8988
9212
|
return result.delegation;
|
|
8989
9213
|
} catch (err2) {
|
|
8990
9214
|
if (err2 instanceof PermissionNotInManifestError) {
|
|
@@ -9005,6 +9229,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9005
9229
|
* @internal
|
|
9006
9230
|
*/
|
|
9007
9231
|
async createDelegationWalletPath(params) {
|
|
9232
|
+
if (params.disableSubDelegation) {
|
|
9233
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
9234
|
+
}
|
|
9008
9235
|
if (!this.signer) {
|
|
9009
9236
|
throw new Error("Cannot createDelegation() in session-only mode. Requires wallet mode.");
|
|
9010
9237
|
}
|
|
@@ -9060,7 +9287,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9060
9287
|
spaceId: params.spaceIdOverride ?? session.spaceId,
|
|
9061
9288
|
path: params.path,
|
|
9062
9289
|
actions: params.actions,
|
|
9063
|
-
disableSubDelegation:
|
|
9290
|
+
disableSubDelegation: false,
|
|
9064
9291
|
expiry: expirationTime,
|
|
9065
9292
|
delegateDID: params.delegateDID,
|
|
9066
9293
|
ownerAddress: session.address,
|
|
@@ -9103,7 +9330,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9103
9330
|
spaceId: publicSpaceId,
|
|
9104
9331
|
path: "",
|
|
9105
9332
|
actions: [KV3.GET, KV3.PUT, KV3.METADATA],
|
|
9106
|
-
disableSubDelegation:
|
|
9333
|
+
disableSubDelegation: false,
|
|
9107
9334
|
expiry: expirationTime,
|
|
9108
9335
|
delegateDID: params.delegateDID,
|
|
9109
9336
|
ownerAddress: session.address,
|
|
@@ -9270,6 +9497,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9270
9497
|
if (parentDelegation.disableSubDelegation) {
|
|
9271
9498
|
throw new Error("Parent delegation does not allow sub-delegation");
|
|
9272
9499
|
}
|
|
9500
|
+
if (params.disableSubDelegation) {
|
|
9501
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
9502
|
+
}
|
|
9273
9503
|
const parentResources = parentDelegation.resources ?? [{
|
|
9274
9504
|
service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
|
|
9275
9505
|
space: parentDelegation.spaceId,
|
|
@@ -9342,7 +9572,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9342
9572
|
spaceId: parentDelegation.spaceId,
|
|
9343
9573
|
path: requestedResources[0].path,
|
|
9344
9574
|
actions: requestedResources[0].actions,
|
|
9345
|
-
disableSubDelegation:
|
|
9575
|
+
disableSubDelegation: false,
|
|
9346
9576
|
expiry: actualExpiry,
|
|
9347
9577
|
delegateDID: params.delegateDID,
|
|
9348
9578
|
ownerAddress: parentDelegation.ownerAddress,
|
|
@@ -9436,8 +9666,8 @@ import {
|
|
|
9436
9666
|
Space
|
|
9437
9667
|
} from "@tinycloud/sdk-core";
|
|
9438
9668
|
import {
|
|
9439
|
-
ProtocolMismatchError,
|
|
9440
|
-
VersionCheckError
|
|
9669
|
+
ProtocolMismatchError as ProtocolMismatchError2,
|
|
9670
|
+
VersionCheckError,
|
|
9441
9671
|
UnsupportedFeatureError as UnsupportedFeatureError2,
|
|
9442
9672
|
checkNodeInfo as checkNodeInfo3
|
|
9443
9673
|
} from "@tinycloud/sdk-core";
|
|
@@ -9470,7 +9700,7 @@ export {
|
|
|
9470
9700
|
NodeUserAuthorization,
|
|
9471
9701
|
PermissionNotInManifestError2 as PermissionNotInManifestError,
|
|
9472
9702
|
PrefixedKVService,
|
|
9473
|
-
ProtocolMismatchError,
|
|
9703
|
+
ProtocolMismatchError2 as ProtocolMismatchError,
|
|
9474
9704
|
SECRET_NAME_RE,
|
|
9475
9705
|
SQLAction,
|
|
9476
9706
|
SQLService3 as SQLService,
|
|
@@ -9490,7 +9720,7 @@ export {
|
|
|
9490
9720
|
VAULT_PERMISSION_SERVICE,
|
|
9491
9721
|
VaultHeaders,
|
|
9492
9722
|
VaultPublicSpaceKVActions,
|
|
9493
|
-
|
|
9723
|
+
VersionCheckError,
|
|
9494
9724
|
WasmKeyProvider,
|
|
9495
9725
|
activateCompactRuntimeDelegation,
|
|
9496
9726
|
addressStorageKey,
|
|
@@ -9519,6 +9749,7 @@ export {
|
|
|
9519
9749
|
expandActionShortNames,
|
|
9520
9750
|
expandPermissionEntries2 as expandPermissionEntries,
|
|
9521
9751
|
expandPermissionEntry,
|
|
9752
|
+
getNodeReplicationLoaders,
|
|
9522
9753
|
getTinyCloudDebugLogs,
|
|
9523
9754
|
installTinyCloudDebugGlobals,
|
|
9524
9755
|
isCapabilitySubset3 as isCapabilitySubset,
|
|
@@ -9532,7 +9763,7 @@ export {
|
|
|
9532
9763
|
parseSecretCatalogKey,
|
|
9533
9764
|
parseSpaceUri2 as parseSpaceUri,
|
|
9534
9765
|
pkhDid3 as pkhDid,
|
|
9535
|
-
|
|
9766
|
+
principalDid,
|
|
9536
9767
|
principalDidEquals4 as principalDidEquals,
|
|
9537
9768
|
registerReplicationLoaders,
|
|
9538
9769
|
resolveManifest2 as resolveManifest,
|