@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/index.cjs CHANGED
@@ -17064,6 +17064,7 @@ __export(index_exports, {
17064
17064
  TinyCloud: () => import_sdk_core7.TinyCloud,
17065
17065
  TinyCloudNode: () => TinyCloudNode,
17066
17066
  UnsupportedFeatureError: () => import_sdk_core18.UnsupportedFeatureError,
17067
+ VAULT_PERMISSION_SERVICE: () => import_sdk_core9.VAULT_PERMISSION_SERVICE,
17067
17068
  VaultHeaders: () => import_sdk_core13.VaultHeaders,
17068
17069
  VaultPublicSpaceKVActions: () => import_sdk_core13.VaultPublicSpaceKVActions,
17069
17070
  VersionCheckError: () => import_sdk_core18.VersionCheckError,
@@ -17080,6 +17081,8 @@ __export(index_exports, {
17080
17081
  defaultSpaceCreationHandler: () => import_sdk_core8.defaultSpaceCreationHandler,
17081
17082
  deserializeDelegation: () => deserializeDelegation,
17082
17083
  expandActionShortNames: () => import_sdk_core9.expandActionShortNames,
17084
+ expandPermissionEntries: () => import_sdk_core9.expandPermissionEntries,
17085
+ expandPermissionEntry: () => import_sdk_core9.expandPermissionEntry,
17083
17086
  isCapabilitySubset: () => import_sdk_core9.isCapabilitySubset,
17084
17087
  loadManifest: () => import_sdk_core9.loadManifest,
17085
17088
  makePublicSpaceId: () => import_sdk_core17.makePublicSpaceId,
@@ -18257,49 +18260,29 @@ function secretsError(code, message, cause) {
18257
18260
  }
18258
18261
  };
18259
18262
  }
18260
- function actionUrn(action) {
18261
- return `tinycloud.kv/${action}`;
18263
+ function displayActionUrn(action) {
18264
+ return action === "put" ? "tinycloud.vault/write" : "tinycloud.vault/delete";
18262
18265
  }
18263
- function secretResourcePath(base2, name) {
18264
- return `${base2}/${SECRET_PREFIX}${name}`;
18266
+ function kvActionUrn(action) {
18267
+ return `tinycloud.kv/${action}`;
18265
18268
  }
18266
18269
  function secretPermissionEntries(name, action) {
18267
18270
  return [
18268
18271
  {
18269
- service: "tinycloud.kv",
18272
+ service: "tinycloud.vault",
18270
18273
  space: SECRETS_SPACE,
18271
- path: secretResourcePath("keys", name),
18272
- actions: [action],
18273
- skipPrefix: true
18274
- },
18275
- {
18276
- service: "tinycloud.kv",
18277
- space: SECRETS_SPACE,
18278
- path: secretResourcePath("vault", name),
18279
- actions: [action],
18274
+ path: `${SECRET_PREFIX}${name}`,
18275
+ actions: [action === "put" ? "write" : "delete"],
18280
18276
  skipPrefix: true
18281
18277
  }
18282
18278
  ];
18283
18279
  }
18280
+ function secretResourcePath(base2, name) {
18281
+ return `${base2}/${SECRET_PREFIX}${name}`;
18282
+ }
18284
18283
  function isSecretsSpace(space) {
18285
18284
  return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
18286
18285
  }
18287
- function composeEscalatedManifest(manifest, additional) {
18288
- if (Array.isArray(manifest)) {
18289
- const [primary, ...rest] = manifest;
18290
- return [
18291
- {
18292
- ...primary,
18293
- permissions: [...primary.permissions ?? [], ...additional]
18294
- },
18295
- ...rest
18296
- ];
18297
- }
18298
- return {
18299
- ...manifest,
18300
- permissions: [...manifest.permissions ?? [], ...additional]
18301
- };
18302
- }
18303
18286
  var NodeSecretsService = class {
18304
18287
  constructor(config) {
18305
18288
  this.config = config;
@@ -18312,10 +18295,11 @@ var NodeSecretsService = class {
18312
18295
  return this.service.isUnlocked;
18313
18296
  }
18314
18297
  async unlock(signer) {
18315
- if (signer !== void 0) {
18316
- this.unlockSigner = signer;
18298
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
18299
+ if (effectiveSigner !== void 0) {
18300
+ this.unlockSigner = effectiveSigner;
18317
18301
  }
18318
- const result = await this.service.unlock(signer);
18302
+ const result = await this.service.unlock(effectiveSigner);
18319
18303
  if (result.ok) {
18320
18304
  this.shouldRestoreUnlock = true;
18321
18305
  }
@@ -18357,29 +18341,16 @@ var NodeSecretsService = class {
18357
18341
  if (!this.config.canEscalate()) {
18358
18342
  return secretsError(
18359
18343
  import_sdk_core4.ErrorCodes.PERMISSION_DENIED,
18360
- `Cannot autosign ${actionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
18361
- );
18362
- }
18363
- const manifest = this.config.getManifest();
18364
- if (manifest === void 0) {
18365
- return secretsError(
18366
- import_sdk_core4.ErrorCodes.PERMISSION_DENIED,
18367
- `Cannot autosign ${actionUrn(action)} for ${name}; set a manifest before mutating secrets.`
18344
+ `Cannot autosign ${displayActionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
18368
18345
  );
18369
18346
  }
18370
18347
  try {
18371
- this.config.setManifest(
18372
- composeEscalatedManifest(
18373
- manifest,
18374
- secretPermissionEntries(name, action)
18375
- )
18376
- );
18377
- await this.config.signIn();
18348
+ await this.config.grantPermissions(secretPermissionEntries(name, action));
18378
18349
  return this.restoreUnlockAfterEscalation();
18379
18350
  } catch (error) {
18380
18351
  return secretsError(
18381
18352
  import_sdk_core4.ErrorCodes.PERMISSION_DENIED,
18382
- error instanceof Error ? error.message : `Autosign escalation for ${actionUrn(action)} on ${name} failed.`,
18353
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${name} failed.`,
18383
18354
  error instanceof Error ? error : void 0
18384
18355
  );
18385
18356
  }
@@ -18396,7 +18367,7 @@ var NodeSecretsService = class {
18396
18367
  return false;
18397
18368
  }
18398
18369
  const manifests = Array.isArray(manifest) ? manifest : [manifest];
18399
- const requiredAction = actionUrn(action);
18370
+ const requiredAction = kvActionUrn(action);
18400
18371
  return manifests.some((entry) => {
18401
18372
  const resolved = (0, import_sdk_core4.resolveManifest)(entry);
18402
18373
  return ["keys", "vault"].every(
@@ -18439,6 +18410,30 @@ var _TinyCloudNode = class _TinyCloudNode {
18439
18410
  this.auth = null;
18440
18411
  this.tc = null;
18441
18412
  this._chainId = 1;
18413
+ this.runtimePermissionGrants = [];
18414
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
18415
+ return this.wasmBindings.invoke(
18416
+ this.selectInvocationSession(session, service, path, action),
18417
+ service,
18418
+ path,
18419
+ action,
18420
+ facts
18421
+ );
18422
+ };
18423
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
18424
+ if (!this.wasmBindings.invokeAny) {
18425
+ throw new Error("WASM binding does not support invokeAny");
18426
+ }
18427
+ const grant = this.findGrantForOperations(
18428
+ entries.map((entry) => ({
18429
+ spaceId: entry.spaceId,
18430
+ service: this.invocationServiceName(entry.service),
18431
+ path: entry.path,
18432
+ action: entry.action
18433
+ }))
18434
+ );
18435
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
18436
+ };
18442
18437
  this.explicitHost = config.host;
18443
18438
  this.config = {
18444
18439
  ...config,
@@ -18474,7 +18469,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18474
18469
  this._sharingService = new import_sdk_core5.SharingService({
18475
18470
  hosts: [this.config.host],
18476
18471
  // session: undefined - not needed for receive()
18477
- invoke: this.wasmBindings.invoke,
18472
+ invoke: this.invokeWithRuntimePermissions,
18478
18473
  fetch: globalThis.fetch.bind(globalThis),
18479
18474
  keyProvider: this._keyProvider,
18480
18475
  registry: this._capabilityRegistry,
@@ -18542,7 +18537,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18542
18537
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
18543
18538
  });
18544
18539
  this.tc = new import_sdk_core5.TinyCloud(this.auth, {
18545
- invokeAny: this.wasmBindings.invokeAny
18540
+ invokeAny: this.invokeAnyWithRuntimePermissions
18546
18541
  });
18547
18542
  }
18548
18543
  syncResolvedHostFromAuth() {
@@ -18667,6 +18662,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18667
18662
  this._secrets = void 0;
18668
18663
  this._spaceService = void 0;
18669
18664
  this._serviceContext = void 0;
18665
+ this.runtimePermissionGrants = [];
18670
18666
  await this.tc.signIn(options);
18671
18667
  this.syncResolvedHostFromAuth();
18672
18668
  this.initializeServices();
@@ -18756,6 +18752,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18756
18752
  this._secrets = void 0;
18757
18753
  this._spaceService = void 0;
18758
18754
  this._serviceContext = void 0;
18755
+ this.runtimePermissionGrants = [];
18759
18756
  if (sessionData.address) {
18760
18757
  this._address = sessionData.address;
18761
18758
  }
@@ -18763,8 +18760,8 @@ var _TinyCloudNode = class _TinyCloudNode {
18763
18760
  this._chainId = sessionData.chainId;
18764
18761
  }
18765
18762
  this._serviceContext = new import_sdk_core5.ServiceContext({
18766
- invoke: this.wasmBindings.invoke,
18767
- invokeAny: this.wasmBindings.invokeAny,
18763
+ invoke: this.invokeWithRuntimePermissions,
18764
+ invokeAny: this.invokeAnyWithRuntimePermissions,
18768
18765
  fetch: globalThis.fetch.bind(globalThis),
18769
18766
  hosts: [this.config.host]
18770
18767
  });
@@ -18850,7 +18847,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18850
18847
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
18851
18848
  });
18852
18849
  this.tc = new import_sdk_core5.TinyCloud(this.auth, {
18853
- invokeAny: this.wasmBindings.invokeAny
18850
+ invokeAny: this.invokeAnyWithRuntimePermissions
18854
18851
  });
18855
18852
  this.config.prefix = prefix;
18856
18853
  }
@@ -18894,7 +18891,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18894
18891
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
18895
18892
  });
18896
18893
  this.tc = new import_sdk_core5.TinyCloud(this.auth, {
18897
- invokeAny: this.wasmBindings.invokeAny
18894
+ invokeAny: this.invokeAnyWithRuntimePermissions
18898
18895
  });
18899
18896
  this.config.prefix = prefix;
18900
18897
  }
@@ -18907,10 +18904,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18907
18904
  if (!session) {
18908
18905
  return;
18909
18906
  }
18910
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
18907
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
18911
18908
  this._serviceContext = new import_sdk_core5.ServiceContext({
18912
- invoke: this.wasmBindings.invoke,
18913
- invokeAny: this.wasmBindings.invokeAny,
18909
+ invoke: this.invokeWithRuntimePermissions,
18910
+ invokeAny: this.invokeAnyWithRuntimePermissions,
18914
18911
  fetch: globalThis.fetch.bind(globalThis),
18915
18912
  hosts: [this.config.host]
18916
18913
  });
@@ -19080,7 +19077,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19080
19077
  this._delegationManager = new import_sdk_core5.DelegationManager({
19081
19078
  hosts: [this.config.host],
19082
19079
  session: serviceSession,
19083
- invoke: this.wasmBindings.invoke,
19080
+ invoke: this.invokeWithRuntimePermissions,
19084
19081
  fetch: globalThis.fetch.bind(globalThis)
19085
19082
  });
19086
19083
  this._spaceService = new import_sdk_core5.SpaceService({
@@ -19347,9 +19344,9 @@ var _TinyCloudNode = class _TinyCloudNode {
19347
19344
  this._secrets = new NodeSecretsService({
19348
19345
  getService: () => this.getBaseSecrets(),
19349
19346
  getManifest: () => this.manifest,
19350
- setManifest: (manifest) => this.setManifest(manifest),
19351
- signIn: () => this.signIn(),
19352
- canEscalate: () => this.signer !== void 0 && this.tc !== void 0
19347
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
19348
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
19349
+ getUnlockSigner: () => this.signer ?? void 0
19353
19350
  });
19354
19351
  }
19355
19352
  return this._secrets;
@@ -19433,6 +19430,171 @@ var _TinyCloudNode = class _TinyCloudNode {
19433
19430
  }
19434
19431
  };
19435
19432
  }
19433
+ /**
19434
+ * Check whether the current session or an approved runtime delegation covers
19435
+ * every requested permission.
19436
+ */
19437
+ hasRuntimePermissions(permissions) {
19438
+ const session = this.auth?.tinyCloudSession;
19439
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
19440
+ return false;
19441
+ }
19442
+ const expanded = this.expandPermissionEntries(permissions);
19443
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
19444
+ return true;
19445
+ }
19446
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
19447
+ }
19448
+ /**
19449
+ * Return installed runtime permission delegations. When `permissions` is
19450
+ * provided, only delegations currently covering those permissions are
19451
+ * returned. Base-session manifest permissions are not represented here.
19452
+ */
19453
+ getRuntimePermissionDelegations(permissions) {
19454
+ this.pruneExpiredRuntimePermissionGrants();
19455
+ if (permissions === void 0) {
19456
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
19457
+ }
19458
+ const session = this.auth?.tinyCloudSession;
19459
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
19460
+ return [];
19461
+ }
19462
+ const expanded = this.expandPermissionEntries(permissions);
19463
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
19464
+ (grant) => grant.delegation
19465
+ );
19466
+ }
19467
+ /**
19468
+ * Install a portable runtime permission delegation into this SDK instance so
19469
+ * matching service calls and downstream `delegateTo()` calls can use it.
19470
+ */
19471
+ async useRuntimeDelegation(delegation) {
19472
+ const session = this.auth?.tinyCloudSession;
19473
+ if (!session) {
19474
+ throw new import_sdk_core5.SessionExpiredError(/* @__PURE__ */ new Date(0));
19475
+ }
19476
+ if (delegation.expiry.getTime() <= Date.now()) {
19477
+ throw new import_sdk_core5.SessionExpiredError(delegation.expiry);
19478
+ }
19479
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
19480
+ if (!expectedDids.has(delegation.delegateDID)) {
19481
+ throw new Error(
19482
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
19483
+ );
19484
+ }
19485
+ const targetHost = delegation.host ?? this.config.host;
19486
+ const activateResult = await (0, import_sdk_core5.activateSessionWithHost)(
19487
+ targetHost,
19488
+ delegation.delegationHeader
19489
+ );
19490
+ if (!activateResult.success) {
19491
+ throw new Error(
19492
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
19493
+ );
19494
+ }
19495
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
19496
+ (grant) => grant.delegation.cid !== delegation.cid
19497
+ );
19498
+ this.runtimePermissionGrants.push(
19499
+ this.runtimeGrantFromDelegation(delegation, session)
19500
+ );
19501
+ }
19502
+ /**
19503
+ * Store additional permissions as narrow delegations to the current session
19504
+ * key. Future service invocations automatically use a stored delegation when
19505
+ * its `(space, service, path, action)` covers the request.
19506
+ */
19507
+ async grantRuntimePermissions(permissions, options) {
19508
+ if (!Array.isArray(permissions) || permissions.length === 0) {
19509
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
19510
+ }
19511
+ const session = this.auth?.tinyCloudSession;
19512
+ if (!session) {
19513
+ throw new import_sdk_core5.SessionExpiredError(/* @__PURE__ */ new Date(0));
19514
+ }
19515
+ const sessionExpiry = extractSiweExpiration(session.siwe);
19516
+ if (sessionExpiry !== void 0) {
19517
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
19518
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
19519
+ throw new import_sdk_core5.SessionExpiredError(sessionExpiry);
19520
+ }
19521
+ }
19522
+ const expanded = this.expandPermissionEntries(permissions);
19523
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
19524
+ return [];
19525
+ }
19526
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
19527
+ if (existingGrants.length > 0) {
19528
+ return existingGrants.map((grant) => grant.delegation);
19529
+ }
19530
+ if (!this.signer) {
19531
+ throw new Error(
19532
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
19533
+ );
19534
+ }
19535
+ const bySpace = /* @__PURE__ */ new Map();
19536
+ for (const entry of expanded) {
19537
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
19538
+ const current = bySpace.get(spaceId) ?? [];
19539
+ current.push(entry);
19540
+ bySpace.set(spaceId, current);
19541
+ }
19542
+ const now = /* @__PURE__ */ new Date();
19543
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
19544
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
19545
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
19546
+ expiresAt = sessionExpiry;
19547
+ }
19548
+ const delegations = [];
19549
+ for (const [spaceId, entries] of bySpace) {
19550
+ const abilities = this.permissionsToAbilities(entries);
19551
+ const prepared = this.wasmBindings.prepareSession({
19552
+ abilities,
19553
+ address: this.wasmBindings.ensureEip55(session.address),
19554
+ chainId: session.chainId,
19555
+ domain: this.siweDomain,
19556
+ issuedAt: now.toISOString(),
19557
+ expirationTime: expiresAt.toISOString(),
19558
+ spaceId,
19559
+ jwk: session.jwk
19560
+ });
19561
+ const signature2 = await this.signer.signMessage(prepared.siwe);
19562
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
19563
+ ...prepared,
19564
+ signature: signature2
19565
+ });
19566
+ const activateResult = await (0, import_sdk_core5.activateSessionWithHost)(
19567
+ this.config.host,
19568
+ delegatedSession.delegationHeader
19569
+ );
19570
+ if (!activateResult.success) {
19571
+ throw new Error(
19572
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
19573
+ );
19574
+ }
19575
+ const delegation = this.runtimeDelegationFromSession(
19576
+ delegatedSession,
19577
+ entries,
19578
+ spaceId,
19579
+ session,
19580
+ expiresAt
19581
+ );
19582
+ this.runtimePermissionGrants.push({
19583
+ session: {
19584
+ delegationHeader: delegatedSession.delegationHeader,
19585
+ delegationCid: delegatedSession.delegationCid,
19586
+ spaceId,
19587
+ verificationMethod: session.verificationMethod,
19588
+ jwk: session.jwk
19589
+ },
19590
+ delegation,
19591
+ operations: this.permissionOperations(entries, spaceId),
19592
+ expiresAt
19593
+ });
19594
+ delegations.push(delegation);
19595
+ }
19596
+ return delegations;
19597
+ }
19436
19598
  /**
19437
19599
  * Get the DelegationManager for delegation CRUD operations.
19438
19600
  *
@@ -19621,7 +19783,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19621
19783
  if (this._serviceContext) {
19622
19784
  const publicKV = new import_sdk_core5.KVService({ prefix: "" });
19623
19785
  const publicContext = new import_sdk_core5.ServiceContext({
19624
- invoke: this.wasmBindings.invoke,
19786
+ invoke: this.invokeWithRuntimePermissions,
19625
19787
  fetch: this._serviceContext.fetch,
19626
19788
  hosts: this._serviceContext.hosts
19627
19789
  });
@@ -19709,8 +19871,9 @@ var _TinyCloudNode = class _TinyCloudNode {
19709
19871
  * Issue a delegation using the capability-chain flow.
19710
19872
  *
19711
19873
  * When every requested permission is a subset of the current
19712
- * session's recap, the delegation is signed by the session key via
19713
- * WASM no wallet prompt. When at least one is NOT derivable, a
19874
+ * session's recap, or of one installed runtime permission delegation,
19875
+ * the delegation is signed by the session key via WASM no wallet
19876
+ * prompt. When at least one is NOT derivable, a
19714
19877
  * {@link PermissionNotInManifestError} is raised (carrying the
19715
19878
  * missing entries) so the caller can trigger an escalation flow
19716
19879
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -19760,10 +19923,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19760
19923
  "delegateTo requires a non-empty permissions array"
19761
19924
  );
19762
19925
  }
19763
- const expandedEntries = permissions.map((entry) => ({
19764
- ...entry,
19765
- actions: (0, import_sdk_core5.expandActionShortNames)(entry.service, entry.actions)
19766
- }));
19926
+ const expandedEntries = this.expandPermissionEntries(permissions);
19767
19927
  const now = /* @__PURE__ */ new Date();
19768
19928
  const expiryMs = resolveExpiryMs(options?.expiry);
19769
19929
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -19790,6 +19950,23 @@ var _TinyCloudNode = class _TinyCloudNode {
19790
19950
  );
19791
19951
  const { subset, missing } = (0, import_sdk_core5.isCapabilitySubset)(expandedEntries, granted);
19792
19952
  if (!subset) {
19953
+ const runtimeGrant = this.findGrantForOperations(
19954
+ this.permissionEntriesToOperations(expandedEntries, session)
19955
+ );
19956
+ if (runtimeGrant) {
19957
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
19958
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
19959
+ throw new import_sdk_core5.SessionExpiredError(runtimeGrant.expiresAt);
19960
+ }
19961
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
19962
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
19963
+ did,
19964
+ expandedEntries,
19965
+ runtimeExpiration,
19966
+ runtimeGrant
19967
+ );
19968
+ return { delegation: delegation2, prompted: false };
19969
+ }
19793
19970
  throw new import_sdk_core5.PermissionNotInManifestError(missing, granted);
19794
19971
  }
19795
19972
  const delegation = await this.createDelegationViaWasmPath(
@@ -19934,6 +20111,41 @@ var _TinyCloudNode = class _TinyCloudNode {
19934
20111
  host: this.config.host
19935
20112
  };
19936
20113
  }
20114
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
20115
+ const result = this.createDelegationWrapper({
20116
+ session: grant.session,
20117
+ delegateDID: did,
20118
+ spaceId: grant.session.spaceId,
20119
+ abilities: this.permissionsToAbilities(entries),
20120
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
20121
+ });
20122
+ const primary = result.resources[0];
20123
+ const delegationHeader = { Authorization: result.delegation };
20124
+ const targetHost = grant.delegation.host ?? this.config.host;
20125
+ const activateResult = await (0, import_sdk_core5.activateSessionWithHost)(
20126
+ targetHost,
20127
+ delegationHeader
20128
+ );
20129
+ if (!activateResult.success) {
20130
+ throw new Error(
20131
+ `Failed to activate delegation with host: ${activateResult.error}`
20132
+ );
20133
+ }
20134
+ return {
20135
+ cid: result.cid,
20136
+ delegationHeader,
20137
+ spaceId: grant.session.spaceId,
20138
+ path: primary.path,
20139
+ actions: primary.actions,
20140
+ resources: result.resources,
20141
+ disableSubDelegation: false,
20142
+ expiry: result.expiry,
20143
+ delegateDID: did,
20144
+ ownerAddress: grant.delegation.ownerAddress,
20145
+ chainId: grant.delegation.chainId,
20146
+ host: targetHost
20147
+ };
20148
+ }
19937
20149
  resolvePermissionSpace(space, session) {
19938
20150
  if (space === void 0) {
19939
20151
  return this.wasmBindings.makeSpaceId(
@@ -19950,6 +20162,220 @@ var _TinyCloudNode = class _TinyCloudNode {
19950
20162
  }
19951
20163
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
19952
20164
  }
20165
+ expandPermissionEntries(permissions) {
20166
+ return (0, import_sdk_core5.expandPermissionEntries)(permissions);
20167
+ }
20168
+ shortServiceName(service) {
20169
+ const short = import_sdk_core5.SERVICE_LONG_TO_SHORT[service];
20170
+ if (short === void 0) {
20171
+ throw new Error(
20172
+ `unknown service '${service}' \u2014 no short-form mapping`
20173
+ );
20174
+ }
20175
+ return short;
20176
+ }
20177
+ permissionsToAbilities(entries) {
20178
+ const abilities = {};
20179
+ for (const entry of entries) {
20180
+ const service = this.shortServiceName(entry.service);
20181
+ abilities[service] ?? (abilities[service] = {});
20182
+ const existing = abilities[service][entry.path] ?? [];
20183
+ const seen = new Set(existing);
20184
+ for (const action of entry.actions) {
20185
+ if (!seen.has(action)) {
20186
+ existing.push(action);
20187
+ seen.add(action);
20188
+ }
20189
+ }
20190
+ abilities[service][entry.path] = existing;
20191
+ }
20192
+ return abilities;
20193
+ }
20194
+ permissionOperations(entries, spaceId) {
20195
+ return entries.flatMap((entry) => {
20196
+ const service = this.shortServiceName(entry.service);
20197
+ return entry.actions.map((action) => ({
20198
+ spaceId,
20199
+ service,
20200
+ path: entry.path,
20201
+ action
20202
+ }));
20203
+ });
20204
+ }
20205
+ sessionCoversPermissionEntries(session, entries) {
20206
+ try {
20207
+ const granted = (0, import_sdk_core5.parseRecapCapabilities)(
20208
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
20209
+ session.siwe
20210
+ );
20211
+ return (0, import_sdk_core5.isCapabilitySubset)(entries, granted).subset;
20212
+ } catch {
20213
+ return false;
20214
+ }
20215
+ }
20216
+ permissionEntriesToOperations(entries, session) {
20217
+ return entries.flatMap((entry) => {
20218
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
20219
+ const service = this.shortServiceName(entry.service);
20220
+ return entry.actions.map((action) => ({
20221
+ spaceId,
20222
+ service,
20223
+ path: entry.path,
20224
+ action
20225
+ }));
20226
+ });
20227
+ }
20228
+ findRuntimeGrantsForPermissionEntries(entries, session) {
20229
+ const grants = [];
20230
+ const operations = this.permissionEntriesToOperations(entries, session);
20231
+ if (operations.length === 0) {
20232
+ return grants;
20233
+ }
20234
+ for (const operation of operations) {
20235
+ const grant = this.findGrantForOperation(operation);
20236
+ if (!grant) {
20237
+ return [];
20238
+ }
20239
+ if (!grants.includes(grant)) {
20240
+ grants.push(grant);
20241
+ }
20242
+ }
20243
+ return grants;
20244
+ }
20245
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
20246
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
20247
+ const primary = resources[0];
20248
+ return {
20249
+ cid: delegatedSession.delegationCid,
20250
+ delegationHeader: delegatedSession.delegationHeader,
20251
+ spaceId,
20252
+ path: primary.path,
20253
+ actions: primary.actions,
20254
+ resources,
20255
+ disableSubDelegation: false,
20256
+ expiry: expiresAt,
20257
+ delegateDID: session.verificationMethod,
20258
+ ownerAddress: session.address,
20259
+ chainId: session.chainId,
20260
+ host: this.config.host
20261
+ };
20262
+ }
20263
+ runtimeGrantFromDelegation(delegation, session) {
20264
+ const operations = this.operationsFromDelegation(delegation);
20265
+ return {
20266
+ session: {
20267
+ delegationHeader: delegation.delegationHeader,
20268
+ delegationCid: delegation.cid,
20269
+ spaceId: delegation.spaceId,
20270
+ verificationMethod: session.verificationMethod,
20271
+ jwk: session.jwk
20272
+ },
20273
+ delegation,
20274
+ operations,
20275
+ expiresAt: delegation.expiry
20276
+ };
20277
+ }
20278
+ delegatedResourcesForEntries(entries, spaceId) {
20279
+ return entries.map((entry) => ({
20280
+ service: this.shortServiceName(entry.service),
20281
+ space: spaceId,
20282
+ path: entry.path,
20283
+ actions: [...entry.actions]
20284
+ }));
20285
+ }
20286
+ operationsFromDelegation(delegation) {
20287
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
20288
+ return resources.flatMap(
20289
+ (resource) => resource.actions.map((action) => ({
20290
+ spaceId: resource.space,
20291
+ service: this.invocationServiceName(resource.service),
20292
+ path: resource.path,
20293
+ action
20294
+ }))
20295
+ );
20296
+ }
20297
+ flatDelegationResources(delegation) {
20298
+ const byService = /* @__PURE__ */ new Map();
20299
+ for (const action of delegation.actions) {
20300
+ const service = this.shortServiceName(action.split("/")[0]);
20301
+ const actions = byService.get(service) ?? [];
20302
+ actions.push(action);
20303
+ byService.set(service, actions);
20304
+ }
20305
+ return [...byService.entries()].map(([service, actions]) => ({
20306
+ service,
20307
+ space: delegation.spaceId,
20308
+ path: delegation.path,
20309
+ actions
20310
+ }));
20311
+ }
20312
+ selectInvocationSession(fallback, service, path, action) {
20313
+ const grant = this.findGrantForOperation({
20314
+ spaceId: fallback.spaceId,
20315
+ service: this.invocationServiceName(service),
20316
+ path,
20317
+ action
20318
+ });
20319
+ return grant?.session ?? fallback;
20320
+ }
20321
+ findGrantForOperations(operations) {
20322
+ if (operations.length === 0) {
20323
+ return void 0;
20324
+ }
20325
+ this.pruneExpiredRuntimePermissionGrants();
20326
+ return this.runtimePermissionGrants.find((grant) => {
20327
+ return operations.every(
20328
+ (operation) => grant.operations.some(
20329
+ (granted) => this.operationCovers(granted, operation)
20330
+ )
20331
+ );
20332
+ });
20333
+ }
20334
+ findGrantForOperation(operation) {
20335
+ return this.findGrantForOperations([operation]);
20336
+ }
20337
+ pruneExpiredRuntimePermissionGrants() {
20338
+ const now = Date.now();
20339
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
20340
+ (grant) => grant.expiresAt.getTime() > now
20341
+ );
20342
+ }
20343
+ operationCovers(granted, requested) {
20344
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
20345
+ }
20346
+ actionContains(grantedAction, requestedAction) {
20347
+ if (grantedAction === requestedAction) {
20348
+ return true;
20349
+ }
20350
+ if (grantedAction.endsWith("/*")) {
20351
+ const prefix = grantedAction.slice(0, -2);
20352
+ return requestedAction.startsWith(`${prefix}/`);
20353
+ }
20354
+ return false;
20355
+ }
20356
+ invocationServiceName(service) {
20357
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
20358
+ }
20359
+ pathContains(grantedPath, requestedPath) {
20360
+ if (grantedPath === "" || grantedPath === "/") {
20361
+ return true;
20362
+ }
20363
+ if (grantedPath.endsWith("/**")) {
20364
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
20365
+ }
20366
+ if (grantedPath.endsWith("/*")) {
20367
+ const prefix = grantedPath.slice(0, -2);
20368
+ if (!requestedPath.startsWith(prefix)) {
20369
+ return false;
20370
+ }
20371
+ const remainder = requestedPath.slice(prefix.length);
20372
+ return !remainder.includes("/") || remainder === "/";
20373
+ }
20374
+ if (grantedPath.endsWith("/")) {
20375
+ return requestedPath.startsWith(grantedPath);
20376
+ }
20377
+ return grantedPath === requestedPath;
20378
+ }
19953
20379
  /**
19954
20380
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
19955
20381
  * {@link PermissionEntry}. Shares the implementation with the public
@@ -20532,6 +20958,7 @@ var import_sdk_core19 = require("@tinycloud/sdk-core");
20532
20958
  TinyCloud,
20533
20959
  TinyCloudNode,
20534
20960
  UnsupportedFeatureError,
20961
+ VAULT_PERMISSION_SERVICE,
20535
20962
  VaultHeaders,
20536
20963
  VaultPublicSpaceKVActions,
20537
20964
  VersionCheckError,
@@ -20548,6 +20975,8 @@ var import_sdk_core19 = require("@tinycloud/sdk-core");
20548
20975
  defaultSpaceCreationHandler,
20549
20976
  deserializeDelegation,
20550
20977
  expandActionShortNames,
20978
+ expandPermissionEntries,
20979
+ expandPermissionEntry,
20551
20980
  isCapabilitySubset,
20552
20981
  loadManifest,
20553
20982
  makePublicSpaceId,