@tinycloud/node-sdk 2.2.0-beta.7 → 2.2.0-beta.9

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.js CHANGED
@@ -972,7 +972,7 @@ import {
972
972
  ACCOUNT_REGISTRY_SPACE,
973
973
  PermissionNotInManifestError,
974
974
  SessionExpiredError,
975
- expandActionShortNames,
975
+ expandPermissionEntries as expandPermissionEntriesCore,
976
976
  isCapabilitySubset,
977
977
  parseRecapCapabilities,
978
978
  SERVICE_LONG_TO_SHORT
@@ -1240,49 +1240,29 @@ function secretsError(code, message, cause) {
1240
1240
  }
1241
1241
  };
1242
1242
  }
1243
- function actionUrn(action) {
1244
- return `tinycloud.kv/${action}`;
1243
+ function displayActionUrn(action) {
1244
+ return action === "put" ? "tinycloud.vault/write" : "tinycloud.vault/delete";
1245
1245
  }
1246
- function secretResourcePath(base, name) {
1247
- return `${base}/${SECRET_PREFIX}${name}`;
1246
+ function kvActionUrn(action) {
1247
+ return `tinycloud.kv/${action}`;
1248
1248
  }
1249
1249
  function secretPermissionEntries(name, action) {
1250
1250
  return [
1251
1251
  {
1252
- service: "tinycloud.kv",
1253
- space: SECRETS_SPACE,
1254
- path: secretResourcePath("keys", name),
1255
- actions: [action],
1256
- skipPrefix: true
1257
- },
1258
- {
1259
- service: "tinycloud.kv",
1252
+ service: "tinycloud.vault",
1260
1253
  space: SECRETS_SPACE,
1261
- path: secretResourcePath("vault", name),
1262
- actions: [action],
1254
+ path: `${SECRET_PREFIX}${name}`,
1255
+ actions: [action === "put" ? "write" : "delete"],
1263
1256
  skipPrefix: true
1264
1257
  }
1265
1258
  ];
1266
1259
  }
1260
+ function secretResourcePath(base, name) {
1261
+ return `${base}/${SECRET_PREFIX}${name}`;
1262
+ }
1267
1263
  function isSecretsSpace(space) {
1268
1264
  return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
1269
1265
  }
1270
- function composeEscalatedManifest(manifest, additional) {
1271
- if (Array.isArray(manifest)) {
1272
- const [primary, ...rest] = manifest;
1273
- return [
1274
- {
1275
- ...primary,
1276
- permissions: [...primary.permissions ?? [], ...additional]
1277
- },
1278
- ...rest
1279
- ];
1280
- }
1281
- return {
1282
- ...manifest,
1283
- permissions: [...manifest.permissions ?? [], ...additional]
1284
- };
1285
- }
1286
1266
  var NodeSecretsService = class {
1287
1267
  constructor(config) {
1288
1268
  this.config = config;
@@ -1295,10 +1275,11 @@ var NodeSecretsService = class {
1295
1275
  return this.service.isUnlocked;
1296
1276
  }
1297
1277
  async unlock(signer) {
1298
- if (signer !== void 0) {
1299
- this.unlockSigner = signer;
1278
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
1279
+ if (effectiveSigner !== void 0) {
1280
+ this.unlockSigner = effectiveSigner;
1300
1281
  }
1301
- const result = await this.service.unlock(signer);
1282
+ const result = await this.service.unlock(effectiveSigner);
1302
1283
  if (result.ok) {
1303
1284
  this.shouldRestoreUnlock = true;
1304
1285
  }
@@ -1340,29 +1321,16 @@ var NodeSecretsService = class {
1340
1321
  if (!this.config.canEscalate()) {
1341
1322
  return secretsError(
1342
1323
  ErrorCodes.PERMISSION_DENIED,
1343
- `Cannot autosign ${actionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1344
- );
1345
- }
1346
- const manifest = this.config.getManifest();
1347
- if (manifest === void 0) {
1348
- return secretsError(
1349
- ErrorCodes.PERMISSION_DENIED,
1350
- `Cannot autosign ${actionUrn(action)} for ${name}; set a manifest before mutating secrets.`
1324
+ `Cannot autosign ${displayActionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1351
1325
  );
1352
1326
  }
1353
1327
  try {
1354
- this.config.setManifest(
1355
- composeEscalatedManifest(
1356
- manifest,
1357
- secretPermissionEntries(name, action)
1358
- )
1359
- );
1360
- await this.config.signIn();
1328
+ await this.config.grantPermissions(secretPermissionEntries(name, action));
1361
1329
  return this.restoreUnlockAfterEscalation();
1362
1330
  } catch (error) {
1363
1331
  return secretsError(
1364
1332
  ErrorCodes.PERMISSION_DENIED,
1365
- error instanceof Error ? error.message : `Autosign escalation for ${actionUrn(action)} on ${name} failed.`,
1333
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${name} failed.`,
1366
1334
  error instanceof Error ? error : void 0
1367
1335
  );
1368
1336
  }
@@ -1379,7 +1347,7 @@ var NodeSecretsService = class {
1379
1347
  return false;
1380
1348
  }
1381
1349
  const manifests = Array.isArray(manifest) ? manifest : [manifest];
1382
- const requiredAction = actionUrn(action);
1350
+ const requiredAction = kvActionUrn(action);
1383
1351
  return manifests.some((entry) => {
1384
1352
  const resolved = resolveManifest(entry);
1385
1353
  return ["keys", "vault"].every(
@@ -1422,6 +1390,30 @@ var _TinyCloudNode = class _TinyCloudNode {
1422
1390
  this.auth = null;
1423
1391
  this.tc = null;
1424
1392
  this._chainId = 1;
1393
+ this.runtimePermissionGrants = [];
1394
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
1395
+ return this.wasmBindings.invoke(
1396
+ this.selectInvocationSession(session, service, path, action),
1397
+ service,
1398
+ path,
1399
+ action,
1400
+ facts
1401
+ );
1402
+ };
1403
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
1404
+ if (!this.wasmBindings.invokeAny) {
1405
+ throw new Error("WASM binding does not support invokeAny");
1406
+ }
1407
+ const grant = this.findGrantForOperations(
1408
+ entries.map((entry) => ({
1409
+ spaceId: entry.spaceId,
1410
+ service: this.invocationServiceName(entry.service),
1411
+ path: entry.path,
1412
+ action: entry.action
1413
+ }))
1414
+ );
1415
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
1416
+ };
1425
1417
  this.explicitHost = config.host;
1426
1418
  this.config = {
1427
1419
  ...config,
@@ -1457,7 +1449,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1457
1449
  this._sharingService = new SharingService({
1458
1450
  hosts: [this.config.host],
1459
1451
  // session: undefined - not needed for receive()
1460
- invoke: this.wasmBindings.invoke,
1452
+ invoke: this.invokeWithRuntimePermissions,
1461
1453
  fetch: globalThis.fetch.bind(globalThis),
1462
1454
  keyProvider: this._keyProvider,
1463
1455
  registry: this._capabilityRegistry,
@@ -1525,7 +1517,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1525
1517
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
1526
1518
  });
1527
1519
  this.tc = new TinyCloud(this.auth, {
1528
- invokeAny: this.wasmBindings.invokeAny
1520
+ invokeAny: this.invokeAnyWithRuntimePermissions
1529
1521
  });
1530
1522
  }
1531
1523
  syncResolvedHostFromAuth() {
@@ -1650,6 +1642,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1650
1642
  this._secrets = void 0;
1651
1643
  this._spaceService = void 0;
1652
1644
  this._serviceContext = void 0;
1645
+ this.runtimePermissionGrants = [];
1653
1646
  await this.tc.signIn(options);
1654
1647
  this.syncResolvedHostFromAuth();
1655
1648
  this.initializeServices();
@@ -1739,6 +1732,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1739
1732
  this._secrets = void 0;
1740
1733
  this._spaceService = void 0;
1741
1734
  this._serviceContext = void 0;
1735
+ this.runtimePermissionGrants = [];
1742
1736
  if (sessionData.address) {
1743
1737
  this._address = sessionData.address;
1744
1738
  }
@@ -1746,8 +1740,8 @@ var _TinyCloudNode = class _TinyCloudNode {
1746
1740
  this._chainId = sessionData.chainId;
1747
1741
  }
1748
1742
  this._serviceContext = new ServiceContext2({
1749
- invoke: this.wasmBindings.invoke,
1750
- invokeAny: this.wasmBindings.invokeAny,
1743
+ invoke: this.invokeWithRuntimePermissions,
1744
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1751
1745
  fetch: globalThis.fetch.bind(globalThis),
1752
1746
  hosts: [this.config.host]
1753
1747
  });
@@ -1833,7 +1827,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1833
1827
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1834
1828
  });
1835
1829
  this.tc = new TinyCloud(this.auth, {
1836
- invokeAny: this.wasmBindings.invokeAny
1830
+ invokeAny: this.invokeAnyWithRuntimePermissions
1837
1831
  });
1838
1832
  this.config.prefix = prefix;
1839
1833
  }
@@ -1877,7 +1871,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1877
1871
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1878
1872
  });
1879
1873
  this.tc = new TinyCloud(this.auth, {
1880
- invokeAny: this.wasmBindings.invokeAny
1874
+ invokeAny: this.invokeAnyWithRuntimePermissions
1881
1875
  });
1882
1876
  this.config.prefix = prefix;
1883
1877
  }
@@ -1890,10 +1884,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1890
1884
  if (!session) {
1891
1885
  return;
1892
1886
  }
1893
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
1887
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
1894
1888
  this._serviceContext = new ServiceContext2({
1895
- invoke: this.wasmBindings.invoke,
1896
- invokeAny: this.wasmBindings.invokeAny,
1889
+ invoke: this.invokeWithRuntimePermissions,
1890
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1897
1891
  fetch: globalThis.fetch.bind(globalThis),
1898
1892
  hosts: [this.config.host]
1899
1893
  });
@@ -2063,7 +2057,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2063
2057
  this._delegationManager = new DelegationManager({
2064
2058
  hosts: [this.config.host],
2065
2059
  session: serviceSession,
2066
- invoke: this.wasmBindings.invoke,
2060
+ invoke: this.invokeWithRuntimePermissions,
2067
2061
  fetch: globalThis.fetch.bind(globalThis)
2068
2062
  });
2069
2063
  this._spaceService = new SpaceService({
@@ -2330,9 +2324,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2330
2324
  this._secrets = new NodeSecretsService({
2331
2325
  getService: () => this.getBaseSecrets(),
2332
2326
  getManifest: () => this.manifest,
2333
- setManifest: (manifest) => this.setManifest(manifest),
2334
- signIn: () => this.signIn(),
2335
- canEscalate: () => this.signer !== void 0 && this.tc !== void 0
2327
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2328
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2329
+ getUnlockSigner: () => this.signer ?? void 0
2336
2330
  });
2337
2331
  }
2338
2332
  return this._secrets;
@@ -2416,6 +2410,171 @@ var _TinyCloudNode = class _TinyCloudNode {
2416
2410
  }
2417
2411
  };
2418
2412
  }
2413
+ /**
2414
+ * Check whether the current session or an approved runtime delegation covers
2415
+ * every requested permission.
2416
+ */
2417
+ hasRuntimePermissions(permissions) {
2418
+ const session = this.auth?.tinyCloudSession;
2419
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2420
+ return false;
2421
+ }
2422
+ const expanded = this.expandPermissionEntries(permissions);
2423
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2424
+ return true;
2425
+ }
2426
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
2427
+ }
2428
+ /**
2429
+ * Return installed runtime permission delegations. When `permissions` is
2430
+ * provided, only delegations currently covering those permissions are
2431
+ * returned. Base-session manifest permissions are not represented here.
2432
+ */
2433
+ getRuntimePermissionDelegations(permissions) {
2434
+ this.pruneExpiredRuntimePermissionGrants();
2435
+ if (permissions === void 0) {
2436
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
2437
+ }
2438
+ const session = this.auth?.tinyCloudSession;
2439
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2440
+ return [];
2441
+ }
2442
+ const expanded = this.expandPermissionEntries(permissions);
2443
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
2444
+ (grant) => grant.delegation
2445
+ );
2446
+ }
2447
+ /**
2448
+ * Install a portable runtime permission delegation into this SDK instance so
2449
+ * matching service calls and downstream `delegateTo()` calls can use it.
2450
+ */
2451
+ async useRuntimeDelegation(delegation) {
2452
+ const session = this.auth?.tinyCloudSession;
2453
+ if (!session) {
2454
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2455
+ }
2456
+ if (delegation.expiry.getTime() <= Date.now()) {
2457
+ throw new SessionExpiredError(delegation.expiry);
2458
+ }
2459
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
2460
+ if (!expectedDids.has(delegation.delegateDID)) {
2461
+ throw new Error(
2462
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
2463
+ );
2464
+ }
2465
+ const targetHost = delegation.host ?? this.config.host;
2466
+ const activateResult = await activateSessionWithHost2(
2467
+ targetHost,
2468
+ delegation.delegationHeader
2469
+ );
2470
+ if (!activateResult.success) {
2471
+ throw new Error(
2472
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2473
+ );
2474
+ }
2475
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
2476
+ (grant) => grant.delegation.cid !== delegation.cid
2477
+ );
2478
+ this.runtimePermissionGrants.push(
2479
+ this.runtimeGrantFromDelegation(delegation, session)
2480
+ );
2481
+ }
2482
+ /**
2483
+ * Store additional permissions as narrow delegations to the current session
2484
+ * key. Future service invocations automatically use a stored delegation when
2485
+ * its `(space, service, path, action)` covers the request.
2486
+ */
2487
+ async grantRuntimePermissions(permissions, options) {
2488
+ if (!Array.isArray(permissions) || permissions.length === 0) {
2489
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2490
+ }
2491
+ const session = this.auth?.tinyCloudSession;
2492
+ if (!session) {
2493
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2494
+ }
2495
+ const sessionExpiry = extractSiweExpiration(session.siwe);
2496
+ if (sessionExpiry !== void 0) {
2497
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2498
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
2499
+ throw new SessionExpiredError(sessionExpiry);
2500
+ }
2501
+ }
2502
+ const expanded = this.expandPermissionEntries(permissions);
2503
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2504
+ return [];
2505
+ }
2506
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
2507
+ if (existingGrants.length > 0) {
2508
+ return existingGrants.map((grant) => grant.delegation);
2509
+ }
2510
+ if (!this.signer) {
2511
+ throw new Error(
2512
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2513
+ );
2514
+ }
2515
+ const bySpace = /* @__PURE__ */ new Map();
2516
+ for (const entry of expanded) {
2517
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
2518
+ const current = bySpace.get(spaceId) ?? [];
2519
+ current.push(entry);
2520
+ bySpace.set(spaceId, current);
2521
+ }
2522
+ const now = /* @__PURE__ */ new Date();
2523
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2524
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
2525
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
2526
+ expiresAt = sessionExpiry;
2527
+ }
2528
+ const delegations = [];
2529
+ for (const [spaceId, entries] of bySpace) {
2530
+ const abilities = this.permissionsToAbilities(entries);
2531
+ const prepared = this.wasmBindings.prepareSession({
2532
+ abilities,
2533
+ address: this.wasmBindings.ensureEip55(session.address),
2534
+ chainId: session.chainId,
2535
+ domain: this.siweDomain,
2536
+ issuedAt: now.toISOString(),
2537
+ expirationTime: expiresAt.toISOString(),
2538
+ spaceId,
2539
+ jwk: session.jwk
2540
+ });
2541
+ const signature = await this.signer.signMessage(prepared.siwe);
2542
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
2543
+ ...prepared,
2544
+ signature
2545
+ });
2546
+ const activateResult = await activateSessionWithHost2(
2547
+ this.config.host,
2548
+ delegatedSession.delegationHeader
2549
+ );
2550
+ if (!activateResult.success) {
2551
+ throw new Error(
2552
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2553
+ );
2554
+ }
2555
+ const delegation = this.runtimeDelegationFromSession(
2556
+ delegatedSession,
2557
+ entries,
2558
+ spaceId,
2559
+ session,
2560
+ expiresAt
2561
+ );
2562
+ this.runtimePermissionGrants.push({
2563
+ session: {
2564
+ delegationHeader: delegatedSession.delegationHeader,
2565
+ delegationCid: delegatedSession.delegationCid,
2566
+ spaceId,
2567
+ verificationMethod: session.verificationMethod,
2568
+ jwk: session.jwk
2569
+ },
2570
+ delegation,
2571
+ operations: this.permissionOperations(entries, spaceId),
2572
+ expiresAt
2573
+ });
2574
+ delegations.push(delegation);
2575
+ }
2576
+ return delegations;
2577
+ }
2419
2578
  /**
2420
2579
  * Get the DelegationManager for delegation CRUD operations.
2421
2580
  *
@@ -2604,7 +2763,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2604
2763
  if (this._serviceContext) {
2605
2764
  const publicKV = new KVService2({ prefix: "" });
2606
2765
  const publicContext = new ServiceContext2({
2607
- invoke: this.wasmBindings.invoke,
2766
+ invoke: this.invokeWithRuntimePermissions,
2608
2767
  fetch: this._serviceContext.fetch,
2609
2768
  hosts: this._serviceContext.hosts
2610
2769
  });
@@ -2692,8 +2851,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2692
2851
  * Issue a delegation using the capability-chain flow.
2693
2852
  *
2694
2853
  * When every requested permission is a subset of the current
2695
- * session's recap, the delegation is signed by the session key via
2696
- * WASM no wallet prompt. When at least one is NOT derivable, a
2854
+ * session's recap, or of one installed runtime permission delegation,
2855
+ * the delegation is signed by the session key via WASM no wallet
2856
+ * prompt. When at least one is NOT derivable, a
2697
2857
  * {@link PermissionNotInManifestError} is raised (carrying the
2698
2858
  * missing entries) so the caller can trigger an escalation flow
2699
2859
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -2743,10 +2903,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2743
2903
  "delegateTo requires a non-empty permissions array"
2744
2904
  );
2745
2905
  }
2746
- const expandedEntries = permissions.map((entry) => ({
2747
- ...entry,
2748
- actions: expandActionShortNames(entry.service, entry.actions)
2749
- }));
2906
+ const expandedEntries = this.expandPermissionEntries(permissions);
2750
2907
  const now = /* @__PURE__ */ new Date();
2751
2908
  const expiryMs = resolveExpiryMs(options?.expiry);
2752
2909
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -2773,6 +2930,23 @@ var _TinyCloudNode = class _TinyCloudNode {
2773
2930
  );
2774
2931
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
2775
2932
  if (!subset) {
2933
+ const runtimeGrant = this.findGrantForOperations(
2934
+ this.permissionEntriesToOperations(expandedEntries, session)
2935
+ );
2936
+ if (runtimeGrant) {
2937
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2938
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
2939
+ throw new SessionExpiredError(runtimeGrant.expiresAt);
2940
+ }
2941
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
2942
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
2943
+ did,
2944
+ expandedEntries,
2945
+ runtimeExpiration,
2946
+ runtimeGrant
2947
+ );
2948
+ return { delegation: delegation2, prompted: false };
2949
+ }
2776
2950
  throw new PermissionNotInManifestError(missing, granted);
2777
2951
  }
2778
2952
  const delegation = await this.createDelegationViaWasmPath(
@@ -2917,6 +3091,41 @@ var _TinyCloudNode = class _TinyCloudNode {
2917
3091
  host: this.config.host
2918
3092
  };
2919
3093
  }
3094
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
3095
+ const result = this.createDelegationWrapper({
3096
+ session: grant.session,
3097
+ delegateDID: did,
3098
+ spaceId: grant.session.spaceId,
3099
+ abilities: this.permissionsToAbilities(entries),
3100
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
3101
+ });
3102
+ const primary = result.resources[0];
3103
+ const delegationHeader = { Authorization: result.delegation };
3104
+ const targetHost = grant.delegation.host ?? this.config.host;
3105
+ const activateResult = await activateSessionWithHost2(
3106
+ targetHost,
3107
+ delegationHeader
3108
+ );
3109
+ if (!activateResult.success) {
3110
+ throw new Error(
3111
+ `Failed to activate delegation with host: ${activateResult.error}`
3112
+ );
3113
+ }
3114
+ return {
3115
+ cid: result.cid,
3116
+ delegationHeader,
3117
+ spaceId: grant.session.spaceId,
3118
+ path: primary.path,
3119
+ actions: primary.actions,
3120
+ resources: result.resources,
3121
+ disableSubDelegation: false,
3122
+ expiry: result.expiry,
3123
+ delegateDID: did,
3124
+ ownerAddress: grant.delegation.ownerAddress,
3125
+ chainId: grant.delegation.chainId,
3126
+ host: targetHost
3127
+ };
3128
+ }
2920
3129
  resolvePermissionSpace(space, session) {
2921
3130
  if (space === void 0) {
2922
3131
  return this.wasmBindings.makeSpaceId(
@@ -2933,6 +3142,220 @@ var _TinyCloudNode = class _TinyCloudNode {
2933
3142
  }
2934
3143
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
2935
3144
  }
3145
+ expandPermissionEntries(permissions) {
3146
+ return expandPermissionEntriesCore(permissions);
3147
+ }
3148
+ shortServiceName(service) {
3149
+ const short = SERVICE_LONG_TO_SHORT[service];
3150
+ if (short === void 0) {
3151
+ throw new Error(
3152
+ `unknown service '${service}' \u2014 no short-form mapping`
3153
+ );
3154
+ }
3155
+ return short;
3156
+ }
3157
+ permissionsToAbilities(entries) {
3158
+ const abilities = {};
3159
+ for (const entry of entries) {
3160
+ const service = this.shortServiceName(entry.service);
3161
+ abilities[service] ?? (abilities[service] = {});
3162
+ const existing = abilities[service][entry.path] ?? [];
3163
+ const seen = new Set(existing);
3164
+ for (const action of entry.actions) {
3165
+ if (!seen.has(action)) {
3166
+ existing.push(action);
3167
+ seen.add(action);
3168
+ }
3169
+ }
3170
+ abilities[service][entry.path] = existing;
3171
+ }
3172
+ return abilities;
3173
+ }
3174
+ permissionOperations(entries, spaceId) {
3175
+ return entries.flatMap((entry) => {
3176
+ const service = this.shortServiceName(entry.service);
3177
+ return entry.actions.map((action) => ({
3178
+ spaceId,
3179
+ service,
3180
+ path: entry.path,
3181
+ action
3182
+ }));
3183
+ });
3184
+ }
3185
+ sessionCoversPermissionEntries(session, entries) {
3186
+ try {
3187
+ const granted = parseRecapCapabilities(
3188
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
3189
+ session.siwe
3190
+ );
3191
+ return isCapabilitySubset(entries, granted).subset;
3192
+ } catch {
3193
+ return false;
3194
+ }
3195
+ }
3196
+ permissionEntriesToOperations(entries, session) {
3197
+ return entries.flatMap((entry) => {
3198
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
3199
+ const service = this.shortServiceName(entry.service);
3200
+ return entry.actions.map((action) => ({
3201
+ spaceId,
3202
+ service,
3203
+ path: entry.path,
3204
+ action
3205
+ }));
3206
+ });
3207
+ }
3208
+ findRuntimeGrantsForPermissionEntries(entries, session) {
3209
+ const grants = [];
3210
+ const operations = this.permissionEntriesToOperations(entries, session);
3211
+ if (operations.length === 0) {
3212
+ return grants;
3213
+ }
3214
+ for (const operation of operations) {
3215
+ const grant = this.findGrantForOperation(operation);
3216
+ if (!grant) {
3217
+ return [];
3218
+ }
3219
+ if (!grants.includes(grant)) {
3220
+ grants.push(grant);
3221
+ }
3222
+ }
3223
+ return grants;
3224
+ }
3225
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
3226
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
3227
+ const primary = resources[0];
3228
+ return {
3229
+ cid: delegatedSession.delegationCid,
3230
+ delegationHeader: delegatedSession.delegationHeader,
3231
+ spaceId,
3232
+ path: primary.path,
3233
+ actions: primary.actions,
3234
+ resources,
3235
+ disableSubDelegation: false,
3236
+ expiry: expiresAt,
3237
+ delegateDID: session.verificationMethod,
3238
+ ownerAddress: session.address,
3239
+ chainId: session.chainId,
3240
+ host: this.config.host
3241
+ };
3242
+ }
3243
+ runtimeGrantFromDelegation(delegation, session) {
3244
+ const operations = this.operationsFromDelegation(delegation);
3245
+ return {
3246
+ session: {
3247
+ delegationHeader: delegation.delegationHeader,
3248
+ delegationCid: delegation.cid,
3249
+ spaceId: delegation.spaceId,
3250
+ verificationMethod: session.verificationMethod,
3251
+ jwk: session.jwk
3252
+ },
3253
+ delegation,
3254
+ operations,
3255
+ expiresAt: delegation.expiry
3256
+ };
3257
+ }
3258
+ delegatedResourcesForEntries(entries, spaceId) {
3259
+ return entries.map((entry) => ({
3260
+ service: this.shortServiceName(entry.service),
3261
+ space: spaceId,
3262
+ path: entry.path,
3263
+ actions: [...entry.actions]
3264
+ }));
3265
+ }
3266
+ operationsFromDelegation(delegation) {
3267
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
3268
+ return resources.flatMap(
3269
+ (resource) => resource.actions.map((action) => ({
3270
+ spaceId: resource.space,
3271
+ service: this.invocationServiceName(resource.service),
3272
+ path: resource.path,
3273
+ action
3274
+ }))
3275
+ );
3276
+ }
3277
+ flatDelegationResources(delegation) {
3278
+ const byService = /* @__PURE__ */ new Map();
3279
+ for (const action of delegation.actions) {
3280
+ const service = this.shortServiceName(action.split("/")[0]);
3281
+ const actions = byService.get(service) ?? [];
3282
+ actions.push(action);
3283
+ byService.set(service, actions);
3284
+ }
3285
+ return [...byService.entries()].map(([service, actions]) => ({
3286
+ service,
3287
+ space: delegation.spaceId,
3288
+ path: delegation.path,
3289
+ actions
3290
+ }));
3291
+ }
3292
+ selectInvocationSession(fallback, service, path, action) {
3293
+ const grant = this.findGrantForOperation({
3294
+ spaceId: fallback.spaceId,
3295
+ service: this.invocationServiceName(service),
3296
+ path,
3297
+ action
3298
+ });
3299
+ return grant?.session ?? fallback;
3300
+ }
3301
+ findGrantForOperations(operations) {
3302
+ if (operations.length === 0) {
3303
+ return void 0;
3304
+ }
3305
+ this.pruneExpiredRuntimePermissionGrants();
3306
+ return this.runtimePermissionGrants.find((grant) => {
3307
+ return operations.every(
3308
+ (operation) => grant.operations.some(
3309
+ (granted) => this.operationCovers(granted, operation)
3310
+ )
3311
+ );
3312
+ });
3313
+ }
3314
+ findGrantForOperation(operation) {
3315
+ return this.findGrantForOperations([operation]);
3316
+ }
3317
+ pruneExpiredRuntimePermissionGrants() {
3318
+ const now = Date.now();
3319
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
3320
+ (grant) => grant.expiresAt.getTime() > now
3321
+ );
3322
+ }
3323
+ operationCovers(granted, requested) {
3324
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
3325
+ }
3326
+ actionContains(grantedAction, requestedAction) {
3327
+ if (grantedAction === requestedAction) {
3328
+ return true;
3329
+ }
3330
+ if (grantedAction.endsWith("/*")) {
3331
+ const prefix = grantedAction.slice(0, -2);
3332
+ return requestedAction.startsWith(`${prefix}/`);
3333
+ }
3334
+ return false;
3335
+ }
3336
+ invocationServiceName(service) {
3337
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3338
+ }
3339
+ pathContains(grantedPath, requestedPath) {
3340
+ if (grantedPath === "" || grantedPath === "/") {
3341
+ return true;
3342
+ }
3343
+ if (grantedPath.endsWith("/**")) {
3344
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
3345
+ }
3346
+ if (grantedPath.endsWith("/*")) {
3347
+ const prefix = grantedPath.slice(0, -2);
3348
+ if (!requestedPath.startsWith(prefix)) {
3349
+ return false;
3350
+ }
3351
+ const remainder = requestedPath.slice(prefix.length);
3352
+ return !remainder.includes("/") || remainder === "/";
3353
+ }
3354
+ if (grantedPath.endsWith("/")) {
3355
+ return requestedPath.startsWith(grantedPath);
3356
+ }
3357
+ return grantedPath === requestedPath;
3358
+ }
2936
3359
  /**
2937
3360
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
2938
3361
  * {@link PermissionEntry}. Shares the implementation with the public
@@ -3320,6 +3743,7 @@ import {
3320
3743
  ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
3321
3744
  DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
3322
3745
  DEFAULT_MANIFEST_VERSION,
3746
+ VAULT_PERMISSION_SERVICE,
3323
3747
  PermissionNotInManifestError as PermissionNotInManifestError2,
3324
3748
  SessionExpiredError as SessionExpiredError2,
3325
3749
  ManifestValidationError,
@@ -3328,7 +3752,9 @@ import {
3328
3752
  validateManifest,
3329
3753
  loadManifest,
3330
3754
  isCapabilitySubset as isCapabilitySubset2,
3331
- expandActionShortNames as expandActionShortNames2,
3755
+ expandActionShortNames,
3756
+ expandPermissionEntries,
3757
+ expandPermissionEntry,
3332
3758
  parseExpiry as parseExpiry2,
3333
3759
  resourceCapabilitiesToSpaceAbilitiesMap as resourceCapabilitiesToSpaceAbilitiesMap2
3334
3760
  } from "@tinycloud/sdk-core";
@@ -3418,6 +3844,7 @@ export {
3418
3844
  TinyCloud2 as TinyCloud,
3419
3845
  TinyCloudNode,
3420
3846
  UnsupportedFeatureError2 as UnsupportedFeatureError,
3847
+ VAULT_PERMISSION_SERVICE,
3421
3848
  VaultHeaders,
3422
3849
  VaultPublicSpaceKVActions,
3423
3850
  VersionCheckError,
@@ -3433,7 +3860,9 @@ export {
3433
3860
  defaultSignStrategy,
3434
3861
  defaultSpaceCreationHandler,
3435
3862
  deserializeDelegation,
3436
- expandActionShortNames2 as expandActionShortNames,
3863
+ expandActionShortNames,
3864
+ expandPermissionEntries,
3865
+ expandPermissionEntry,
3437
3866
  isCapabilitySubset2 as isCapabilitySubset,
3438
3867
  loadManifest,
3439
3868
  makePublicSpaceId2 as makePublicSpaceId,