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

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 CHANGED
@@ -1299,22 +1299,6 @@ function secretPermissionEntries(name, action) {
1299
1299
  function isSecretsSpace(space) {
1300
1300
  return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
1301
1301
  }
1302
- function composeEscalatedManifest(manifest, additional) {
1303
- if (Array.isArray(manifest)) {
1304
- const [primary, ...rest] = manifest;
1305
- return [
1306
- {
1307
- ...primary,
1308
- permissions: [...primary.permissions ?? [], ...additional]
1309
- },
1310
- ...rest
1311
- ];
1312
- }
1313
- return {
1314
- ...manifest,
1315
- permissions: [...manifest.permissions ?? [], ...additional]
1316
- };
1317
- }
1318
1302
  var NodeSecretsService = class {
1319
1303
  constructor(config) {
1320
1304
  this.config = config;
@@ -1327,10 +1311,11 @@ var NodeSecretsService = class {
1327
1311
  return this.service.isUnlocked;
1328
1312
  }
1329
1313
  async unlock(signer) {
1330
- if (signer !== void 0) {
1331
- this.unlockSigner = signer;
1314
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
1315
+ if (effectiveSigner !== void 0) {
1316
+ this.unlockSigner = effectiveSigner;
1332
1317
  }
1333
- const result = await this.service.unlock(signer);
1318
+ const result = await this.service.unlock(effectiveSigner);
1334
1319
  if (result.ok) {
1335
1320
  this.shouldRestoreUnlock = true;
1336
1321
  }
@@ -1375,21 +1360,8 @@ var NodeSecretsService = class {
1375
1360
  `Cannot autosign ${actionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1376
1361
  );
1377
1362
  }
1378
- const manifest = this.config.getManifest();
1379
- if (manifest === void 0) {
1380
- return secretsError(
1381
- import_sdk_core5.ErrorCodes.PERMISSION_DENIED,
1382
- `Cannot autosign ${actionUrn(action)} for ${name}; set a manifest before mutating secrets.`
1383
- );
1384
- }
1385
1363
  try {
1386
- this.config.setManifest(
1387
- composeEscalatedManifest(
1388
- manifest,
1389
- secretPermissionEntries(name, action)
1390
- )
1391
- );
1392
- await this.config.signIn();
1364
+ await this.config.grantPermissions(secretPermissionEntries(name, action));
1393
1365
  return this.restoreUnlockAfterEscalation();
1394
1366
  } catch (error) {
1395
1367
  return secretsError(
@@ -1454,6 +1426,30 @@ var _TinyCloudNode = class _TinyCloudNode {
1454
1426
  this.auth = null;
1455
1427
  this.tc = null;
1456
1428
  this._chainId = 1;
1429
+ this.runtimePermissionGrants = [];
1430
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
1431
+ return this.wasmBindings.invoke(
1432
+ this.selectInvocationSession(session, service, path, action),
1433
+ service,
1434
+ path,
1435
+ action,
1436
+ facts
1437
+ );
1438
+ };
1439
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
1440
+ if (!this.wasmBindings.invokeAny) {
1441
+ throw new Error("WASM binding does not support invokeAny");
1442
+ }
1443
+ const grant = this.findGrantForOperations(
1444
+ entries.map((entry) => ({
1445
+ spaceId: entry.spaceId,
1446
+ service: this.invocationServiceName(entry.service),
1447
+ path: entry.path,
1448
+ action: entry.action
1449
+ }))
1450
+ );
1451
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
1452
+ };
1457
1453
  this.explicitHost = config.host;
1458
1454
  this.config = {
1459
1455
  ...config,
@@ -1489,7 +1485,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1489
1485
  this._sharingService = new import_sdk_core6.SharingService({
1490
1486
  hosts: [this.config.host],
1491
1487
  // session: undefined - not needed for receive()
1492
- invoke: this.wasmBindings.invoke,
1488
+ invoke: this.invokeWithRuntimePermissions,
1493
1489
  fetch: globalThis.fetch.bind(globalThis),
1494
1490
  keyProvider: this._keyProvider,
1495
1491
  registry: this._capabilityRegistry,
@@ -1557,7 +1553,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1557
1553
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
1558
1554
  });
1559
1555
  this.tc = new import_sdk_core6.TinyCloud(this.auth, {
1560
- invokeAny: this.wasmBindings.invokeAny
1556
+ invokeAny: this.invokeAnyWithRuntimePermissions
1561
1557
  });
1562
1558
  }
1563
1559
  syncResolvedHostFromAuth() {
@@ -1682,6 +1678,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1682
1678
  this._secrets = void 0;
1683
1679
  this._spaceService = void 0;
1684
1680
  this._serviceContext = void 0;
1681
+ this.runtimePermissionGrants = [];
1685
1682
  await this.tc.signIn(options);
1686
1683
  this.syncResolvedHostFromAuth();
1687
1684
  this.initializeServices();
@@ -1771,6 +1768,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1771
1768
  this._secrets = void 0;
1772
1769
  this._spaceService = void 0;
1773
1770
  this._serviceContext = void 0;
1771
+ this.runtimePermissionGrants = [];
1774
1772
  if (sessionData.address) {
1775
1773
  this._address = sessionData.address;
1776
1774
  }
@@ -1778,8 +1776,8 @@ var _TinyCloudNode = class _TinyCloudNode {
1778
1776
  this._chainId = sessionData.chainId;
1779
1777
  }
1780
1778
  this._serviceContext = new import_sdk_core6.ServiceContext({
1781
- invoke: this.wasmBindings.invoke,
1782
- invokeAny: this.wasmBindings.invokeAny,
1779
+ invoke: this.invokeWithRuntimePermissions,
1780
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1783
1781
  fetch: globalThis.fetch.bind(globalThis),
1784
1782
  hosts: [this.config.host]
1785
1783
  });
@@ -1865,7 +1863,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1865
1863
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1866
1864
  });
1867
1865
  this.tc = new import_sdk_core6.TinyCloud(this.auth, {
1868
- invokeAny: this.wasmBindings.invokeAny
1866
+ invokeAny: this.invokeAnyWithRuntimePermissions
1869
1867
  });
1870
1868
  this.config.prefix = prefix;
1871
1869
  }
@@ -1909,7 +1907,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1909
1907
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1910
1908
  });
1911
1909
  this.tc = new import_sdk_core6.TinyCloud(this.auth, {
1912
- invokeAny: this.wasmBindings.invokeAny
1910
+ invokeAny: this.invokeAnyWithRuntimePermissions
1913
1911
  });
1914
1912
  this.config.prefix = prefix;
1915
1913
  }
@@ -1922,10 +1920,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1922
1920
  if (!session) {
1923
1921
  return;
1924
1922
  }
1925
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
1923
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
1926
1924
  this._serviceContext = new import_sdk_core6.ServiceContext({
1927
- invoke: this.wasmBindings.invoke,
1928
- invokeAny: this.wasmBindings.invokeAny,
1925
+ invoke: this.invokeWithRuntimePermissions,
1926
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1929
1927
  fetch: globalThis.fetch.bind(globalThis),
1930
1928
  hosts: [this.config.host]
1931
1929
  });
@@ -2095,7 +2093,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2095
2093
  this._delegationManager = new import_sdk_core6.DelegationManager({
2096
2094
  hosts: [this.config.host],
2097
2095
  session: serviceSession,
2098
- invoke: this.wasmBindings.invoke,
2096
+ invoke: this.invokeWithRuntimePermissions,
2099
2097
  fetch: globalThis.fetch.bind(globalThis)
2100
2098
  });
2101
2099
  this._spaceService = new import_sdk_core6.SpaceService({
@@ -2362,9 +2360,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2362
2360
  this._secrets = new NodeSecretsService({
2363
2361
  getService: () => this.getBaseSecrets(),
2364
2362
  getManifest: () => this.manifest,
2365
- setManifest: (manifest) => this.setManifest(manifest),
2366
- signIn: () => this.signIn(),
2367
- canEscalate: () => this.signer !== void 0 && this.tc !== void 0
2363
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2364
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2365
+ getUnlockSigner: () => this.signer ?? void 0
2368
2366
  });
2369
2367
  }
2370
2368
  return this._secrets;
@@ -2448,6 +2446,171 @@ var _TinyCloudNode = class _TinyCloudNode {
2448
2446
  }
2449
2447
  };
2450
2448
  }
2449
+ /**
2450
+ * Check whether the current session or an approved runtime delegation covers
2451
+ * every requested permission.
2452
+ */
2453
+ hasRuntimePermissions(permissions) {
2454
+ const session = this.auth?.tinyCloudSession;
2455
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2456
+ return false;
2457
+ }
2458
+ const expanded = this.expandPermissionEntries(permissions);
2459
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2460
+ return true;
2461
+ }
2462
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
2463
+ }
2464
+ /**
2465
+ * Return installed runtime permission delegations. When `permissions` is
2466
+ * provided, only delegations currently covering those permissions are
2467
+ * returned. Base-session manifest permissions are not represented here.
2468
+ */
2469
+ getRuntimePermissionDelegations(permissions) {
2470
+ this.pruneExpiredRuntimePermissionGrants();
2471
+ if (permissions === void 0) {
2472
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
2473
+ }
2474
+ const session = this.auth?.tinyCloudSession;
2475
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2476
+ return [];
2477
+ }
2478
+ const expanded = this.expandPermissionEntries(permissions);
2479
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
2480
+ (grant) => grant.delegation
2481
+ );
2482
+ }
2483
+ /**
2484
+ * Install a portable runtime permission delegation into this SDK instance so
2485
+ * matching service calls and downstream `delegateTo()` calls can use it.
2486
+ */
2487
+ async useRuntimeDelegation(delegation) {
2488
+ const session = this.auth?.tinyCloudSession;
2489
+ if (!session) {
2490
+ throw new import_sdk_core6.SessionExpiredError(/* @__PURE__ */ new Date(0));
2491
+ }
2492
+ if (delegation.expiry.getTime() <= Date.now()) {
2493
+ throw new import_sdk_core6.SessionExpiredError(delegation.expiry);
2494
+ }
2495
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
2496
+ if (!expectedDids.has(delegation.delegateDID)) {
2497
+ throw new Error(
2498
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
2499
+ );
2500
+ }
2501
+ const targetHost = delegation.host ?? this.config.host;
2502
+ const activateResult = await (0, import_sdk_core6.activateSessionWithHost)(
2503
+ targetHost,
2504
+ delegation.delegationHeader
2505
+ );
2506
+ if (!activateResult.success) {
2507
+ throw new Error(
2508
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2509
+ );
2510
+ }
2511
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
2512
+ (grant) => grant.delegation.cid !== delegation.cid
2513
+ );
2514
+ this.runtimePermissionGrants.push(
2515
+ this.runtimeGrantFromDelegation(delegation, session)
2516
+ );
2517
+ }
2518
+ /**
2519
+ * Store additional permissions as narrow delegations to the current session
2520
+ * key. Future service invocations automatically use a stored delegation when
2521
+ * its `(space, service, path, action)` covers the request.
2522
+ */
2523
+ async grantRuntimePermissions(permissions, options) {
2524
+ if (!Array.isArray(permissions) || permissions.length === 0) {
2525
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2526
+ }
2527
+ const session = this.auth?.tinyCloudSession;
2528
+ if (!session) {
2529
+ throw new import_sdk_core6.SessionExpiredError(/* @__PURE__ */ new Date(0));
2530
+ }
2531
+ const sessionExpiry = extractSiweExpiration(session.siwe);
2532
+ if (sessionExpiry !== void 0) {
2533
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2534
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
2535
+ throw new import_sdk_core6.SessionExpiredError(sessionExpiry);
2536
+ }
2537
+ }
2538
+ const expanded = this.expandPermissionEntries(permissions);
2539
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2540
+ return [];
2541
+ }
2542
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
2543
+ if (existingGrants.length > 0) {
2544
+ return existingGrants.map((grant) => grant.delegation);
2545
+ }
2546
+ if (!this.signer) {
2547
+ throw new Error(
2548
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2549
+ );
2550
+ }
2551
+ const bySpace = /* @__PURE__ */ new Map();
2552
+ for (const entry of expanded) {
2553
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
2554
+ const current = bySpace.get(spaceId) ?? [];
2555
+ current.push(entry);
2556
+ bySpace.set(spaceId, current);
2557
+ }
2558
+ const now = /* @__PURE__ */ new Date();
2559
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2560
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
2561
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
2562
+ expiresAt = sessionExpiry;
2563
+ }
2564
+ const delegations = [];
2565
+ for (const [spaceId, entries] of bySpace) {
2566
+ const abilities = this.permissionsToAbilities(entries);
2567
+ const prepared = this.wasmBindings.prepareSession({
2568
+ abilities,
2569
+ address: this.wasmBindings.ensureEip55(session.address),
2570
+ chainId: session.chainId,
2571
+ domain: this.siweDomain,
2572
+ issuedAt: now.toISOString(),
2573
+ expirationTime: expiresAt.toISOString(),
2574
+ spaceId,
2575
+ jwk: session.jwk
2576
+ });
2577
+ const signature = await this.signer.signMessage(prepared.siwe);
2578
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
2579
+ ...prepared,
2580
+ signature
2581
+ });
2582
+ const activateResult = await (0, import_sdk_core6.activateSessionWithHost)(
2583
+ this.config.host,
2584
+ delegatedSession.delegationHeader
2585
+ );
2586
+ if (!activateResult.success) {
2587
+ throw new Error(
2588
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2589
+ );
2590
+ }
2591
+ const delegation = this.runtimeDelegationFromSession(
2592
+ delegatedSession,
2593
+ entries,
2594
+ spaceId,
2595
+ session,
2596
+ expiresAt
2597
+ );
2598
+ this.runtimePermissionGrants.push({
2599
+ session: {
2600
+ delegationHeader: delegatedSession.delegationHeader,
2601
+ delegationCid: delegatedSession.delegationCid,
2602
+ spaceId,
2603
+ verificationMethod: session.verificationMethod,
2604
+ jwk: session.jwk
2605
+ },
2606
+ delegation,
2607
+ operations: this.permissionOperations(entries, spaceId),
2608
+ expiresAt
2609
+ });
2610
+ delegations.push(delegation);
2611
+ }
2612
+ return delegations;
2613
+ }
2451
2614
  /**
2452
2615
  * Get the DelegationManager for delegation CRUD operations.
2453
2616
  *
@@ -2636,7 +2799,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2636
2799
  if (this._serviceContext) {
2637
2800
  const publicKV = new import_sdk_core6.KVService({ prefix: "" });
2638
2801
  const publicContext = new import_sdk_core6.ServiceContext({
2639
- invoke: this.wasmBindings.invoke,
2802
+ invoke: this.invokeWithRuntimePermissions,
2640
2803
  fetch: this._serviceContext.fetch,
2641
2804
  hosts: this._serviceContext.hosts
2642
2805
  });
@@ -2724,8 +2887,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2724
2887
  * Issue a delegation using the capability-chain flow.
2725
2888
  *
2726
2889
  * When every requested permission is a subset of the current
2727
- * session's recap, the delegation is signed by the session key via
2728
- * WASM no wallet prompt. When at least one is NOT derivable, a
2890
+ * session's recap, or of one installed runtime permission delegation,
2891
+ * the delegation is signed by the session key via WASM no wallet
2892
+ * prompt. When at least one is NOT derivable, a
2729
2893
  * {@link PermissionNotInManifestError} is raised (carrying the
2730
2894
  * missing entries) so the caller can trigger an escalation flow
2731
2895
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -2805,6 +2969,23 @@ var _TinyCloudNode = class _TinyCloudNode {
2805
2969
  );
2806
2970
  const { subset, missing } = (0, import_sdk_core6.isCapabilitySubset)(expandedEntries, granted);
2807
2971
  if (!subset) {
2972
+ const runtimeGrant = this.findGrantForOperations(
2973
+ this.permissionEntriesToOperations(expandedEntries, session)
2974
+ );
2975
+ if (runtimeGrant) {
2976
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2977
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
2978
+ throw new import_sdk_core6.SessionExpiredError(runtimeGrant.expiresAt);
2979
+ }
2980
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
2981
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
2982
+ did,
2983
+ expandedEntries,
2984
+ runtimeExpiration,
2985
+ runtimeGrant
2986
+ );
2987
+ return { delegation: delegation2, prompted: false };
2988
+ }
2808
2989
  throw new import_sdk_core6.PermissionNotInManifestError(missing, granted);
2809
2990
  }
2810
2991
  const delegation = await this.createDelegationViaWasmPath(
@@ -2949,6 +3130,41 @@ var _TinyCloudNode = class _TinyCloudNode {
2949
3130
  host: this.config.host
2950
3131
  };
2951
3132
  }
3133
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
3134
+ const result = this.createDelegationWrapper({
3135
+ session: grant.session,
3136
+ delegateDID: did,
3137
+ spaceId: grant.session.spaceId,
3138
+ abilities: this.permissionsToAbilities(entries),
3139
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
3140
+ });
3141
+ const primary = result.resources[0];
3142
+ const delegationHeader = { Authorization: result.delegation };
3143
+ const targetHost = grant.delegation.host ?? this.config.host;
3144
+ const activateResult = await (0, import_sdk_core6.activateSessionWithHost)(
3145
+ targetHost,
3146
+ delegationHeader
3147
+ );
3148
+ if (!activateResult.success) {
3149
+ throw new Error(
3150
+ `Failed to activate delegation with host: ${activateResult.error}`
3151
+ );
3152
+ }
3153
+ return {
3154
+ cid: result.cid,
3155
+ delegationHeader,
3156
+ spaceId: grant.session.spaceId,
3157
+ path: primary.path,
3158
+ actions: primary.actions,
3159
+ resources: result.resources,
3160
+ disableSubDelegation: false,
3161
+ expiry: result.expiry,
3162
+ delegateDID: did,
3163
+ ownerAddress: grant.delegation.ownerAddress,
3164
+ chainId: grant.delegation.chainId,
3165
+ host: targetHost
3166
+ };
3167
+ }
2952
3168
  resolvePermissionSpace(space, session) {
2953
3169
  if (space === void 0) {
2954
3170
  return this.wasmBindings.makeSpaceId(
@@ -2965,6 +3181,223 @@ var _TinyCloudNode = class _TinyCloudNode {
2965
3181
  }
2966
3182
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
2967
3183
  }
3184
+ expandPermissionEntries(permissions) {
3185
+ return permissions.map((entry) => ({
3186
+ ...entry,
3187
+ actions: (0, import_sdk_core6.expandActionShortNames)(entry.service, entry.actions)
3188
+ }));
3189
+ }
3190
+ shortServiceName(service) {
3191
+ const short = import_sdk_core6.SERVICE_LONG_TO_SHORT[service];
3192
+ if (short === void 0) {
3193
+ throw new Error(
3194
+ `unknown service '${service}' \u2014 no short-form mapping`
3195
+ );
3196
+ }
3197
+ return short;
3198
+ }
3199
+ permissionsToAbilities(entries) {
3200
+ const abilities = {};
3201
+ for (const entry of entries) {
3202
+ const service = this.shortServiceName(entry.service);
3203
+ abilities[service] ?? (abilities[service] = {});
3204
+ const existing = abilities[service][entry.path] ?? [];
3205
+ const seen = new Set(existing);
3206
+ for (const action of entry.actions) {
3207
+ if (!seen.has(action)) {
3208
+ existing.push(action);
3209
+ seen.add(action);
3210
+ }
3211
+ }
3212
+ abilities[service][entry.path] = existing;
3213
+ }
3214
+ return abilities;
3215
+ }
3216
+ permissionOperations(entries, spaceId) {
3217
+ return entries.flatMap((entry) => {
3218
+ const service = this.shortServiceName(entry.service);
3219
+ return entry.actions.map((action) => ({
3220
+ spaceId,
3221
+ service,
3222
+ path: entry.path,
3223
+ action
3224
+ }));
3225
+ });
3226
+ }
3227
+ sessionCoversPermissionEntries(session, entries) {
3228
+ try {
3229
+ const granted = (0, import_sdk_core6.parseRecapCapabilities)(
3230
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
3231
+ session.siwe
3232
+ );
3233
+ return (0, import_sdk_core6.isCapabilitySubset)(entries, granted).subset;
3234
+ } catch {
3235
+ return false;
3236
+ }
3237
+ }
3238
+ permissionEntriesToOperations(entries, session) {
3239
+ return entries.flatMap((entry) => {
3240
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
3241
+ const service = this.shortServiceName(entry.service);
3242
+ return entry.actions.map((action) => ({
3243
+ spaceId,
3244
+ service,
3245
+ path: entry.path,
3246
+ action
3247
+ }));
3248
+ });
3249
+ }
3250
+ findRuntimeGrantsForPermissionEntries(entries, session) {
3251
+ const grants = [];
3252
+ const operations = this.permissionEntriesToOperations(entries, session);
3253
+ if (operations.length === 0) {
3254
+ return grants;
3255
+ }
3256
+ for (const operation of operations) {
3257
+ const grant = this.findGrantForOperation(operation);
3258
+ if (!grant) {
3259
+ return [];
3260
+ }
3261
+ if (!grants.includes(grant)) {
3262
+ grants.push(grant);
3263
+ }
3264
+ }
3265
+ return grants;
3266
+ }
3267
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
3268
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
3269
+ const primary = resources[0];
3270
+ return {
3271
+ cid: delegatedSession.delegationCid,
3272
+ delegationHeader: delegatedSession.delegationHeader,
3273
+ spaceId,
3274
+ path: primary.path,
3275
+ actions: primary.actions,
3276
+ resources,
3277
+ disableSubDelegation: false,
3278
+ expiry: expiresAt,
3279
+ delegateDID: session.verificationMethod,
3280
+ ownerAddress: session.address,
3281
+ chainId: session.chainId,
3282
+ host: this.config.host
3283
+ };
3284
+ }
3285
+ runtimeGrantFromDelegation(delegation, session) {
3286
+ const operations = this.operationsFromDelegation(delegation);
3287
+ return {
3288
+ session: {
3289
+ delegationHeader: delegation.delegationHeader,
3290
+ delegationCid: delegation.cid,
3291
+ spaceId: delegation.spaceId,
3292
+ verificationMethod: session.verificationMethod,
3293
+ jwk: session.jwk
3294
+ },
3295
+ delegation,
3296
+ operations,
3297
+ expiresAt: delegation.expiry
3298
+ };
3299
+ }
3300
+ delegatedResourcesForEntries(entries, spaceId) {
3301
+ return entries.map((entry) => ({
3302
+ service: this.shortServiceName(entry.service),
3303
+ space: spaceId,
3304
+ path: entry.path,
3305
+ actions: [...entry.actions]
3306
+ }));
3307
+ }
3308
+ operationsFromDelegation(delegation) {
3309
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
3310
+ return resources.flatMap(
3311
+ (resource) => resource.actions.map((action) => ({
3312
+ spaceId: resource.space,
3313
+ service: this.invocationServiceName(resource.service),
3314
+ path: resource.path,
3315
+ action
3316
+ }))
3317
+ );
3318
+ }
3319
+ flatDelegationResources(delegation) {
3320
+ const byService = /* @__PURE__ */ new Map();
3321
+ for (const action of delegation.actions) {
3322
+ const service = this.shortServiceName(action.split("/")[0]);
3323
+ const actions = byService.get(service) ?? [];
3324
+ actions.push(action);
3325
+ byService.set(service, actions);
3326
+ }
3327
+ return [...byService.entries()].map(([service, actions]) => ({
3328
+ service,
3329
+ space: delegation.spaceId,
3330
+ path: delegation.path,
3331
+ actions
3332
+ }));
3333
+ }
3334
+ selectInvocationSession(fallback, service, path, action) {
3335
+ const grant = this.findGrantForOperation({
3336
+ spaceId: fallback.spaceId,
3337
+ service: this.invocationServiceName(service),
3338
+ path,
3339
+ action
3340
+ });
3341
+ return grant?.session ?? fallback;
3342
+ }
3343
+ findGrantForOperations(operations) {
3344
+ if (operations.length === 0) {
3345
+ return void 0;
3346
+ }
3347
+ this.pruneExpiredRuntimePermissionGrants();
3348
+ return this.runtimePermissionGrants.find((grant) => {
3349
+ return operations.every(
3350
+ (operation) => grant.operations.some(
3351
+ (granted) => this.operationCovers(granted, operation)
3352
+ )
3353
+ );
3354
+ });
3355
+ }
3356
+ findGrantForOperation(operation) {
3357
+ return this.findGrantForOperations([operation]);
3358
+ }
3359
+ pruneExpiredRuntimePermissionGrants() {
3360
+ const now = Date.now();
3361
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
3362
+ (grant) => grant.expiresAt.getTime() > now
3363
+ );
3364
+ }
3365
+ operationCovers(granted, requested) {
3366
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
3367
+ }
3368
+ actionContains(grantedAction, requestedAction) {
3369
+ if (grantedAction === requestedAction) {
3370
+ return true;
3371
+ }
3372
+ if (grantedAction.endsWith("/*")) {
3373
+ const prefix = grantedAction.slice(0, -2);
3374
+ return requestedAction.startsWith(`${prefix}/`);
3375
+ }
3376
+ return false;
3377
+ }
3378
+ invocationServiceName(service) {
3379
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3380
+ }
3381
+ pathContains(grantedPath, requestedPath) {
3382
+ if (grantedPath === "" || grantedPath === "/") {
3383
+ return true;
3384
+ }
3385
+ if (grantedPath.endsWith("/**")) {
3386
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
3387
+ }
3388
+ if (grantedPath.endsWith("/*")) {
3389
+ const prefix = grantedPath.slice(0, -2);
3390
+ if (!requestedPath.startsWith(prefix)) {
3391
+ return false;
3392
+ }
3393
+ const remainder = requestedPath.slice(prefix.length);
3394
+ return !remainder.includes("/") || remainder === "/";
3395
+ }
3396
+ if (grantedPath.endsWith("/")) {
3397
+ return requestedPath.startsWith(grantedPath);
3398
+ }
3399
+ return grantedPath === requestedPath;
3400
+ }
2968
3401
  /**
2969
3402
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
2970
3403
  * {@link PermissionEntry}. Shares the implementation with the public