@tinycloud/node-sdk 2.1.0-beta.0 → 2.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.cjs +519 -99
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +232 -4
- package/dist/core.d.ts +232 -4
- package/dist/core.js +451 -19
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +523 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +453 -19
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/core.js
CHANGED
|
@@ -203,7 +203,9 @@ import {
|
|
|
203
203
|
submitHostDelegation,
|
|
204
204
|
activateSessionWithHost,
|
|
205
205
|
checkNodeInfo,
|
|
206
|
-
AutoApproveSpaceCreationHandler
|
|
206
|
+
AutoApproveSpaceCreationHandler,
|
|
207
|
+
manifestAbilitiesUnion,
|
|
208
|
+
resolveManifest
|
|
207
209
|
} from "@tinycloud/sdk-core";
|
|
208
210
|
|
|
209
211
|
// src/authorization/strategies.ts
|
|
@@ -272,8 +274,25 @@ var NodeUserAuthorization = class {
|
|
|
272
274
|
this.enablePublicSpace = config.enablePublicSpace ?? true;
|
|
273
275
|
this.nonce = config.nonce;
|
|
274
276
|
this.siweConfig = config.siweConfig;
|
|
277
|
+
this._manifest = config.manifest;
|
|
275
278
|
this.sessionManager = this.wasm.createSessionManager();
|
|
276
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Return the manifest currently driving sign-in behavior, or
|
|
282
|
+
* `undefined` if none is set. Used by TinyCloudWeb/TinyCloudNode
|
|
283
|
+
* internals to surface the manifest for requestPermissions flows
|
|
284
|
+
* without forcing the caller to track it separately.
|
|
285
|
+
*/
|
|
286
|
+
get manifest() {
|
|
287
|
+
return this._manifest;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Install or replace the stored manifest. Takes effect on the next
|
|
291
|
+
* `signIn()` call — the current session (if any) is not touched.
|
|
292
|
+
*/
|
|
293
|
+
setManifest(manifest) {
|
|
294
|
+
this._manifest = manifest;
|
|
295
|
+
}
|
|
277
296
|
/**
|
|
278
297
|
* The current active session (web-core compatible).
|
|
279
298
|
*/
|
|
@@ -290,6 +309,39 @@ var NodeUserAuthorization = class {
|
|
|
290
309
|
get nodeFeatures() {
|
|
291
310
|
return this._nodeFeatures;
|
|
292
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Compute the `abilities` map the WASM `prepareSession` call should
|
|
314
|
+
* see at sign-in time.
|
|
315
|
+
*
|
|
316
|
+
* When a manifest is installed, we resolve it and union together:
|
|
317
|
+
* - the app's own `resources` (what it needs at runtime)
|
|
318
|
+
* - every `additionalDelegates[*].permissions` list (what it will
|
|
319
|
+
* re-delegate to other DIDs post sign-in)
|
|
320
|
+
*
|
|
321
|
+
* into the short-service / path / full-URN-actions shape the WASM
|
|
322
|
+
* layer expects. This is the key invariant that lets
|
|
323
|
+
* {@link TinyCloudNode.delegateTo} issue manifest-declared
|
|
324
|
+
* delegations via the session key (no wallet prompt): the session's
|
|
325
|
+
* own recap already covers every action those delegations need.
|
|
326
|
+
*
|
|
327
|
+
* When no manifest is installed, we fall back to the
|
|
328
|
+
* {@link defaultActions} table so existing callers see no change.
|
|
329
|
+
*
|
|
330
|
+
* This is a pure function of `this._manifest` + `this.defaultActions`
|
|
331
|
+
* — the manifest resolution performs no I/O and throws a
|
|
332
|
+
* {@link ManifestValidationError} on structural problems (missing
|
|
333
|
+
* id/name, unparseable expiry, etc), which will surface at sign-in
|
|
334
|
+
* rather than being silently swallowed.
|
|
335
|
+
*
|
|
336
|
+
* @internal
|
|
337
|
+
*/
|
|
338
|
+
resolveSignInAbilities() {
|
|
339
|
+
if (this._manifest === void 0) {
|
|
340
|
+
return this.defaultActions;
|
|
341
|
+
}
|
|
342
|
+
const resolved = resolveManifest(this._manifest);
|
|
343
|
+
return manifestAbilitiesUnion(resolved);
|
|
344
|
+
}
|
|
293
345
|
/**
|
|
294
346
|
* Build SIWE overrides from the top-level nonce and siweConfig.
|
|
295
347
|
* - Top-level `nonce` is seeded first so `siweConfig.nonce` wins if both are set.
|
|
@@ -493,7 +545,7 @@ var NodeUserAuthorization = class {
|
|
|
493
545
|
const now = /* @__PURE__ */ new Date();
|
|
494
546
|
const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
|
|
495
547
|
const prepared = this.wasm.prepareSession({
|
|
496
|
-
abilities: this.
|
|
548
|
+
abilities: this.resolveSignInAbilities(),
|
|
497
549
|
address,
|
|
498
550
|
chainId,
|
|
499
551
|
domain: this.domain,
|
|
@@ -636,7 +688,7 @@ var NodeUserAuthorization = class {
|
|
|
636
688
|
const now = /* @__PURE__ */ new Date();
|
|
637
689
|
const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
|
|
638
690
|
const prepared = this.wasm.prepareSession({
|
|
639
|
-
abilities: this.
|
|
691
|
+
abilities: this.resolveSignInAbilities(),
|
|
640
692
|
address,
|
|
641
693
|
chainId,
|
|
642
694
|
domain: this.domain,
|
|
@@ -820,7 +872,13 @@ import {
|
|
|
820
872
|
CapabilityKeyRegistry,
|
|
821
873
|
SharingService,
|
|
822
874
|
UnsupportedFeatureError,
|
|
823
|
-
makePublicSpaceId
|
|
875
|
+
makePublicSpaceId,
|
|
876
|
+
PermissionNotInManifestError,
|
|
877
|
+
SessionExpiredError,
|
|
878
|
+
expandActionShortNames,
|
|
879
|
+
isCapabilitySubset,
|
|
880
|
+
parseRecapCapabilities,
|
|
881
|
+
SERVICE_LONG_TO_SHORT
|
|
824
882
|
} from "@tinycloud/sdk-core";
|
|
825
883
|
|
|
826
884
|
// src/DelegatedAccess.ts
|
|
@@ -982,9 +1040,72 @@ function createWasmKeyProvider(sessionManager) {
|
|
|
982
1040
|
return new WasmKeyProvider({ sessionManager });
|
|
983
1041
|
}
|
|
984
1042
|
|
|
1043
|
+
// src/delegateToHelpers.ts
|
|
1044
|
+
import {
|
|
1045
|
+
parseExpiry,
|
|
1046
|
+
SiweMessage
|
|
1047
|
+
} from "@tinycloud/sdk-core";
|
|
1048
|
+
function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
|
|
1049
|
+
const byService = /* @__PURE__ */ new Map();
|
|
1050
|
+
for (const a of actions) {
|
|
1051
|
+
const slashIdx = a.indexOf("/");
|
|
1052
|
+
if (slashIdx === -1) {
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
const service = a.slice(0, slashIdx);
|
|
1056
|
+
if (!service.startsWith("tinycloud.")) {
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
const list = byService.get(service);
|
|
1060
|
+
if (list === void 0) {
|
|
1061
|
+
byService.set(service, [a]);
|
|
1062
|
+
} else {
|
|
1063
|
+
list.push(a);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
const space = spaceIdOverride ?? "default";
|
|
1067
|
+
const entries = [];
|
|
1068
|
+
for (const [service, actionList] of byService) {
|
|
1069
|
+
entries.push({
|
|
1070
|
+
service,
|
|
1071
|
+
space,
|
|
1072
|
+
path,
|
|
1073
|
+
actions: actionList
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return entries;
|
|
1077
|
+
}
|
|
1078
|
+
function resolveExpiryMs(expiry) {
|
|
1079
|
+
if (expiry === void 0) {
|
|
1080
|
+
return 60 * 60 * 1e3;
|
|
1081
|
+
}
|
|
1082
|
+
if (typeof expiry === "number") {
|
|
1083
|
+
if (!Number.isFinite(expiry) || expiry <= 0) {
|
|
1084
|
+
throw new Error(
|
|
1085
|
+
`delegateTo expiry must be a positive finite number (got ${expiry})`
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
return expiry;
|
|
1089
|
+
}
|
|
1090
|
+
return parseExpiry(expiry);
|
|
1091
|
+
}
|
|
1092
|
+
function extractSiweExpiration(siwe) {
|
|
1093
|
+
const parsed = new SiweMessage(siwe);
|
|
1094
|
+
if (parsed.expirationTime === void 0 || parsed.expirationTime === null) {
|
|
1095
|
+
return void 0;
|
|
1096
|
+
}
|
|
1097
|
+
const d = new Date(parsed.expirationTime);
|
|
1098
|
+
if (Number.isNaN(d.getTime())) {
|
|
1099
|
+
throw new Error(
|
|
1100
|
+
`Session SIWE has unparseable expirationTime: ${parsed.expirationTime}`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
return d;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
985
1106
|
// src/TinyCloudNode.ts
|
|
986
1107
|
var DEFAULT_HOST = "https://node.tinycloud.xyz";
|
|
987
|
-
var
|
|
1108
|
+
var _TinyCloudNode = class _TinyCloudNode {
|
|
988
1109
|
/**
|
|
989
1110
|
* Create a new TinyCloudNode instance.
|
|
990
1111
|
*
|
|
@@ -1108,12 +1229,35 @@ var TinyCloudNode = class _TinyCloudNode {
|
|
|
1108
1229
|
enablePublicSpace: config.enablePublicSpace ?? true,
|
|
1109
1230
|
spaceCreationHandler: config.spaceCreationHandler,
|
|
1110
1231
|
nonce: config.nonce,
|
|
1111
|
-
siweConfig: config.siweConfig
|
|
1232
|
+
siweConfig: config.siweConfig,
|
|
1233
|
+
manifest: config.manifest
|
|
1112
1234
|
});
|
|
1113
1235
|
this.tc = new TinyCloud(this.auth, {
|
|
1114
1236
|
invokeAny: this.wasmBindings.invokeAny
|
|
1115
1237
|
});
|
|
1116
1238
|
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Install or replace the manifest that drives the SIWE recap at
|
|
1241
|
+
* sign-in. Takes effect on the next `signIn()` call — the current
|
|
1242
|
+
* session (if any) is not touched. Wire this up from a higher
|
|
1243
|
+
* layer (e.g. TinyCloudWeb.setManifest) so the manifest is kept
|
|
1244
|
+
* in sync across the stack.
|
|
1245
|
+
*/
|
|
1246
|
+
setManifest(manifest) {
|
|
1247
|
+
if (!this.auth) {
|
|
1248
|
+
throw new Error(
|
|
1249
|
+
"setManifest requires wallet mode. Provide a signer or privateKey in the TinyCloudNode config."
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
this.auth.setManifest(manifest);
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Return the manifest currently installed on the auth handler,
|
|
1256
|
+
* or `undefined` if none is set.
|
|
1257
|
+
*/
|
|
1258
|
+
get manifest() {
|
|
1259
|
+
return this.auth?.manifest;
|
|
1260
|
+
}
|
|
1117
1261
|
/**
|
|
1118
1262
|
* Get the primary identity DID for this user.
|
|
1119
1263
|
* - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
|
|
@@ -1620,7 +1764,19 @@ var TinyCloudNode = class _TinyCloudNode {
|
|
|
1620
1764
|
}
|
|
1621
1765
|
/**
|
|
1622
1766
|
* Wrapper for the WASM createDelegation function.
|
|
1623
|
-
*
|
|
1767
|
+
*
|
|
1768
|
+
* The WASM call now takes a multi-resource `abilities` map
|
|
1769
|
+
* (matching `prepareSession`'s shape) and emits ONE UCAN that
|
|
1770
|
+
* covers every `(service, path, actions)` entry. We mirror the raw
|
|
1771
|
+
* result back through `CreateDelegationWasmResult`, converting the
|
|
1772
|
+
* seconds-since-epoch `expiry` to a Date and normalizing the
|
|
1773
|
+
* `delegateDid` → `delegateDID` case.
|
|
1774
|
+
*
|
|
1775
|
+
* Both SharingService (single-entry) and
|
|
1776
|
+
* {@link TinyCloudNode.delegateTo} (multi-entry) drive this through
|
|
1777
|
+
* the same code path so there's exactly one place that touches the
|
|
1778
|
+
* WASM boundary.
|
|
1779
|
+
*
|
|
1624
1780
|
* @internal
|
|
1625
1781
|
*/
|
|
1626
1782
|
createDelegationWrapper(params) {
|
|
@@ -1635,18 +1791,19 @@ var TinyCloudNode = class _TinyCloudNode {
|
|
|
1635
1791
|
wasmSession,
|
|
1636
1792
|
params.delegateDID,
|
|
1637
1793
|
params.spaceId,
|
|
1638
|
-
params.
|
|
1639
|
-
params.actions,
|
|
1794
|
+
params.abilities,
|
|
1640
1795
|
params.expirationSecs,
|
|
1641
1796
|
params.notBeforeSecs
|
|
1642
1797
|
);
|
|
1643
1798
|
return {
|
|
1644
1799
|
delegation: result.delegation,
|
|
1645
1800
|
cid: result.cid,
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1801
|
+
// Rust serde `rename_all = "camelCase"` emits `delegateDid`
|
|
1802
|
+
// (lowercase d); the TypeScript interface uses `delegateDID`
|
|
1803
|
+
// (historical, matches Delegation.delegateDID). Normalize here.
|
|
1804
|
+
delegateDID: result.delegateDid ?? result.delegateDID,
|
|
1805
|
+
expiry: new Date(result.expiry * 1e3),
|
|
1806
|
+
resources: result.resources
|
|
1650
1807
|
};
|
|
1651
1808
|
}
|
|
1652
1809
|
/**
|
|
@@ -2118,6 +2275,209 @@ var TinyCloudNode = class _TinyCloudNode {
|
|
|
2118
2275
|
async checkPermission(path, action) {
|
|
2119
2276
|
return this.delegationManager.checkPermission(path, action);
|
|
2120
2277
|
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Issue a delegation using the capability-chain flow.
|
|
2280
|
+
*
|
|
2281
|
+
* When every requested permission is a subset of the current
|
|
2282
|
+
* session's recap, the delegation is signed by the session key via
|
|
2283
|
+
* WASM — no wallet prompt. When at least one is NOT derivable, a
|
|
2284
|
+
* {@link PermissionNotInManifestError} is raised (carrying the
|
|
2285
|
+
* missing entries) so the caller can trigger an escalation flow
|
|
2286
|
+
* (e.g. `TinyCloudWeb.requestPermissions`). Passing
|
|
2287
|
+
* `forceWalletSign: true` bypasses the derivability check and
|
|
2288
|
+
* always uses the wallet-signed SIWE path — used by the legacy
|
|
2289
|
+
* `createDelegation` fallback and by callers that want explicit
|
|
2290
|
+
* wallet confirmation.
|
|
2291
|
+
*
|
|
2292
|
+
* Multi-entry delegations are now emitted as **one** signed UCAN:
|
|
2293
|
+
* the underlying WASM `createDelegation` takes a full
|
|
2294
|
+
* `HashMap<Service, HashMap<Path, Vec<Ability>>>` abilities map
|
|
2295
|
+
* and produces a single attenuation carrying every
|
|
2296
|
+
* `(service, path, actions)` entry. The returned
|
|
2297
|
+
* {@link DelegateToResult.delegation} is that single blob, and
|
|
2298
|
+
* apps can POST it to their backend exactly like a single-entry
|
|
2299
|
+
* delegation (the server verifies all granted resources from one
|
|
2300
|
+
* UCAN).
|
|
2301
|
+
*
|
|
2302
|
+
* For single-entry requests the `PortableDelegation.path` and
|
|
2303
|
+
* `.actions` fields mirror the one granted entry. For
|
|
2304
|
+
* multi-entry requests they mirror the **first** entry (stable
|
|
2305
|
+
* lexicographic order from the Rust side); consumers that need
|
|
2306
|
+
* the full picture read `PortableDelegation.resources`.
|
|
2307
|
+
*
|
|
2308
|
+
* @throws {@link SessionExpiredError} when there is no session or
|
|
2309
|
+
* the current session has expired (or will within the 60s
|
|
2310
|
+
* safety margin).
|
|
2311
|
+
* @throws {@link PermissionNotInManifestError} when any requested
|
|
2312
|
+
* entry is not a subset of the granted session capabilities and
|
|
2313
|
+
* `forceWalletSign` is not set.
|
|
2314
|
+
*/
|
|
2315
|
+
async delegateTo(did, permissions, options) {
|
|
2316
|
+
const session = this.auth?.tinyCloudSession;
|
|
2317
|
+
if (!session) {
|
|
2318
|
+
throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
|
|
2319
|
+
}
|
|
2320
|
+
const sessionExpiry = extractSiweExpiration(session.siwe);
|
|
2321
|
+
if (sessionExpiry !== void 0) {
|
|
2322
|
+
const now2 = Date.now();
|
|
2323
|
+
const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
|
|
2324
|
+
if (sessionExpiry.getTime() <= now2 + marginMs) {
|
|
2325
|
+
throw new SessionExpiredError(sessionExpiry);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
if (!Array.isArray(permissions) || permissions.length === 0) {
|
|
2329
|
+
throw new Error(
|
|
2330
|
+
"delegateTo requires a non-empty permissions array"
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
const expandedEntries = permissions.map((entry) => ({
|
|
2334
|
+
...entry,
|
|
2335
|
+
actions: expandActionShortNames(entry.service, entry.actions)
|
|
2336
|
+
}));
|
|
2337
|
+
const now = /* @__PURE__ */ new Date();
|
|
2338
|
+
const expiryMs = resolveExpiryMs(options?.expiry);
|
|
2339
|
+
const expirationTime = new Date(now.getTime() + expiryMs);
|
|
2340
|
+
let effectiveExpiration = expirationTime;
|
|
2341
|
+
if (sessionExpiry !== void 0 && sessionExpiry < expirationTime) {
|
|
2342
|
+
effectiveExpiration = sessionExpiry;
|
|
2343
|
+
}
|
|
2344
|
+
if (options?.forceWalletSign) {
|
|
2345
|
+
if (expandedEntries.length > 1) {
|
|
2346
|
+
throw new Error(
|
|
2347
|
+
"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."
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
const delegation2 = await this.createDelegationLegacyWalletPath(
|
|
2351
|
+
did,
|
|
2352
|
+
expandedEntries[0],
|
|
2353
|
+
effectiveExpiration
|
|
2354
|
+
);
|
|
2355
|
+
return { delegation: delegation2, prompted: true };
|
|
2356
|
+
}
|
|
2357
|
+
const granted = parseRecapCapabilities(
|
|
2358
|
+
(siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
|
|
2359
|
+
session.siwe
|
|
2360
|
+
);
|
|
2361
|
+
const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
|
|
2362
|
+
if (!subset) {
|
|
2363
|
+
throw new PermissionNotInManifestError(missing, granted);
|
|
2364
|
+
}
|
|
2365
|
+
const delegation = await this.createDelegationViaWasmPath(
|
|
2366
|
+
did,
|
|
2367
|
+
expandedEntries,
|
|
2368
|
+
effectiveExpiration,
|
|
2369
|
+
session
|
|
2370
|
+
);
|
|
2371
|
+
return { delegation, prompted: false };
|
|
2372
|
+
}
|
|
2373
|
+
/**
|
|
2374
|
+
* Issue a delegation via the session-key UCAN WASM path.
|
|
2375
|
+
*
|
|
2376
|
+
* The caller has already verified every entry is derivable from
|
|
2377
|
+
* the current session; we build one multi-resource abilities map
|
|
2378
|
+
* and emit one signed UCAN covering them all.
|
|
2379
|
+
*
|
|
2380
|
+
* All entries must share the same target space (the UCAN is
|
|
2381
|
+
* scoped to a single space). If they don't, this throws — mixing
|
|
2382
|
+
* spaces in a single delegation is not supported by the underlying
|
|
2383
|
+
* Rust create_delegation call and the resulting UCAN would be
|
|
2384
|
+
* under-specified.
|
|
2385
|
+
*
|
|
2386
|
+
* @internal
|
|
2387
|
+
*/
|
|
2388
|
+
async createDelegationViaWasmPath(did, entries, expirationTime, session) {
|
|
2389
|
+
if (entries.length === 0) {
|
|
2390
|
+
throw new Error(
|
|
2391
|
+
"createDelegationViaWasmPath requires a non-empty entries array"
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
const resolvedSpaces = /* @__PURE__ */ new Set();
|
|
2395
|
+
for (const entry of entries) {
|
|
2396
|
+
const spaceId2 = entry.space === "default" ? session.spaceId : entry.space;
|
|
2397
|
+
resolvedSpaces.add(spaceId2);
|
|
2398
|
+
}
|
|
2399
|
+
if (resolvedSpaces.size !== 1) {
|
|
2400
|
+
throw new Error(
|
|
2401
|
+
`delegateTo: all permission entries must target the same space, got ${resolvedSpaces.size}: ${JSON.stringify([...resolvedSpaces])}`
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
2404
|
+
const spaceId = [...resolvedSpaces][0];
|
|
2405
|
+
const abilities = {};
|
|
2406
|
+
for (const entry of entries) {
|
|
2407
|
+
const shortService = SERVICE_LONG_TO_SHORT[entry.service];
|
|
2408
|
+
if (shortService === void 0) {
|
|
2409
|
+
throw new Error(
|
|
2410
|
+
`delegateTo: unknown service '${entry.service}' \u2014 no short-form mapping`
|
|
2411
|
+
);
|
|
2412
|
+
}
|
|
2413
|
+
if (abilities[shortService] === void 0) {
|
|
2414
|
+
abilities[shortService] = {};
|
|
2415
|
+
}
|
|
2416
|
+
const pathsMap = abilities[shortService];
|
|
2417
|
+
const existing = pathsMap[entry.path];
|
|
2418
|
+
if (existing === void 0) {
|
|
2419
|
+
pathsMap[entry.path] = [...entry.actions];
|
|
2420
|
+
} else {
|
|
2421
|
+
const seen = new Set(existing);
|
|
2422
|
+
for (const action of entry.actions) {
|
|
2423
|
+
if (!seen.has(action)) {
|
|
2424
|
+
existing.push(action);
|
|
2425
|
+
seen.add(action);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const serviceSession = {
|
|
2431
|
+
delegationHeader: session.delegationHeader,
|
|
2432
|
+
delegationCid: session.delegationCid,
|
|
2433
|
+
jwk: session.jwk,
|
|
2434
|
+
spaceId,
|
|
2435
|
+
verificationMethod: session.verificationMethod
|
|
2436
|
+
};
|
|
2437
|
+
const expirationSecs = Math.floor(expirationTime.getTime() / 1e3);
|
|
2438
|
+
const result = this.createDelegationWrapper({
|
|
2439
|
+
session: serviceSession,
|
|
2440
|
+
delegateDID: did,
|
|
2441
|
+
spaceId,
|
|
2442
|
+
abilities,
|
|
2443
|
+
expirationSecs
|
|
2444
|
+
});
|
|
2445
|
+
const primary = result.resources[0];
|
|
2446
|
+
return {
|
|
2447
|
+
cid: result.cid,
|
|
2448
|
+
delegationHeader: { Authorization: `Bearer ${result.delegation}` },
|
|
2449
|
+
spaceId,
|
|
2450
|
+
path: primary.path,
|
|
2451
|
+
actions: primary.actions,
|
|
2452
|
+
resources: result.resources,
|
|
2453
|
+
disableSubDelegation: false,
|
|
2454
|
+
expiry: result.expiry,
|
|
2455
|
+
delegateDID: did,
|
|
2456
|
+
ownerAddress: session.address,
|
|
2457
|
+
chainId: session.chainId,
|
|
2458
|
+
host: this.config.host
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Issue a delegation via the legacy wallet-signed SIWE path for a single
|
|
2463
|
+
* {@link PermissionEntry}. Shares the implementation with the public
|
|
2464
|
+
* `createDelegation` method via {@link createDelegationWalletPath} so
|
|
2465
|
+
* both entry points hit exactly the same SIWE / signer / public-space
|
|
2466
|
+
* logic without mutual recursion.
|
|
2467
|
+
*
|
|
2468
|
+
* @internal
|
|
2469
|
+
*/
|
|
2470
|
+
async createDelegationLegacyWalletPath(delegateDID, entry, expirationTime) {
|
|
2471
|
+
const spaceIdOverride = entry.space === "default" ? void 0 : entry.space;
|
|
2472
|
+
return this.createDelegationWalletPath({
|
|
2473
|
+
path: entry.path,
|
|
2474
|
+
actions: entry.actions,
|
|
2475
|
+
delegateDID,
|
|
2476
|
+
includePublicSpace: true,
|
|
2477
|
+
expiryMs: Math.max(0, expirationTime.getTime() - Date.now()),
|
|
2478
|
+
spaceIdOverride
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2121
2481
|
/**
|
|
2122
2482
|
* Create a delegation from this user to another user.
|
|
2123
2483
|
*
|
|
@@ -2128,6 +2488,49 @@ var TinyCloudNode = class _TinyCloudNode {
|
|
|
2128
2488
|
* @returns A portable delegation that can be sent to the recipient
|
|
2129
2489
|
*/
|
|
2130
2490
|
async createDelegation(params) {
|
|
2491
|
+
if (!this.signer) {
|
|
2492
|
+
throw new Error("Cannot createDelegation() in session-only mode. Requires wallet mode.");
|
|
2493
|
+
}
|
|
2494
|
+
if (!this.auth?.tinyCloudSession) {
|
|
2495
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
2496
|
+
}
|
|
2497
|
+
let resolvedDelegateDID = params.delegateDID;
|
|
2498
|
+
if (resolvedDelegateDID.endsWith(".eth") && this.config.ensResolver) {
|
|
2499
|
+
const address = await this.config.ensResolver.resolveAddress(resolvedDelegateDID);
|
|
2500
|
+
if (!address) throw new Error(`Could not resolve ENS name: ${resolvedDelegateDID}`);
|
|
2501
|
+
resolvedDelegateDID = `did:pkh:eip155:1:${address}`;
|
|
2502
|
+
}
|
|
2503
|
+
const entries = legacyParamsToPermissionEntries(
|
|
2504
|
+
params.actions,
|
|
2505
|
+
params.path,
|
|
2506
|
+
params.spaceIdOverride
|
|
2507
|
+
);
|
|
2508
|
+
try {
|
|
2509
|
+
const result = await this.delegateTo(
|
|
2510
|
+
resolvedDelegateDID,
|
|
2511
|
+
entries,
|
|
2512
|
+
params.expiryMs !== void 0 ? { expiry: params.expiryMs } : void 0
|
|
2513
|
+
);
|
|
2514
|
+
return result.delegation;
|
|
2515
|
+
} catch (err) {
|
|
2516
|
+
if (err instanceof PermissionNotInManifestError) {
|
|
2517
|
+
} else {
|
|
2518
|
+
throw err;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
return this.createDelegationWalletPath({
|
|
2522
|
+
...params,
|
|
2523
|
+
delegateDID: resolvedDelegateDID
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Legacy wallet-signed SIWE delegation path. Lifted from the original
|
|
2528
|
+
* `createDelegation` body verbatim so both the legacy public method and
|
|
2529
|
+
* `delegateTo({ forceWalletSign: true })` hit the same code.
|
|
2530
|
+
*
|
|
2531
|
+
* @internal
|
|
2532
|
+
*/
|
|
2533
|
+
async createDelegationWalletPath(params) {
|
|
2131
2534
|
if (!this.signer) {
|
|
2132
2535
|
throw new Error("Cannot createDelegation() in session-only mode. Requires wallet mode.");
|
|
2133
2536
|
}
|
|
@@ -2135,11 +2538,6 @@ var TinyCloudNode = class _TinyCloudNode {
|
|
|
2135
2538
|
if (!session) {
|
|
2136
2539
|
throw new Error("Not signed in. Call signIn() first.");
|
|
2137
2540
|
}
|
|
2138
|
-
if (params.delegateDID.endsWith(".eth") && this.config.ensResolver) {
|
|
2139
|
-
const address = await this.config.ensResolver.resolveAddress(params.delegateDID);
|
|
2140
|
-
if (!address) throw new Error(`Could not resolve ENS name: ${params.delegateDID}`);
|
|
2141
|
-
params = { ...params, delegateDID: `did:pkh:eip155:1:${address}` };
|
|
2142
|
-
}
|
|
2143
2541
|
const abilities = {};
|
|
2144
2542
|
const kvActions = params.actions.filter((a) => a.startsWith("tinycloud.kv/"));
|
|
2145
2543
|
const sqlActions = params.actions.filter((a) => a.startsWith("tinycloud.sql/"));
|
|
@@ -2427,6 +2825,31 @@ var TinyCloudNode = class _TinyCloudNode {
|
|
|
2427
2825
|
};
|
|
2428
2826
|
}
|
|
2429
2827
|
};
|
|
2828
|
+
// ===========================================================================
|
|
2829
|
+
// Capability-chain delegation (spec: .claude/specs/capability-chain.md)
|
|
2830
|
+
// ===========================================================================
|
|
2831
|
+
/**
|
|
2832
|
+
* Safety margin before the session's own expiry at which {@link delegateTo}
|
|
2833
|
+
* will refuse to issue a derived delegation. Prevents issuing sub-delegations
|
|
2834
|
+
* that would be invalid by the time the recipient used them. Spec: 60 seconds.
|
|
2835
|
+
*
|
|
2836
|
+
* @internal
|
|
2837
|
+
*/
|
|
2838
|
+
_TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS = 6e4;
|
|
2839
|
+
var TinyCloudNode = _TinyCloudNode;
|
|
2840
|
+
|
|
2841
|
+
// src/core.ts
|
|
2842
|
+
import {
|
|
2843
|
+
PermissionNotInManifestError as PermissionNotInManifestError2,
|
|
2844
|
+
SessionExpiredError as SessionExpiredError2,
|
|
2845
|
+
ManifestValidationError,
|
|
2846
|
+
resolveManifest as resolveManifest2,
|
|
2847
|
+
validateManifest,
|
|
2848
|
+
loadManifest,
|
|
2849
|
+
isCapabilitySubset as isCapabilitySubset2,
|
|
2850
|
+
expandActionShortNames as expandActionShortNames2,
|
|
2851
|
+
parseExpiry as parseExpiry2
|
|
2852
|
+
} from "@tinycloud/sdk-core";
|
|
2430
2853
|
|
|
2431
2854
|
// src/delegation.ts
|
|
2432
2855
|
function serializeDelegation(delegation) {
|
|
@@ -2490,13 +2913,16 @@ export {
|
|
|
2490
2913
|
DuckDbService3 as DuckDbService,
|
|
2491
2914
|
FileSessionStorage,
|
|
2492
2915
|
KVService3 as KVService,
|
|
2916
|
+
ManifestValidationError,
|
|
2493
2917
|
MemorySessionStorage,
|
|
2494
2918
|
NodeUserAuthorization,
|
|
2919
|
+
PermissionNotInManifestError2 as PermissionNotInManifestError,
|
|
2495
2920
|
PrefixedKVService,
|
|
2496
2921
|
ProtocolMismatchError,
|
|
2497
2922
|
SQLAction,
|
|
2498
2923
|
SQLService3 as SQLService,
|
|
2499
2924
|
ServiceContext3 as ServiceContext,
|
|
2925
|
+
SessionExpiredError2 as SessionExpiredError,
|
|
2500
2926
|
SharingService2 as SharingService,
|
|
2501
2927
|
SilentNotificationHandler2 as SilentNotificationHandler,
|
|
2502
2928
|
Space,
|
|
@@ -2519,8 +2945,14 @@ export {
|
|
|
2519
2945
|
defaultSignStrategy,
|
|
2520
2946
|
defaultSpaceCreationHandler,
|
|
2521
2947
|
deserializeDelegation,
|
|
2948
|
+
expandActionShortNames2 as expandActionShortNames,
|
|
2949
|
+
isCapabilitySubset2 as isCapabilitySubset,
|
|
2950
|
+
loadManifest,
|
|
2522
2951
|
makePublicSpaceId2 as makePublicSpaceId,
|
|
2952
|
+
parseExpiry2 as parseExpiry,
|
|
2523
2953
|
parseSpaceUri,
|
|
2524
|
-
|
|
2954
|
+
resolveManifest2 as resolveManifest,
|
|
2955
|
+
serializeDelegation,
|
|
2956
|
+
validateManifest
|
|
2525
2957
|
};
|
|
2526
2958
|
//# sourceMappingURL=core.js.map
|