@tinycloud/node-sdk 2.2.0-beta.6 → 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
@@ -17159,6 +17159,7 @@ import {
17159
17159
  DuckDbService as DuckDbService2,
17160
17160
  HooksService as HooksService2,
17161
17161
  DataVaultService,
17162
+ SecretsService,
17162
17163
  createVaultCrypto,
17163
17164
  ServiceContext as ServiceContext2,
17164
17165
  SilentNotificationHandler,
@@ -18241,6 +18242,151 @@ function extractSiweExpiration(siwe) {
18241
18242
  return d;
18242
18243
  }
18243
18244
 
18245
+ // src/NodeSecretsService.ts
18246
+ import {
18247
+ ErrorCodes,
18248
+ resolveManifest
18249
+ } from "@tinycloud/sdk-core";
18250
+ var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
18251
+ var SECRET_PREFIX = "secrets/";
18252
+ var SECRETS_SPACE = "secrets";
18253
+ function ok() {
18254
+ return { ok: true, data: void 0 };
18255
+ }
18256
+ function secretsError(code, message, cause) {
18257
+ return {
18258
+ ok: false,
18259
+ error: {
18260
+ code,
18261
+ service: "secrets",
18262
+ message,
18263
+ ...cause ? { cause } : {}
18264
+ }
18265
+ };
18266
+ }
18267
+ function actionUrn(action) {
18268
+ return `tinycloud.kv/${action}`;
18269
+ }
18270
+ function secretResourcePath(base2, name) {
18271
+ return `${base2}/${SECRET_PREFIX}${name}`;
18272
+ }
18273
+ function secretPermissionEntries(name, action) {
18274
+ return [
18275
+ {
18276
+ service: "tinycloud.kv",
18277
+ space: SECRETS_SPACE,
18278
+ path: secretResourcePath("keys", name),
18279
+ actions: [action],
18280
+ skipPrefix: true
18281
+ },
18282
+ {
18283
+ service: "tinycloud.kv",
18284
+ space: SECRETS_SPACE,
18285
+ path: secretResourcePath("vault", name),
18286
+ actions: [action],
18287
+ skipPrefix: true
18288
+ }
18289
+ ];
18290
+ }
18291
+ function isSecretsSpace(space) {
18292
+ return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
18293
+ }
18294
+ var NodeSecretsService = class {
18295
+ constructor(config) {
18296
+ this.config = config;
18297
+ this.shouldRestoreUnlock = false;
18298
+ }
18299
+ get vault() {
18300
+ return this.service.vault;
18301
+ }
18302
+ get isUnlocked() {
18303
+ return this.service.isUnlocked;
18304
+ }
18305
+ async unlock(signer) {
18306
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
18307
+ if (effectiveSigner !== void 0) {
18308
+ this.unlockSigner = effectiveSigner;
18309
+ }
18310
+ const result = await this.service.unlock(effectiveSigner);
18311
+ if (result.ok) {
18312
+ this.shouldRestoreUnlock = true;
18313
+ }
18314
+ return result;
18315
+ }
18316
+ lock() {
18317
+ this.shouldRestoreUnlock = false;
18318
+ this.service.lock();
18319
+ }
18320
+ get(name) {
18321
+ return this.service.get(name);
18322
+ }
18323
+ async put(name, value) {
18324
+ const permission = await this.ensureMutationPermission(name, "put");
18325
+ if (!permission.ok) return permission;
18326
+ return this.service.put(name, value);
18327
+ }
18328
+ async delete(name) {
18329
+ const permission = await this.ensureMutationPermission(name, "del");
18330
+ if (!permission.ok) return permission;
18331
+ return this.service.delete(name);
18332
+ }
18333
+ list() {
18334
+ return this.service.list();
18335
+ }
18336
+ get service() {
18337
+ return this.config.getService();
18338
+ }
18339
+ async ensureMutationPermission(name, action) {
18340
+ if (!SECRET_NAME_RE.test(name)) {
18341
+ return secretsError(
18342
+ ErrorCodes.INVALID_INPUT,
18343
+ `Invalid secret name ${JSON.stringify(name)}. Secret names must match ${SECRET_NAME_RE.source}.`
18344
+ );
18345
+ }
18346
+ if (this.hasMutationPermission(name, action)) {
18347
+ return ok();
18348
+ }
18349
+ if (!this.config.canEscalate()) {
18350
+ return secretsError(
18351
+ ErrorCodes.PERMISSION_DENIED,
18352
+ `Cannot autosign ${actionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
18353
+ );
18354
+ }
18355
+ try {
18356
+ await this.config.grantPermissions(secretPermissionEntries(name, action));
18357
+ return this.restoreUnlockAfterEscalation();
18358
+ } catch (error) {
18359
+ return secretsError(
18360
+ ErrorCodes.PERMISSION_DENIED,
18361
+ error instanceof Error ? error.message : `Autosign escalation for ${actionUrn(action)} on ${name} failed.`,
18362
+ error instanceof Error ? error : void 0
18363
+ );
18364
+ }
18365
+ }
18366
+ async restoreUnlockAfterEscalation() {
18367
+ if (!this.shouldRestoreUnlock) {
18368
+ return ok();
18369
+ }
18370
+ return this.service.unlock(this.unlockSigner);
18371
+ }
18372
+ hasMutationPermission(name, action) {
18373
+ const manifest = this.config.getManifest();
18374
+ if (manifest === void 0) {
18375
+ return false;
18376
+ }
18377
+ const manifests = Array.isArray(manifest) ? manifest : [manifest];
18378
+ const requiredAction = actionUrn(action);
18379
+ return manifests.some((entry) => {
18380
+ const resolved = resolveManifest(entry);
18381
+ return ["keys", "vault"].every(
18382
+ (base2) => resolved.resources.some(
18383
+ (resource) => resource.service === "tinycloud.kv" && isSecretsSpace(resource.space) && resource.path === secretResourcePath(base2, name) && resource.actions.includes(requiredAction)
18384
+ )
18385
+ );
18386
+ });
18387
+ }
18388
+ };
18389
+
18244
18390
  // src/TinyCloudNode.ts
18245
18391
  var DEFAULT_HOST = "https://node.tinycloud.xyz";
18246
18392
  var _TinyCloudNode = class _TinyCloudNode {
@@ -18272,6 +18418,30 @@ var _TinyCloudNode = class _TinyCloudNode {
18272
18418
  this.auth = null;
18273
18419
  this.tc = null;
18274
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
+ };
18275
18445
  this.explicitHost = config.host;
18276
18446
  this.config = {
18277
18447
  ...config,
@@ -18307,7 +18477,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18307
18477
  this._sharingService = new SharingService({
18308
18478
  hosts: [this.config.host],
18309
18479
  // session: undefined - not needed for receive()
18310
- invoke: this.wasmBindings.invoke,
18480
+ invoke: this.invokeWithRuntimePermissions,
18311
18481
  fetch: globalThis.fetch.bind(globalThis),
18312
18482
  keyProvider: this._keyProvider,
18313
18483
  registry: this._capabilityRegistry,
@@ -18375,7 +18545,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18375
18545
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
18376
18546
  });
18377
18547
  this.tc = new TinyCloud(this.auth, {
18378
- invokeAny: this.wasmBindings.invokeAny
18548
+ invokeAny: this.invokeAnyWithRuntimePermissions
18379
18549
  });
18380
18550
  }
18381
18551
  syncResolvedHostFromAuth() {
@@ -18495,7 +18665,12 @@ var _TinyCloudNode = class _TinyCloudNode {
18495
18665
  this._sql = void 0;
18496
18666
  this._duckdb = void 0;
18497
18667
  this._hooks = void 0;
18668
+ this._vault = void 0;
18669
+ this._baseSecrets = void 0;
18670
+ this._secrets = void 0;
18671
+ this._spaceService = void 0;
18498
18672
  this._serviceContext = void 0;
18673
+ this.runtimePermissionGrants = [];
18499
18674
  await this.tc.signIn(options);
18500
18675
  this.syncResolvedHostFromAuth();
18501
18676
  this.initializeServices();
@@ -18580,7 +18755,12 @@ var _TinyCloudNode = class _TinyCloudNode {
18580
18755
  this._sql = void 0;
18581
18756
  this._duckdb = void 0;
18582
18757
  this._hooks = void 0;
18758
+ this._vault = void 0;
18759
+ this._baseSecrets = void 0;
18760
+ this._secrets = void 0;
18761
+ this._spaceService = void 0;
18583
18762
  this._serviceContext = void 0;
18763
+ this.runtimePermissionGrants = [];
18584
18764
  if (sessionData.address) {
18585
18765
  this._address = sessionData.address;
18586
18766
  }
@@ -18588,8 +18768,8 @@ var _TinyCloudNode = class _TinyCloudNode {
18588
18768
  this._chainId = sessionData.chainId;
18589
18769
  }
18590
18770
  this._serviceContext = new ServiceContext2({
18591
- invoke: this.wasmBindings.invoke,
18592
- invokeAny: this.wasmBindings.invokeAny,
18771
+ invoke: this.invokeWithRuntimePermissions,
18772
+ invokeAny: this.invokeAnyWithRuntimePermissions,
18593
18773
  fetch: globalThis.fetch.bind(globalThis),
18594
18774
  hosts: [this.config.host]
18595
18775
  });
@@ -18613,41 +18793,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18613
18793
  jwk: sessionData.jwk
18614
18794
  };
18615
18795
  this._serviceContext.setSession(serviceSession);
18616
- const wasm = this.wasmBindings;
18617
- const vaultCrypto = createVaultCrypto({
18618
- vault_encrypt: wasm.vault_encrypt,
18619
- vault_decrypt: wasm.vault_decrypt,
18620
- vault_derive_key: wasm.vault_derive_key,
18621
- vault_x25519_from_seed: wasm.vault_x25519_from_seed,
18622
- vault_x25519_dh: wasm.vault_x25519_dh,
18623
- vault_random_bytes: wasm.vault_random_bytes,
18624
- vault_sha256: wasm.vault_sha256
18625
- });
18626
- const self2 = this;
18627
- this._vault = new DataVaultService({
18628
- spaceId: sessionData.spaceId,
18629
- crypto: vaultCrypto,
18630
- tc: {
18631
- kv: this._kv,
18632
- ensurePublicSpace: async () => {
18633
- try {
18634
- await self2.ensurePublicSpace();
18635
- return { ok: true, data: void 0 };
18636
- } catch (error) {
18637
- return { ok: false, error: { code: "STORAGE_ERROR", message: error instanceof Error ? error.message : String(error), service: "vault" } };
18638
- }
18639
- },
18640
- get publicKV() {
18641
- return self2._publicKV ?? self2.tc.publicKV;
18642
- },
18643
- readPublicSpace: (host, spaceId, key2) => TinyCloud.readPublicSpace(host, spaceId, key2),
18644
- makePublicSpaceId: TinyCloud.makePublicSpaceId,
18645
- did: this.did,
18646
- address: sessionData.address ?? this._address ?? "",
18647
- chainId: sessionData.chainId ?? this._chainId,
18648
- hosts: [this.config.host]
18649
- }
18650
- });
18796
+ this._vault = this.createVaultService(sessionData.spaceId, this._kv);
18651
18797
  this._vault.initialize(this._serviceContext);
18652
18798
  this._serviceContext.registerService("vault", this._vault);
18653
18799
  this.initializeV2Services(serviceSession);
@@ -18709,7 +18855,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18709
18855
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
18710
18856
  });
18711
18857
  this.tc = new TinyCloud(this.auth, {
18712
- invokeAny: this.wasmBindings.invokeAny
18858
+ invokeAny: this.invokeAnyWithRuntimePermissions
18713
18859
  });
18714
18860
  this.config.prefix = prefix;
18715
18861
  }
@@ -18753,7 +18899,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18753
18899
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
18754
18900
  });
18755
18901
  this.tc = new TinyCloud(this.auth, {
18756
- invokeAny: this.wasmBindings.invokeAny
18902
+ invokeAny: this.invokeAnyWithRuntimePermissions
18757
18903
  });
18758
18904
  this.config.prefix = prefix;
18759
18905
  }
@@ -18766,10 +18912,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18766
18912
  if (!session) {
18767
18913
  return;
18768
18914
  }
18769
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
18915
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
18770
18916
  this._serviceContext = new ServiceContext2({
18771
- invoke: this.wasmBindings.invoke,
18772
- invokeAny: this.wasmBindings.invokeAny,
18917
+ invoke: this.invokeWithRuntimePermissions,
18918
+ invokeAny: this.invokeAnyWithRuntimePermissions,
18773
18919
  fetch: globalThis.fetch.bind(globalThis),
18774
18920
  hosts: [this.config.host]
18775
18921
  });
@@ -18799,6 +18945,28 @@ var _TinyCloudNode = class _TinyCloudNode {
18799
18945
  };
18800
18946
  this._serviceContext.setSession(serviceSession);
18801
18947
  this.tc.serviceContext.setSession(serviceSession);
18948
+ this._vault = this.createVaultService(session.spaceId, this._kv);
18949
+ this._vault.initialize(this._serviceContext);
18950
+ this._serviceContext.registerService("vault", this._vault);
18951
+ this.initializeV2Services(serviceSession);
18952
+ }
18953
+ createSpaceScopedKVService(spaceId) {
18954
+ const kvService = new KVService2({});
18955
+ if (this._serviceContext) {
18956
+ const spaceScopedContext = new ServiceContext2({
18957
+ invoke: this._serviceContext.invoke,
18958
+ fetch: this._serviceContext.fetch,
18959
+ hosts: this._serviceContext.hosts
18960
+ });
18961
+ const session = this._serviceContext.session;
18962
+ if (session) {
18963
+ spaceScopedContext.setSession({ ...session, spaceId });
18964
+ }
18965
+ kvService.initialize(spaceScopedContext);
18966
+ }
18967
+ return kvService;
18968
+ }
18969
+ createVaultService(spaceId, kv) {
18802
18970
  const wasm = this.wasmBindings;
18803
18971
  const vaultCrypto = createVaultCrypto({
18804
18972
  vault_encrypt: wasm.vault_encrypt,
@@ -18810,11 +18978,11 @@ var _TinyCloudNode = class _TinyCloudNode {
18810
18978
  vault_sha256: wasm.vault_sha256
18811
18979
  });
18812
18980
  const self2 = this;
18813
- this._vault = new DataVaultService({
18814
- spaceId: session.spaceId,
18981
+ return new DataVaultService({
18982
+ spaceId,
18815
18983
  crypto: vaultCrypto,
18816
18984
  tc: {
18817
- kv: this._kv,
18985
+ kv,
18818
18986
  ensurePublicSpace: async () => {
18819
18987
  try {
18820
18988
  await self2.ensurePublicSpace();
@@ -18826,17 +18994,14 @@ var _TinyCloudNode = class _TinyCloudNode {
18826
18994
  get publicKV() {
18827
18995
  return self2._publicKV ?? self2.tc.publicKV;
18828
18996
  },
18829
- readPublicSpace: (host, spaceId, key2) => TinyCloud.readPublicSpace(host, spaceId, key2),
18997
+ readPublicSpace: (host, targetSpaceId, key2) => TinyCloud.readPublicSpace(host, targetSpaceId, key2),
18830
18998
  makePublicSpaceId: TinyCloud.makePublicSpaceId,
18831
18999
  did: this.did,
18832
- address: this._address,
19000
+ address: this._address ?? "",
18833
19001
  chainId: this._chainId,
18834
19002
  hosts: [this.config.host]
18835
19003
  }
18836
19004
  });
18837
- this._vault.initialize(this._serviceContext);
18838
- this._serviceContext.registerService("vault", this._vault);
18839
- this.initializeV2Services(serviceSession);
18840
19005
  }
18841
19006
  /**
18842
19007
  * Initialize the v2 delegation system services.
@@ -18920,7 +19085,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18920
19085
  this._delegationManager = new DelegationManager({
18921
19086
  hosts: [this.config.host],
18922
19087
  session: serviceSession,
18923
- invoke: this.wasmBindings.invoke,
19088
+ invoke: this.invokeWithRuntimePermissions,
18924
19089
  fetch: globalThis.fetch.bind(globalThis)
18925
19090
  });
18926
19091
  this._spaceService = new SpaceService({
@@ -18931,20 +19096,15 @@ var _TinyCloudNode = class _TinyCloudNode {
18931
19096
  capabilityRegistry: this._capabilityRegistry,
18932
19097
  userDid: this.did,
18933
19098
  createKVService: (spaceId) => {
18934
- const kvService = new KVService2({});
19099
+ return this.createSpaceScopedKVService(spaceId);
19100
+ },
19101
+ createVaultService: (spaceId) => {
19102
+ const kvService = this.createSpaceScopedKVService(spaceId);
19103
+ const vaultService = this.createVaultService(spaceId, kvService);
18935
19104
  if (this._serviceContext) {
18936
- const spaceScopedContext = new ServiceContext2({
18937
- invoke: this._serviceContext.invoke,
18938
- fetch: this._serviceContext.fetch,
18939
- hosts: this._serviceContext.hosts
18940
- });
18941
- const session = this._serviceContext.session;
18942
- if (session) {
18943
- spaceScopedContext.setSession({ ...session, spaceId });
18944
- }
18945
- kvService.initialize(spaceScopedContext);
19105
+ vaultService.initialize(this._serviceContext);
18946
19106
  }
18947
- return kvService;
19107
+ return vaultService;
18948
19108
  },
18949
19109
  // Enable space.delegations.create() via SIWE-based delegation
18950
19110
  createDelegation: async (params) => {
@@ -19181,6 +19341,33 @@ var _TinyCloudNode = class _TinyCloudNode {
19181
19341
  }
19182
19342
  return this._vault;
19183
19343
  }
19344
+ /**
19345
+ * App-facing secrets API backed by the `secrets` space vault.
19346
+ */
19347
+ get secrets() {
19348
+ if (!this._spaceService) {
19349
+ throw new Error("Not signed in. Call signIn() first.");
19350
+ }
19351
+ if (!this._secrets) {
19352
+ this._secrets = new NodeSecretsService({
19353
+ getService: () => this.getBaseSecrets(),
19354
+ getManifest: () => this.manifest,
19355
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
19356
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
19357
+ getUnlockSigner: () => this.signer ?? void 0
19358
+ });
19359
+ }
19360
+ return this._secrets;
19361
+ }
19362
+ getBaseSecrets() {
19363
+ if (!this._spaceService) {
19364
+ throw new Error("Not signed in. Call signIn() first.");
19365
+ }
19366
+ if (!this._baseSecrets) {
19367
+ this._baseSecrets = new SecretsService(() => this.space("secrets").vault);
19368
+ }
19369
+ return this._baseSecrets;
19370
+ }
19184
19371
  /**
19185
19372
  * Hooks write stream subscription API.
19186
19373
  */
@@ -19251,6 +19438,171 @@ var _TinyCloudNode = class _TinyCloudNode {
19251
19438
  }
19252
19439
  };
19253
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
+ }
19254
19606
  /**
19255
19607
  * Get the DelegationManager for delegation CRUD operations.
19256
19608
  *
@@ -19319,6 +19671,12 @@ var _TinyCloudNode = class _TinyCloudNode {
19319
19671
  get spaceService() {
19320
19672
  return this.spaces;
19321
19673
  }
19674
+ /**
19675
+ * Get a Space object by short name or full URI.
19676
+ */
19677
+ space(nameOrUri) {
19678
+ return this.spaces.get(nameOrUri);
19679
+ }
19322
19680
  /**
19323
19681
  * Get the SharingService for creating and receiving v2 sharing links.
19324
19682
  *
@@ -19433,7 +19791,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19433
19791
  if (this._serviceContext) {
19434
19792
  const publicKV = new KVService2({ prefix: "" });
19435
19793
  const publicContext = new ServiceContext2({
19436
- invoke: this.wasmBindings.invoke,
19794
+ invoke: this.invokeWithRuntimePermissions,
19437
19795
  fetch: this._serviceContext.fetch,
19438
19796
  hosts: this._serviceContext.hosts
19439
19797
  });
@@ -19521,8 +19879,9 @@ var _TinyCloudNode = class _TinyCloudNode {
19521
19879
  * Issue a delegation using the capability-chain flow.
19522
19880
  *
19523
19881
  * When every requested permission is a subset of the current
19524
- * session's recap, the delegation is signed by the session key via
19525
- * 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
19526
19885
  * {@link PermissionNotInManifestError} is raised (carrying the
19527
19886
  * missing entries) so the caller can trigger an escalation flow
19528
19887
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -19602,6 +19961,23 @@ var _TinyCloudNode = class _TinyCloudNode {
19602
19961
  );
19603
19962
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
19604
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
+ }
19605
19981
  throw new PermissionNotInManifestError(missing, granted);
19606
19982
  }
19607
19983
  const delegation = await this.createDelegationViaWasmPath(
@@ -19746,6 +20122,41 @@ var _TinyCloudNode = class _TinyCloudNode {
19746
20122
  host: this.config.host
19747
20123
  };
19748
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
+ }
19749
20160
  resolvePermissionSpace(space, session) {
19750
20161
  if (space === void 0) {
19751
20162
  return this.wasmBindings.makeSpaceId(
@@ -19762,6 +20173,223 @@ var _TinyCloudNode = class _TinyCloudNode {
19762
20173
  }
19763
20174
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
19764
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
+ }
19765
20393
  /**
19766
20394
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
19767
20395
  * {@link PermissionEntry}. Shares the implementation with the public
@@ -20287,7 +20915,7 @@ import {
20287
20915
  SessionExpiredError as SessionExpiredError2,
20288
20916
  ManifestValidationError,
20289
20917
  composeManifestRequest as composeManifestRequest2,
20290
- resolveManifest,
20918
+ resolveManifest as resolveManifest2,
20291
20919
  validateManifest,
20292
20920
  loadManifest,
20293
20921
  isCapabilitySubset as isCapabilitySubset2,
@@ -20324,7 +20952,8 @@ import {
20324
20952
  DataVaultService as DataVaultService2,
20325
20953
  VaultHeaders,
20326
20954
  VaultPublicSpaceKVActions,
20327
- createVaultCrypto as createVaultCrypto2
20955
+ createVaultCrypto as createVaultCrypto2,
20956
+ SecretsService as SecretsService2
20328
20957
  } from "@tinycloud/sdk-core";
20329
20958
  import { HooksService as HooksService3 } from "@tinycloud/sdk-core";
20330
20959
  import {
@@ -20383,6 +21012,7 @@ export {
20383
21012
  ProtocolMismatchError,
20384
21013
  SQLAction,
20385
21014
  SQLService3 as SQLService,
21015
+ SecretsService2 as SecretsService,
20386
21016
  ServiceContext3 as ServiceContext,
20387
21017
  SessionExpiredError2 as SessionExpiredError,
20388
21018
  SharingService2 as SharingService,
@@ -20414,7 +21044,7 @@ export {
20414
21044
  makePublicSpaceId2 as makePublicSpaceId,
20415
21045
  parseExpiry2 as parseExpiry,
20416
21046
  parseSpaceUri,
20417
- resolveManifest,
21047
+ resolveManifest2 as resolveManifest,
20418
21048
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
20419
21049
  serializeDelegation,
20420
21050
  validateManifest