@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.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 },
|
|
@@ -7251,6 +7437,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7251
7437
|
}
|
|
7252
7438
|
};
|
|
7253
7439
|
}
|
|
7440
|
+
if (result.status === "aad_mismatch") return { status: "read_failed" };
|
|
7254
7441
|
return { status: result.status };
|
|
7255
7442
|
}
|
|
7256
7443
|
if (!isSecretPayload(result.entry.value)) {
|
|
@@ -7926,7 +8113,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7926
8113
|
return this.delegationManager.create(params);
|
|
7927
8114
|
}
|
|
7928
8115
|
/**
|
|
7929
|
-
* Revoke a delegation
|
|
8116
|
+
* Revoke a delegation with the owner wallet when available. Session-only
|
|
8117
|
+
* clients continue to use their delegated revocation authority.
|
|
7930
8118
|
*
|
|
7931
8119
|
* @param cid - The CID of the delegation to revoke
|
|
7932
8120
|
* @returns Result indicating success or failure
|
|
@@ -8042,6 +8230,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8042
8230
|
effectiveExpiration = sessionExpiry;
|
|
8043
8231
|
}
|
|
8044
8232
|
if (options?.forceWalletSign) {
|
|
8233
|
+
if (options.disableSubDelegation) {
|
|
8234
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
8235
|
+
}
|
|
8045
8236
|
if (expandedEntries.length > 1) {
|
|
8046
8237
|
throw new Error(
|
|
8047
8238
|
"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 +8268,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8077
8268
|
plan.effectiveExpiration,
|
|
8078
8269
|
plan.grant,
|
|
8079
8270
|
plan.operations,
|
|
8080
|
-
onPrepared
|
|
8271
|
+
onPrepared,
|
|
8272
|
+
options?.disableSubDelegation === true
|
|
8081
8273
|
);
|
|
8082
8274
|
return { delegation: delegation2, prompted: false };
|
|
8083
8275
|
}
|
|
@@ -8086,7 +8278,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8086
8278
|
expandedEntries,
|
|
8087
8279
|
effectiveExpiration,
|
|
8088
8280
|
session,
|
|
8089
|
-
onPrepared
|
|
8281
|
+
onPrepared,
|
|
8282
|
+
options?.disableSubDelegation === true
|
|
8090
8283
|
);
|
|
8091
8284
|
return { delegation, prompted: false };
|
|
8092
8285
|
}
|
|
@@ -8201,34 +8394,67 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8201
8394
|
return auth.replicationSignInEntries();
|
|
8202
8395
|
}
|
|
8203
8396
|
async replicationAuthority() {
|
|
8204
|
-
const { createReplicationAuthority } = await getNodeReplicationLoaders().authority();
|
|
8397
|
+
const { createReplicationAuthority, mintNodeReplicationGrant, verifiedOwnerConsentAnchor } = await getNodeReplicationLoaders().authority();
|
|
8205
8398
|
return createReplicationAuthority({
|
|
8206
8399
|
replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
|
|
8400
|
+
verifiedConsentAnchor: (session) => {
|
|
8401
|
+
const current = this.currentTinyCloudSession();
|
|
8402
|
+
return session === current && current !== void 0 ? verifiedOwnerConsentAnchor(current, this.wasmBindings, exactVerifiedRecap) : null;
|
|
8403
|
+
},
|
|
8207
8404
|
siweExpiration: (siwe) => extractSiweExpiration(siwe),
|
|
8208
8405
|
planDelegation: (entries, options) => this.planDelegation(entries, options),
|
|
8209
|
-
mintDelegation:
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8406
|
+
mintDelegation: (deviceDid, entries, options) => mintNodeReplicationGrant(
|
|
8407
|
+
deviceDid,
|
|
8408
|
+
entries,
|
|
8409
|
+
options,
|
|
8410
|
+
options?.renewalOnly ? this.currentTinyCloudSession() : void 0,
|
|
8411
|
+
this
|
|
8412
|
+
)
|
|
8216
8413
|
});
|
|
8217
8414
|
}
|
|
8218
|
-
async
|
|
8415
|
+
async quiesceReplicaRuntime() {
|
|
8219
8416
|
if (!this.replicationRuntime) return;
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8417
|
+
try {
|
|
8418
|
+
await (await this.replicationRuntime).unbind();
|
|
8419
|
+
} catch {
|
|
8420
|
+
this.replicaRestoreGate = "gate_unavailable";
|
|
8421
|
+
}
|
|
8422
|
+
}
|
|
8423
|
+
async bindReplicaOrGate(context, session, kv, principal) {
|
|
8424
|
+
if (!this.replicationRuntime) return;
|
|
8425
|
+
const gate = this.replicaRestoreGate;
|
|
8426
|
+
const epoch = this.sessionEpoch;
|
|
8427
|
+
const attempt = this.latestSessionAttempt;
|
|
8428
|
+
try {
|
|
8429
|
+
const runtime = await this.replicationRuntime;
|
|
8430
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context || gate !== this.replicaRestoreGate) return;
|
|
8431
|
+
if (gate) {
|
|
8432
|
+
await runtime.bindGatedNetworkOnly({
|
|
8433
|
+
context,
|
|
8434
|
+
session,
|
|
8435
|
+
gate,
|
|
8436
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
8437
|
+
}, getNodeReplicationLoaders().gatedNetwork);
|
|
8438
|
+
return;
|
|
8439
|
+
}
|
|
8440
|
+
const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : pkhDid2(this._address, this._chainId));
|
|
8441
|
+
if (identityPrincipal === void 0) return;
|
|
8442
|
+
const authority = await this.replicationAuthority();
|
|
8443
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
|
|
8444
|
+
await runtime.bind({
|
|
8445
|
+
context,
|
|
8446
|
+
session,
|
|
8447
|
+
principal: identityPrincipal,
|
|
8448
|
+
stamp: this.replicaSessionStamp,
|
|
8449
|
+
authority,
|
|
8450
|
+
nodeFeatures: this.replicationNodeFeatures,
|
|
8451
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
8452
|
+
});
|
|
8453
|
+
} catch {
|
|
8454
|
+
if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
|
|
8455
|
+
this.replicaRestoreGate = "gate_unavailable";
|
|
8456
|
+
await this.quiesceReplicaRuntime();
|
|
8457
|
+
}
|
|
8232
8458
|
}
|
|
8233
8459
|
/** Stable control facade for this node's optional replication runtime. */
|
|
8234
8460
|
get replication() {
|
|
@@ -8284,7 +8510,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8284
8510
|
*
|
|
8285
8511
|
* @internal
|
|
8286
8512
|
*/
|
|
8287
|
-
async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared) {
|
|
8513
|
+
async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared, disableSubDelegation = false) {
|
|
8288
8514
|
if (entries.length === 0) {
|
|
8289
8515
|
throw new Error(
|
|
8290
8516
|
"createDelegationViaWasmPath requires a non-empty entries array"
|
|
@@ -8344,7 +8570,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8344
8570
|
spaceId,
|
|
8345
8571
|
abilities,
|
|
8346
8572
|
expirationSecs
|
|
8347
|
-
});
|
|
8573
|
+
}, disableSubDelegation);
|
|
8348
8574
|
const primary = result.resources[0];
|
|
8349
8575
|
const delegationHeader = { Authorization: result.delegation };
|
|
8350
8576
|
const delegation = {
|
|
@@ -8354,7 +8580,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8354
8580
|
path: primary.path,
|
|
8355
8581
|
actions: primary.actions,
|
|
8356
8582
|
resources: result.resources,
|
|
8357
|
-
disableSubDelegation
|
|
8583
|
+
disableSubDelegation,
|
|
8358
8584
|
expiry: result.expiry,
|
|
8359
8585
|
delegateDID: did,
|
|
8360
8586
|
ownerAddress: session.address,
|
|
@@ -8376,7 +8602,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8376
8602
|
}
|
|
8377
8603
|
return delegation;
|
|
8378
8604
|
}
|
|
8379
|
-
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared) {
|
|
8605
|
+
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared, disableSubDelegation = false) {
|
|
8380
8606
|
this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
|
|
8381
8607
|
this.assertDelegationCaveatsPreservable(entries);
|
|
8382
8608
|
const result = this.createDelegationWrapper({
|
|
@@ -8385,7 +8611,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8385
8611
|
spaceId: grant.session.spaceId,
|
|
8386
8612
|
abilities: this.permissionsToAbilities(entries),
|
|
8387
8613
|
expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
|
|
8388
|
-
});
|
|
8614
|
+
}, disableSubDelegation);
|
|
8389
8615
|
const primary = result.resources[0];
|
|
8390
8616
|
const delegationHeader = { Authorization: result.delegation };
|
|
8391
8617
|
const targetHost = grant.delegation.host ?? this.config.host;
|
|
@@ -8396,7 +8622,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8396
8622
|
path: primary.path,
|
|
8397
8623
|
actions: primary.actions,
|
|
8398
8624
|
resources: result.resources,
|
|
8399
|
-
disableSubDelegation
|
|
8625
|
+
disableSubDelegation,
|
|
8400
8626
|
expiry: result.expiry,
|
|
8401
8627
|
delegateDID: did,
|
|
8402
8628
|
ownerAddress: grant.delegation.ownerAddress,
|
|
@@ -8980,11 +9206,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
8980
9206
|
params.spaceIdOverride
|
|
8981
9207
|
);
|
|
8982
9208
|
try {
|
|
8983
|
-
const result = await this.delegateTo(
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
);
|
|
9209
|
+
const result = await this.delegateTo(resolvedDelegateDID, entries, {
|
|
9210
|
+
...params.expiryMs !== void 0 ? { expiry: params.expiryMs } : {},
|
|
9211
|
+
disableSubDelegation: params.disableSubDelegation
|
|
9212
|
+
});
|
|
8988
9213
|
return result.delegation;
|
|
8989
9214
|
} catch (err2) {
|
|
8990
9215
|
if (err2 instanceof PermissionNotInManifestError) {
|
|
@@ -9005,6 +9230,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9005
9230
|
* @internal
|
|
9006
9231
|
*/
|
|
9007
9232
|
async createDelegationWalletPath(params) {
|
|
9233
|
+
if (params.disableSubDelegation) {
|
|
9234
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
9235
|
+
}
|
|
9008
9236
|
if (!this.signer) {
|
|
9009
9237
|
throw new Error("Cannot createDelegation() in session-only mode. Requires wallet mode.");
|
|
9010
9238
|
}
|
|
@@ -9060,7 +9288,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9060
9288
|
spaceId: params.spaceIdOverride ?? session.spaceId,
|
|
9061
9289
|
path: params.path,
|
|
9062
9290
|
actions: params.actions,
|
|
9063
|
-
disableSubDelegation:
|
|
9291
|
+
disableSubDelegation: false,
|
|
9064
9292
|
expiry: expirationTime,
|
|
9065
9293
|
delegateDID: params.delegateDID,
|
|
9066
9294
|
ownerAddress: session.address,
|
|
@@ -9103,7 +9331,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9103
9331
|
spaceId: publicSpaceId,
|
|
9104
9332
|
path: "",
|
|
9105
9333
|
actions: [KV3.GET, KV3.PUT, KV3.METADATA],
|
|
9106
|
-
disableSubDelegation:
|
|
9334
|
+
disableSubDelegation: false,
|
|
9107
9335
|
expiry: expirationTime,
|
|
9108
9336
|
delegateDID: params.delegateDID,
|
|
9109
9337
|
ownerAddress: session.address,
|
|
@@ -9270,6 +9498,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9270
9498
|
if (parentDelegation.disableSubDelegation) {
|
|
9271
9499
|
throw new Error("Parent delegation does not allow sub-delegation");
|
|
9272
9500
|
}
|
|
9501
|
+
if (params.disableSubDelegation) {
|
|
9502
|
+
throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
|
|
9503
|
+
}
|
|
9273
9504
|
const parentResources = parentDelegation.resources ?? [{
|
|
9274
9505
|
service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
|
|
9275
9506
|
space: parentDelegation.spaceId,
|
|
@@ -9342,7 +9573,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
9342
9573
|
spaceId: parentDelegation.spaceId,
|
|
9343
9574
|
path: requestedResources[0].path,
|
|
9344
9575
|
actions: requestedResources[0].actions,
|
|
9345
|
-
disableSubDelegation:
|
|
9576
|
+
disableSubDelegation: false,
|
|
9346
9577
|
expiry: actualExpiry,
|
|
9347
9578
|
delegateDID: params.delegateDID,
|
|
9348
9579
|
ownerAddress: parentDelegation.ownerAddress,
|
|
@@ -9436,8 +9667,8 @@ import {
|
|
|
9436
9667
|
Space
|
|
9437
9668
|
} from "@tinycloud/sdk-core";
|
|
9438
9669
|
import {
|
|
9439
|
-
ProtocolMismatchError,
|
|
9440
|
-
VersionCheckError
|
|
9670
|
+
ProtocolMismatchError as ProtocolMismatchError2,
|
|
9671
|
+
VersionCheckError,
|
|
9441
9672
|
UnsupportedFeatureError as UnsupportedFeatureError2,
|
|
9442
9673
|
checkNodeInfo as checkNodeInfo3
|
|
9443
9674
|
} from "@tinycloud/sdk-core";
|
|
@@ -9470,7 +9701,7 @@ export {
|
|
|
9470
9701
|
NodeUserAuthorization,
|
|
9471
9702
|
PermissionNotInManifestError2 as PermissionNotInManifestError,
|
|
9472
9703
|
PrefixedKVService,
|
|
9473
|
-
ProtocolMismatchError,
|
|
9704
|
+
ProtocolMismatchError2 as ProtocolMismatchError,
|
|
9474
9705
|
SECRET_NAME_RE,
|
|
9475
9706
|
SQLAction,
|
|
9476
9707
|
SQLService3 as SQLService,
|
|
@@ -9490,7 +9721,7 @@ export {
|
|
|
9490
9721
|
VAULT_PERMISSION_SERVICE,
|
|
9491
9722
|
VaultHeaders,
|
|
9492
9723
|
VaultPublicSpaceKVActions,
|
|
9493
|
-
|
|
9724
|
+
VersionCheckError,
|
|
9494
9725
|
WasmKeyProvider,
|
|
9495
9726
|
activateCompactRuntimeDelegation,
|
|
9496
9727
|
addressStorageKey,
|
|
@@ -9519,6 +9750,7 @@ export {
|
|
|
9519
9750
|
expandActionShortNames,
|
|
9520
9751
|
expandPermissionEntries2 as expandPermissionEntries,
|
|
9521
9752
|
expandPermissionEntry,
|
|
9753
|
+
getNodeReplicationLoaders,
|
|
9522
9754
|
getTinyCloudDebugLogs,
|
|
9523
9755
|
installTinyCloudDebugGlobals,
|
|
9524
9756
|
isCapabilitySubset3 as isCapabilitySubset,
|
|
@@ -9532,7 +9764,7 @@ export {
|
|
|
9532
9764
|
parseSecretCatalogKey,
|
|
9533
9765
|
parseSpaceUri2 as parseSpaceUri,
|
|
9534
9766
|
pkhDid3 as pkhDid,
|
|
9535
|
-
|
|
9767
|
+
principalDid,
|
|
9536
9768
|
principalDidEquals4 as principalDidEquals,
|
|
9537
9769
|
registerReplicationLoaders,
|
|
9538
9770
|
resolveManifest2 as resolveManifest,
|