@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/index.js CHANGED
@@ -18291,22 +18291,6 @@ function secretPermissionEntries(name, action) {
18291
18291
  function isSecretsSpace(space) {
18292
18292
  return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
18293
18293
  }
18294
- function composeEscalatedManifest(manifest, additional) {
18295
- if (Array.isArray(manifest)) {
18296
- const [primary, ...rest] = manifest;
18297
- return [
18298
- {
18299
- ...primary,
18300
- permissions: [...primary.permissions ?? [], ...additional]
18301
- },
18302
- ...rest
18303
- ];
18304
- }
18305
- return {
18306
- ...manifest,
18307
- permissions: [...manifest.permissions ?? [], ...additional]
18308
- };
18309
- }
18310
18294
  var NodeSecretsService = class {
18311
18295
  constructor(config) {
18312
18296
  this.config = config;
@@ -18319,10 +18303,11 @@ var NodeSecretsService = class {
18319
18303
  return this.service.isUnlocked;
18320
18304
  }
18321
18305
  async unlock(signer) {
18322
- if (signer !== void 0) {
18323
- this.unlockSigner = signer;
18306
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
18307
+ if (effectiveSigner !== void 0) {
18308
+ this.unlockSigner = effectiveSigner;
18324
18309
  }
18325
- const result = await this.service.unlock(signer);
18310
+ const result = await this.service.unlock(effectiveSigner);
18326
18311
  if (result.ok) {
18327
18312
  this.shouldRestoreUnlock = true;
18328
18313
  }
@@ -18367,21 +18352,8 @@ var NodeSecretsService = class {
18367
18352
  `Cannot autosign ${actionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
18368
18353
  );
18369
18354
  }
18370
- const manifest = this.config.getManifest();
18371
- if (manifest === void 0) {
18372
- return secretsError(
18373
- ErrorCodes.PERMISSION_DENIED,
18374
- `Cannot autosign ${actionUrn(action)} for ${name}; set a manifest before mutating secrets.`
18375
- );
18376
- }
18377
18355
  try {
18378
- this.config.setManifest(
18379
- composeEscalatedManifest(
18380
- manifest,
18381
- secretPermissionEntries(name, action)
18382
- )
18383
- );
18384
- await this.config.signIn();
18356
+ await this.config.grantPermissions(secretPermissionEntries(name, action));
18385
18357
  return this.restoreUnlockAfterEscalation();
18386
18358
  } catch (error) {
18387
18359
  return secretsError(
@@ -18446,6 +18418,30 @@ var _TinyCloudNode = class _TinyCloudNode {
18446
18418
  this.auth = null;
18447
18419
  this.tc = null;
18448
18420
  this._chainId = 1;
18421
+ this.runtimePermissionGrants = [];
18422
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
18423
+ return this.wasmBindings.invoke(
18424
+ this.selectInvocationSession(session, service, path, action),
18425
+ service,
18426
+ path,
18427
+ action,
18428
+ facts
18429
+ );
18430
+ };
18431
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
18432
+ if (!this.wasmBindings.invokeAny) {
18433
+ throw new Error("WASM binding does not support invokeAny");
18434
+ }
18435
+ const grant = this.findGrantForOperations(
18436
+ entries.map((entry) => ({
18437
+ spaceId: entry.spaceId,
18438
+ service: this.invocationServiceName(entry.service),
18439
+ path: entry.path,
18440
+ action: entry.action
18441
+ }))
18442
+ );
18443
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
18444
+ };
18449
18445
  this.explicitHost = config.host;
18450
18446
  this.config = {
18451
18447
  ...config,
@@ -18481,7 +18477,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18481
18477
  this._sharingService = new SharingService({
18482
18478
  hosts: [this.config.host],
18483
18479
  // session: undefined - not needed for receive()
18484
- invoke: this.wasmBindings.invoke,
18480
+ invoke: this.invokeWithRuntimePermissions,
18485
18481
  fetch: globalThis.fetch.bind(globalThis),
18486
18482
  keyProvider: this._keyProvider,
18487
18483
  registry: this._capabilityRegistry,
@@ -18549,7 +18545,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18549
18545
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
18550
18546
  });
18551
18547
  this.tc = new TinyCloud(this.auth, {
18552
- invokeAny: this.wasmBindings.invokeAny
18548
+ invokeAny: this.invokeAnyWithRuntimePermissions
18553
18549
  });
18554
18550
  }
18555
18551
  syncResolvedHostFromAuth() {
@@ -18674,6 +18670,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18674
18670
  this._secrets = void 0;
18675
18671
  this._spaceService = void 0;
18676
18672
  this._serviceContext = void 0;
18673
+ this.runtimePermissionGrants = [];
18677
18674
  await this.tc.signIn(options);
18678
18675
  this.syncResolvedHostFromAuth();
18679
18676
  this.initializeServices();
@@ -18763,6 +18760,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18763
18760
  this._secrets = void 0;
18764
18761
  this._spaceService = void 0;
18765
18762
  this._serviceContext = void 0;
18763
+ this.runtimePermissionGrants = [];
18766
18764
  if (sessionData.address) {
18767
18765
  this._address = sessionData.address;
18768
18766
  }
@@ -18770,8 +18768,8 @@ var _TinyCloudNode = class _TinyCloudNode {
18770
18768
  this._chainId = sessionData.chainId;
18771
18769
  }
18772
18770
  this._serviceContext = new ServiceContext2({
18773
- invoke: this.wasmBindings.invoke,
18774
- invokeAny: this.wasmBindings.invokeAny,
18771
+ invoke: this.invokeWithRuntimePermissions,
18772
+ invokeAny: this.invokeAnyWithRuntimePermissions,
18775
18773
  fetch: globalThis.fetch.bind(globalThis),
18776
18774
  hosts: [this.config.host]
18777
18775
  });
@@ -18857,7 +18855,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18857
18855
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
18858
18856
  });
18859
18857
  this.tc = new TinyCloud(this.auth, {
18860
- invokeAny: this.wasmBindings.invokeAny
18858
+ invokeAny: this.invokeAnyWithRuntimePermissions
18861
18859
  });
18862
18860
  this.config.prefix = prefix;
18863
18861
  }
@@ -18901,7 +18899,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18901
18899
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
18902
18900
  });
18903
18901
  this.tc = new TinyCloud(this.auth, {
18904
- invokeAny: this.wasmBindings.invokeAny
18902
+ invokeAny: this.invokeAnyWithRuntimePermissions
18905
18903
  });
18906
18904
  this.config.prefix = prefix;
18907
18905
  }
@@ -18914,10 +18912,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18914
18912
  if (!session) {
18915
18913
  return;
18916
18914
  }
18917
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
18915
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
18918
18916
  this._serviceContext = new ServiceContext2({
18919
- invoke: this.wasmBindings.invoke,
18920
- invokeAny: this.wasmBindings.invokeAny,
18917
+ invoke: this.invokeWithRuntimePermissions,
18918
+ invokeAny: this.invokeAnyWithRuntimePermissions,
18921
18919
  fetch: globalThis.fetch.bind(globalThis),
18922
18920
  hosts: [this.config.host]
18923
18921
  });
@@ -19087,7 +19085,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19087
19085
  this._delegationManager = new DelegationManager({
19088
19086
  hosts: [this.config.host],
19089
19087
  session: serviceSession,
19090
- invoke: this.wasmBindings.invoke,
19088
+ invoke: this.invokeWithRuntimePermissions,
19091
19089
  fetch: globalThis.fetch.bind(globalThis)
19092
19090
  });
19093
19091
  this._spaceService = new SpaceService({
@@ -19354,9 +19352,9 @@ var _TinyCloudNode = class _TinyCloudNode {
19354
19352
  this._secrets = new NodeSecretsService({
19355
19353
  getService: () => this.getBaseSecrets(),
19356
19354
  getManifest: () => this.manifest,
19357
- setManifest: (manifest) => this.setManifest(manifest),
19358
- signIn: () => this.signIn(),
19359
- canEscalate: () => this.signer !== void 0 && this.tc !== void 0
19355
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
19356
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
19357
+ getUnlockSigner: () => this.signer ?? void 0
19360
19358
  });
19361
19359
  }
19362
19360
  return this._secrets;
@@ -19440,6 +19438,171 @@ var _TinyCloudNode = class _TinyCloudNode {
19440
19438
  }
19441
19439
  };
19442
19440
  }
19441
+ /**
19442
+ * Check whether the current session or an approved runtime delegation covers
19443
+ * every requested permission.
19444
+ */
19445
+ hasRuntimePermissions(permissions) {
19446
+ const session = this.auth?.tinyCloudSession;
19447
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
19448
+ return false;
19449
+ }
19450
+ const expanded = this.expandPermissionEntries(permissions);
19451
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
19452
+ return true;
19453
+ }
19454
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
19455
+ }
19456
+ /**
19457
+ * Return installed runtime permission delegations. When `permissions` is
19458
+ * provided, only delegations currently covering those permissions are
19459
+ * returned. Base-session manifest permissions are not represented here.
19460
+ */
19461
+ getRuntimePermissionDelegations(permissions) {
19462
+ this.pruneExpiredRuntimePermissionGrants();
19463
+ if (permissions === void 0) {
19464
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
19465
+ }
19466
+ const session = this.auth?.tinyCloudSession;
19467
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
19468
+ return [];
19469
+ }
19470
+ const expanded = this.expandPermissionEntries(permissions);
19471
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
19472
+ (grant) => grant.delegation
19473
+ );
19474
+ }
19475
+ /**
19476
+ * Install a portable runtime permission delegation into this SDK instance so
19477
+ * matching service calls and downstream `delegateTo()` calls can use it.
19478
+ */
19479
+ async useRuntimeDelegation(delegation) {
19480
+ const session = this.auth?.tinyCloudSession;
19481
+ if (!session) {
19482
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
19483
+ }
19484
+ if (delegation.expiry.getTime() <= Date.now()) {
19485
+ throw new SessionExpiredError(delegation.expiry);
19486
+ }
19487
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
19488
+ if (!expectedDids.has(delegation.delegateDID)) {
19489
+ throw new Error(
19490
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
19491
+ );
19492
+ }
19493
+ const targetHost = delegation.host ?? this.config.host;
19494
+ const activateResult = await activateSessionWithHost2(
19495
+ targetHost,
19496
+ delegation.delegationHeader
19497
+ );
19498
+ if (!activateResult.success) {
19499
+ throw new Error(
19500
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
19501
+ );
19502
+ }
19503
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
19504
+ (grant) => grant.delegation.cid !== delegation.cid
19505
+ );
19506
+ this.runtimePermissionGrants.push(
19507
+ this.runtimeGrantFromDelegation(delegation, session)
19508
+ );
19509
+ }
19510
+ /**
19511
+ * Store additional permissions as narrow delegations to the current session
19512
+ * key. Future service invocations automatically use a stored delegation when
19513
+ * its `(space, service, path, action)` covers the request.
19514
+ */
19515
+ async grantRuntimePermissions(permissions, options) {
19516
+ if (!Array.isArray(permissions) || permissions.length === 0) {
19517
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
19518
+ }
19519
+ const session = this.auth?.tinyCloudSession;
19520
+ if (!session) {
19521
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
19522
+ }
19523
+ const sessionExpiry = extractSiweExpiration(session.siwe);
19524
+ if (sessionExpiry !== void 0) {
19525
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
19526
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
19527
+ throw new SessionExpiredError(sessionExpiry);
19528
+ }
19529
+ }
19530
+ const expanded = this.expandPermissionEntries(permissions);
19531
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
19532
+ return [];
19533
+ }
19534
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
19535
+ if (existingGrants.length > 0) {
19536
+ return existingGrants.map((grant) => grant.delegation);
19537
+ }
19538
+ if (!this.signer) {
19539
+ throw new Error(
19540
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
19541
+ );
19542
+ }
19543
+ const bySpace = /* @__PURE__ */ new Map();
19544
+ for (const entry of expanded) {
19545
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
19546
+ const current = bySpace.get(spaceId) ?? [];
19547
+ current.push(entry);
19548
+ bySpace.set(spaceId, current);
19549
+ }
19550
+ const now = /* @__PURE__ */ new Date();
19551
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
19552
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
19553
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
19554
+ expiresAt = sessionExpiry;
19555
+ }
19556
+ const delegations = [];
19557
+ for (const [spaceId, entries] of bySpace) {
19558
+ const abilities = this.permissionsToAbilities(entries);
19559
+ const prepared = this.wasmBindings.prepareSession({
19560
+ abilities,
19561
+ address: this.wasmBindings.ensureEip55(session.address),
19562
+ chainId: session.chainId,
19563
+ domain: this.siweDomain,
19564
+ issuedAt: now.toISOString(),
19565
+ expirationTime: expiresAt.toISOString(),
19566
+ spaceId,
19567
+ jwk: session.jwk
19568
+ });
19569
+ const signature2 = await this.signer.signMessage(prepared.siwe);
19570
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
19571
+ ...prepared,
19572
+ signature: signature2
19573
+ });
19574
+ const activateResult = await activateSessionWithHost2(
19575
+ this.config.host,
19576
+ delegatedSession.delegationHeader
19577
+ );
19578
+ if (!activateResult.success) {
19579
+ throw new Error(
19580
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
19581
+ );
19582
+ }
19583
+ const delegation = this.runtimeDelegationFromSession(
19584
+ delegatedSession,
19585
+ entries,
19586
+ spaceId,
19587
+ session,
19588
+ expiresAt
19589
+ );
19590
+ this.runtimePermissionGrants.push({
19591
+ session: {
19592
+ delegationHeader: delegatedSession.delegationHeader,
19593
+ delegationCid: delegatedSession.delegationCid,
19594
+ spaceId,
19595
+ verificationMethod: session.verificationMethod,
19596
+ jwk: session.jwk
19597
+ },
19598
+ delegation,
19599
+ operations: this.permissionOperations(entries, spaceId),
19600
+ expiresAt
19601
+ });
19602
+ delegations.push(delegation);
19603
+ }
19604
+ return delegations;
19605
+ }
19443
19606
  /**
19444
19607
  * Get the DelegationManager for delegation CRUD operations.
19445
19608
  *
@@ -19628,7 +19791,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19628
19791
  if (this._serviceContext) {
19629
19792
  const publicKV = new KVService2({ prefix: "" });
19630
19793
  const publicContext = new ServiceContext2({
19631
- invoke: this.wasmBindings.invoke,
19794
+ invoke: this.invokeWithRuntimePermissions,
19632
19795
  fetch: this._serviceContext.fetch,
19633
19796
  hosts: this._serviceContext.hosts
19634
19797
  });
@@ -19716,8 +19879,9 @@ var _TinyCloudNode = class _TinyCloudNode {
19716
19879
  * Issue a delegation using the capability-chain flow.
19717
19880
  *
19718
19881
  * When every requested permission is a subset of the current
19719
- * session's recap, the delegation is signed by the session key via
19720
- * WASM no wallet prompt. When at least one is NOT derivable, a
19882
+ * session's recap, or of one installed runtime permission delegation,
19883
+ * the delegation is signed by the session key via WASM no wallet
19884
+ * prompt. When at least one is NOT derivable, a
19721
19885
  * {@link PermissionNotInManifestError} is raised (carrying the
19722
19886
  * missing entries) so the caller can trigger an escalation flow
19723
19887
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -19797,6 +19961,23 @@ var _TinyCloudNode = class _TinyCloudNode {
19797
19961
  );
19798
19962
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
19799
19963
  if (!subset) {
19964
+ const runtimeGrant = this.findGrantForOperations(
19965
+ this.permissionEntriesToOperations(expandedEntries, session)
19966
+ );
19967
+ if (runtimeGrant) {
19968
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
19969
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
19970
+ throw new SessionExpiredError(runtimeGrant.expiresAt);
19971
+ }
19972
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
19973
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
19974
+ did,
19975
+ expandedEntries,
19976
+ runtimeExpiration,
19977
+ runtimeGrant
19978
+ );
19979
+ return { delegation: delegation2, prompted: false };
19980
+ }
19800
19981
  throw new PermissionNotInManifestError(missing, granted);
19801
19982
  }
19802
19983
  const delegation = await this.createDelegationViaWasmPath(
@@ -19941,6 +20122,41 @@ var _TinyCloudNode = class _TinyCloudNode {
19941
20122
  host: this.config.host
19942
20123
  };
19943
20124
  }
20125
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
20126
+ const result = this.createDelegationWrapper({
20127
+ session: grant.session,
20128
+ delegateDID: did,
20129
+ spaceId: grant.session.spaceId,
20130
+ abilities: this.permissionsToAbilities(entries),
20131
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
20132
+ });
20133
+ const primary = result.resources[0];
20134
+ const delegationHeader = { Authorization: result.delegation };
20135
+ const targetHost = grant.delegation.host ?? this.config.host;
20136
+ const activateResult = await activateSessionWithHost2(
20137
+ targetHost,
20138
+ delegationHeader
20139
+ );
20140
+ if (!activateResult.success) {
20141
+ throw new Error(
20142
+ `Failed to activate delegation with host: ${activateResult.error}`
20143
+ );
20144
+ }
20145
+ return {
20146
+ cid: result.cid,
20147
+ delegationHeader,
20148
+ spaceId: grant.session.spaceId,
20149
+ path: primary.path,
20150
+ actions: primary.actions,
20151
+ resources: result.resources,
20152
+ disableSubDelegation: false,
20153
+ expiry: result.expiry,
20154
+ delegateDID: did,
20155
+ ownerAddress: grant.delegation.ownerAddress,
20156
+ chainId: grant.delegation.chainId,
20157
+ host: targetHost
20158
+ };
20159
+ }
19944
20160
  resolvePermissionSpace(space, session) {
19945
20161
  if (space === void 0) {
19946
20162
  return this.wasmBindings.makeSpaceId(
@@ -19957,6 +20173,223 @@ var _TinyCloudNode = class _TinyCloudNode {
19957
20173
  }
19958
20174
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
19959
20175
  }
20176
+ expandPermissionEntries(permissions) {
20177
+ return permissions.map((entry) => ({
20178
+ ...entry,
20179
+ actions: expandActionShortNames(entry.service, entry.actions)
20180
+ }));
20181
+ }
20182
+ shortServiceName(service) {
20183
+ const short = SERVICE_LONG_TO_SHORT[service];
20184
+ if (short === void 0) {
20185
+ throw new Error(
20186
+ `unknown service '${service}' \u2014 no short-form mapping`
20187
+ );
20188
+ }
20189
+ return short;
20190
+ }
20191
+ permissionsToAbilities(entries) {
20192
+ const abilities = {};
20193
+ for (const entry of entries) {
20194
+ const service = this.shortServiceName(entry.service);
20195
+ abilities[service] ?? (abilities[service] = {});
20196
+ const existing = abilities[service][entry.path] ?? [];
20197
+ const seen = new Set(existing);
20198
+ for (const action of entry.actions) {
20199
+ if (!seen.has(action)) {
20200
+ existing.push(action);
20201
+ seen.add(action);
20202
+ }
20203
+ }
20204
+ abilities[service][entry.path] = existing;
20205
+ }
20206
+ return abilities;
20207
+ }
20208
+ permissionOperations(entries, spaceId) {
20209
+ return entries.flatMap((entry) => {
20210
+ const service = this.shortServiceName(entry.service);
20211
+ return entry.actions.map((action) => ({
20212
+ spaceId,
20213
+ service,
20214
+ path: entry.path,
20215
+ action
20216
+ }));
20217
+ });
20218
+ }
20219
+ sessionCoversPermissionEntries(session, entries) {
20220
+ try {
20221
+ const granted = parseRecapCapabilities(
20222
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
20223
+ session.siwe
20224
+ );
20225
+ return isCapabilitySubset(entries, granted).subset;
20226
+ } catch {
20227
+ return false;
20228
+ }
20229
+ }
20230
+ permissionEntriesToOperations(entries, session) {
20231
+ return entries.flatMap((entry) => {
20232
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
20233
+ const service = this.shortServiceName(entry.service);
20234
+ return entry.actions.map((action) => ({
20235
+ spaceId,
20236
+ service,
20237
+ path: entry.path,
20238
+ action
20239
+ }));
20240
+ });
20241
+ }
20242
+ findRuntimeGrantsForPermissionEntries(entries, session) {
20243
+ const grants = [];
20244
+ const operations = this.permissionEntriesToOperations(entries, session);
20245
+ if (operations.length === 0) {
20246
+ return grants;
20247
+ }
20248
+ for (const operation of operations) {
20249
+ const grant = this.findGrantForOperation(operation);
20250
+ if (!grant) {
20251
+ return [];
20252
+ }
20253
+ if (!grants.includes(grant)) {
20254
+ grants.push(grant);
20255
+ }
20256
+ }
20257
+ return grants;
20258
+ }
20259
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
20260
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
20261
+ const primary = resources[0];
20262
+ return {
20263
+ cid: delegatedSession.delegationCid,
20264
+ delegationHeader: delegatedSession.delegationHeader,
20265
+ spaceId,
20266
+ path: primary.path,
20267
+ actions: primary.actions,
20268
+ resources,
20269
+ disableSubDelegation: false,
20270
+ expiry: expiresAt,
20271
+ delegateDID: session.verificationMethod,
20272
+ ownerAddress: session.address,
20273
+ chainId: session.chainId,
20274
+ host: this.config.host
20275
+ };
20276
+ }
20277
+ runtimeGrantFromDelegation(delegation, session) {
20278
+ const operations = this.operationsFromDelegation(delegation);
20279
+ return {
20280
+ session: {
20281
+ delegationHeader: delegation.delegationHeader,
20282
+ delegationCid: delegation.cid,
20283
+ spaceId: delegation.spaceId,
20284
+ verificationMethod: session.verificationMethod,
20285
+ jwk: session.jwk
20286
+ },
20287
+ delegation,
20288
+ operations,
20289
+ expiresAt: delegation.expiry
20290
+ };
20291
+ }
20292
+ delegatedResourcesForEntries(entries, spaceId) {
20293
+ return entries.map((entry) => ({
20294
+ service: this.shortServiceName(entry.service),
20295
+ space: spaceId,
20296
+ path: entry.path,
20297
+ actions: [...entry.actions]
20298
+ }));
20299
+ }
20300
+ operationsFromDelegation(delegation) {
20301
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
20302
+ return resources.flatMap(
20303
+ (resource) => resource.actions.map((action) => ({
20304
+ spaceId: resource.space,
20305
+ service: this.invocationServiceName(resource.service),
20306
+ path: resource.path,
20307
+ action
20308
+ }))
20309
+ );
20310
+ }
20311
+ flatDelegationResources(delegation) {
20312
+ const byService = /* @__PURE__ */ new Map();
20313
+ for (const action of delegation.actions) {
20314
+ const service = this.shortServiceName(action.split("/")[0]);
20315
+ const actions = byService.get(service) ?? [];
20316
+ actions.push(action);
20317
+ byService.set(service, actions);
20318
+ }
20319
+ return [...byService.entries()].map(([service, actions]) => ({
20320
+ service,
20321
+ space: delegation.spaceId,
20322
+ path: delegation.path,
20323
+ actions
20324
+ }));
20325
+ }
20326
+ selectInvocationSession(fallback, service, path, action) {
20327
+ const grant = this.findGrantForOperation({
20328
+ spaceId: fallback.spaceId,
20329
+ service: this.invocationServiceName(service),
20330
+ path,
20331
+ action
20332
+ });
20333
+ return grant?.session ?? fallback;
20334
+ }
20335
+ findGrantForOperations(operations) {
20336
+ if (operations.length === 0) {
20337
+ return void 0;
20338
+ }
20339
+ this.pruneExpiredRuntimePermissionGrants();
20340
+ return this.runtimePermissionGrants.find((grant) => {
20341
+ return operations.every(
20342
+ (operation) => grant.operations.some(
20343
+ (granted) => this.operationCovers(granted, operation)
20344
+ )
20345
+ );
20346
+ });
20347
+ }
20348
+ findGrantForOperation(operation) {
20349
+ return this.findGrantForOperations([operation]);
20350
+ }
20351
+ pruneExpiredRuntimePermissionGrants() {
20352
+ const now = Date.now();
20353
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
20354
+ (grant) => grant.expiresAt.getTime() > now
20355
+ );
20356
+ }
20357
+ operationCovers(granted, requested) {
20358
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
20359
+ }
20360
+ actionContains(grantedAction, requestedAction) {
20361
+ if (grantedAction === requestedAction) {
20362
+ return true;
20363
+ }
20364
+ if (grantedAction.endsWith("/*")) {
20365
+ const prefix = grantedAction.slice(0, -2);
20366
+ return requestedAction.startsWith(`${prefix}/`);
20367
+ }
20368
+ return false;
20369
+ }
20370
+ invocationServiceName(service) {
20371
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
20372
+ }
20373
+ pathContains(grantedPath, requestedPath) {
20374
+ if (grantedPath === "" || grantedPath === "/") {
20375
+ return true;
20376
+ }
20377
+ if (grantedPath.endsWith("/**")) {
20378
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
20379
+ }
20380
+ if (grantedPath.endsWith("/*")) {
20381
+ const prefix = grantedPath.slice(0, -2);
20382
+ if (!requestedPath.startsWith(prefix)) {
20383
+ return false;
20384
+ }
20385
+ const remainder = requestedPath.slice(prefix.length);
20386
+ return !remainder.includes("/") || remainder === "/";
20387
+ }
20388
+ if (grantedPath.endsWith("/")) {
20389
+ return requestedPath.startsWith(grantedPath);
20390
+ }
20391
+ return grantedPath === requestedPath;
20392
+ }
19960
20393
  /**
19961
20394
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
19962
20395
  * {@link PermissionEntry}. Shares the implementation with the public