@tinycloud/node-sdk 2.2.0-beta.1 → 2.2.0-beta.11

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,
@@ -17171,10 +17172,11 @@ import {
17171
17172
  ACCOUNT_REGISTRY_SPACE,
17172
17173
  PermissionNotInManifestError,
17173
17174
  SessionExpiredError,
17174
- expandActionShortNames,
17175
+ expandPermissionEntries as expandPermissionEntriesCore,
17175
17176
  isCapabilitySubset,
17176
17177
  parseRecapCapabilities,
17177
- SERVICE_LONG_TO_SHORT
17178
+ SERVICE_LONG_TO_SHORT,
17179
+ EXPIRY as EXPIRY3
17178
17180
  } from "@tinycloud/sdk-core";
17179
17181
 
17180
17182
  // src/authorization/NodeUserAuthorization.ts
@@ -17187,7 +17189,9 @@ import {
17187
17189
  DEFAULT_MANIFEST_SPACE,
17188
17190
  composeManifestRequest,
17189
17191
  resourceCapabilitiesToAbilitiesMap,
17190
- resourceCapabilitiesToSpaceAbilitiesMap
17192
+ resourceCapabilitiesToSpaceAbilitiesMap,
17193
+ resolveTinyCloudHosts,
17194
+ EXPIRY
17191
17195
  } from "@tinycloud/sdk-core";
17192
17196
 
17193
17197
  // src/authorization/strategies.ts
@@ -17318,12 +17322,12 @@ var NodeUserAuthorization = class {
17318
17322
  ]
17319
17323
  }
17320
17324
  };
17321
- this.sessionExpirationMs = config.sessionExpirationMs ?? 60 * 60 * 1e3;
17325
+ this.sessionExpirationMs = config.sessionExpirationMs ?? EXPIRY.SESSION_MS;
17322
17326
  this.autoCreateSpace = config.autoCreateSpace ?? false;
17323
17327
  this.spaceCreationHandler = config.spaceCreationHandler;
17324
- this.tinycloudHosts = config.tinycloudHosts ?? [
17325
- "https://node.tinycloud.xyz"
17326
- ];
17328
+ this.tinycloudHosts = config.tinycloudHosts;
17329
+ this.tinycloudRegistryUrl = config.tinycloudRegistryUrl;
17330
+ this.tinycloudFallbackHosts = config.tinycloudFallbackHosts;
17327
17331
  this.enablePublicSpace = config.enablePublicSpace ?? true;
17328
17332
  this.nonce = config.nonce;
17329
17333
  this.siweConfig = config.siweConfig;
@@ -17344,6 +17348,9 @@ var NodeUserAuthorization = class {
17344
17348
  get capabilityRequest() {
17345
17349
  return this.getCapabilityRequest();
17346
17350
  }
17351
+ get hosts() {
17352
+ return this.tinycloudHosts ? [...this.tinycloudHosts] : [];
17353
+ }
17347
17354
  /**
17348
17355
  * Install or replace the stored manifest. Takes effect on the next
17349
17356
  * `signIn()` call — the current session (if any) is not touched.
@@ -17368,6 +17375,26 @@ var NodeUserAuthorization = class {
17368
17375
  get tinyCloudSession() {
17369
17376
  return this._tinyCloudSession;
17370
17377
  }
17378
+ async resolveTinyCloudHostsForSignIn(address, chainId) {
17379
+ if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
17380
+ return;
17381
+ }
17382
+ const subject = `did:pkh:eip155:${chainId}:${address}`;
17383
+ const resolved = await resolveTinyCloudHosts(subject, {
17384
+ registryUrl: this.tinycloudRegistryUrl,
17385
+ fallbackHosts: this.tinycloudFallbackHosts
17386
+ });
17387
+ this.tinycloudHosts = resolved.hosts;
17388
+ }
17389
+ requireTinyCloudHosts() {
17390
+ if (!this.tinycloudHosts || this.tinycloudHosts.length === 0) {
17391
+ throw new Error("TinyCloud hosts have not been resolved. Call signIn() first.");
17392
+ }
17393
+ return this.tinycloudHosts;
17394
+ }
17395
+ get primaryTinyCloudHost() {
17396
+ return this.requireTinyCloudHosts()[0];
17397
+ }
17371
17398
  get nodeFeatures() {
17372
17399
  return this._nodeFeatures;
17373
17400
  }
@@ -17499,7 +17526,7 @@ var NodeUserAuthorization = class {
17499
17526
  if (!this._tinyCloudSession || !this._address || !this._chainId) {
17500
17527
  throw new Error("Must be signed in to host space");
17501
17528
  }
17502
- const host = this.tinycloudHosts[0];
17529
+ const host = this.primaryTinyCloudHost;
17503
17530
  const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
17504
17531
  const peerId = await fetchPeerId(host, spaceId);
17505
17532
  const siwe = this.wasm.generateHostSIWEMessage({
@@ -17541,7 +17568,7 @@ var NodeUserAuthorization = class {
17541
17568
  if (!this._tinyCloudSession) {
17542
17569
  throw new Error("Must be signed in to ensure space exists");
17543
17570
  }
17544
- const host = this.tinycloudHosts[0];
17571
+ const host = this.primaryTinyCloudHost;
17545
17572
  const primarySpaceId = this._tinyCloudSession.spaceId;
17546
17573
  const result = await activateSessionWithHost(
17547
17574
  host,
@@ -17650,6 +17677,7 @@ var NodeUserAuthorization = class {
17650
17677
  this._chainId = await this.signer.getChainId();
17651
17678
  const address = this.wasm.ensureEip55(this._address);
17652
17679
  const chainId = this._chainId;
17680
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
17653
17681
  const keyId = `session-${Date.now()}`;
17654
17682
  this.sessionManager.renameSessionKeyId("default", keyId);
17655
17683
  const jwkString = this.sessionManager.jwk(keyId);
@@ -17728,7 +17756,7 @@ var NodeUserAuthorization = class {
17728
17756
  this._address = address;
17729
17757
  this._chainId = chainId;
17730
17758
  const nodeInfo = await checkNodeInfo(
17731
- this.tinycloudHosts[0],
17759
+ this.primaryTinyCloudHost,
17732
17760
  this.wasm.protocolVersion()
17733
17761
  );
17734
17762
  this._nodeFeatures = nodeInfo.features;
@@ -17844,6 +17872,7 @@ var NodeUserAuthorization = class {
17844
17872
  });
17845
17873
  const address = this.wasm.ensureEip55(await this.signer.getAddress());
17846
17874
  const chainId = await this.signer.getChainId();
17875
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
17847
17876
  const clientSession = {
17848
17877
  address,
17849
17878
  walletAddress: address,
@@ -17893,7 +17922,7 @@ var NodeUserAuthorization = class {
17893
17922
  this._address = address;
17894
17923
  this._chainId = chainId;
17895
17924
  const nodeInfo = await checkNodeInfo(
17896
- this.tinycloudHosts[0],
17925
+ this.primaryTinyCloudHost,
17897
17926
  this.wasm.protocolVersion()
17898
17927
  );
17899
17928
  this._nodeFeatures = nodeInfo.features;
@@ -18057,6 +18086,24 @@ var DelegatedAccess = class {
18057
18086
  get hooks() {
18058
18087
  return this._hooks;
18059
18088
  }
18089
+ /**
18090
+ * Export the handles needed to rehydrate this activated delegation via
18091
+ * `TinyCloudNode.restoreSession(...)` in another process or after a
18092
+ * restart.
18093
+ *
18094
+ * See `RestorableSession` for lifetime caveats.
18095
+ */
18096
+ get restorable() {
18097
+ return {
18098
+ delegationHeader: this.session.delegationHeader,
18099
+ delegationCid: this.session.delegationCid,
18100
+ spaceId: this.session.spaceId,
18101
+ jwk: this.session.jwk,
18102
+ verificationMethod: this.session.verificationMethod,
18103
+ address: this.session.address,
18104
+ chainId: this.session.chainId
18105
+ };
18106
+ }
18060
18107
  };
18061
18108
 
18062
18109
  // src/keys/WasmKeyProvider.ts
@@ -18137,7 +18184,8 @@ function createWasmKeyProvider(sessionManager) {
18137
18184
  // src/delegateToHelpers.ts
18138
18185
  import {
18139
18186
  parseExpiry,
18140
- SiweMessage
18187
+ SiweMessage,
18188
+ EXPIRY as EXPIRY2
18141
18189
  } from "@tinycloud/sdk-core";
18142
18190
  function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
18143
18191
  const byService = /* @__PURE__ */ new Map();
@@ -18169,9 +18217,10 @@ function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
18169
18217
  }
18170
18218
  return entries;
18171
18219
  }
18220
+ var DEFAULT_DELEGATION_EXPIRY_MS = EXPIRY2.SESSION_MS;
18172
18221
  function resolveExpiryMs(expiry) {
18173
18222
  if (expiry === void 0) {
18174
- return 60 * 60 * 1e3;
18223
+ return DEFAULT_DELEGATION_EXPIRY_MS;
18175
18224
  }
18176
18225
  if (typeof expiry === "number") {
18177
18226
  if (!Number.isFinite(expiry) || expiry <= 0) {
@@ -18197,8 +18246,155 @@ function extractSiweExpiration(siwe) {
18197
18246
  return d;
18198
18247
  }
18199
18248
 
18249
+ // src/NodeSecretsService.ts
18250
+ import {
18251
+ ErrorCodes,
18252
+ resolveSecretPath,
18253
+ resolveManifest
18254
+ } from "@tinycloud/sdk-core";
18255
+ var SECRETS_SPACE = "secrets";
18256
+ function ok() {
18257
+ return { ok: true, data: void 0 };
18258
+ }
18259
+ function secretsError(code, message, cause) {
18260
+ return {
18261
+ ok: false,
18262
+ error: {
18263
+ code,
18264
+ service: "secrets",
18265
+ message,
18266
+ ...cause ? { cause } : {}
18267
+ }
18268
+ };
18269
+ }
18270
+ function displayActionUrn(action) {
18271
+ return action === "put" ? "tinycloud.vault/write" : "tinycloud.vault/delete";
18272
+ }
18273
+ function kvActionUrn(action) {
18274
+ return `tinycloud.kv/${action}`;
18275
+ }
18276
+ function vaultMutationAction(action) {
18277
+ return action === "put" ? "write" : "delete";
18278
+ }
18279
+ function secretPermissionEntries(name, options, action) {
18280
+ const secretPath = resolveSecretPath(name, options);
18281
+ return [
18282
+ {
18283
+ service: "tinycloud.vault",
18284
+ space: SECRETS_SPACE,
18285
+ path: secretPath.vaultKey,
18286
+ actions: [vaultMutationAction(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, options) {
18321
+ return options === void 0 ? this.service.get(name) : this.service.get(name, options);
18322
+ }
18323
+ async put(name, value, options) {
18324
+ const permission = await this.ensureMutationPermission(name, options, "put");
18325
+ if (!permission.ok) return permission;
18326
+ return options === void 0 ? this.service.put(name, value) : this.service.put(name, value, options);
18327
+ }
18328
+ async delete(name, options) {
18329
+ const permission = await this.ensureMutationPermission(name, options, "del");
18330
+ if (!permission.ok) return permission;
18331
+ return options === void 0 ? this.service.delete(name) : this.service.delete(name, options);
18332
+ }
18333
+ list(options) {
18334
+ return options === void 0 ? this.service.list() : this.service.list(options);
18335
+ }
18336
+ get service() {
18337
+ return this.config.getService();
18338
+ }
18339
+ async ensureMutationPermission(name, options, action) {
18340
+ let permissionEntries;
18341
+ try {
18342
+ permissionEntries = secretPermissionEntries(name, options, action);
18343
+ } catch (error) {
18344
+ return secretsError(
18345
+ ErrorCodes.INVALID_INPUT,
18346
+ error instanceof Error ? error.message : String(error),
18347
+ error instanceof Error ? error : void 0
18348
+ );
18349
+ }
18350
+ if (this.hasMutationPermission(name, options, action)) {
18351
+ return ok();
18352
+ }
18353
+ if (!this.config.canEscalate()) {
18354
+ return secretsError(
18355
+ ErrorCodes.PERMISSION_DENIED,
18356
+ `Cannot autosign ${displayActionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
18357
+ );
18358
+ }
18359
+ try {
18360
+ await this.config.grantPermissions(permissionEntries);
18361
+ return this.restoreUnlockAfterEscalation();
18362
+ } catch (error) {
18363
+ return secretsError(
18364
+ ErrorCodes.PERMISSION_DENIED,
18365
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${name} failed.`,
18366
+ error instanceof Error ? error : void 0
18367
+ );
18368
+ }
18369
+ }
18370
+ async restoreUnlockAfterEscalation() {
18371
+ if (!this.shouldRestoreUnlock) {
18372
+ return ok();
18373
+ }
18374
+ return this.service.unlock(this.unlockSigner);
18375
+ }
18376
+ hasMutationPermission(name, options, action) {
18377
+ const manifest = this.config.getManifest();
18378
+ if (manifest === void 0) {
18379
+ return false;
18380
+ }
18381
+ const manifests = Array.isArray(manifest) ? manifest : [manifest];
18382
+ const requiredAction = kvActionUrn(action);
18383
+ const secretPath = resolveSecretPath(name, options);
18384
+ return manifests.some((entry) => {
18385
+ const resolved = resolveManifest(entry);
18386
+ return ["keys", "vault"].every(
18387
+ (base2) => resolved.resources.some(
18388
+ (resource) => resource.service === "tinycloud.kv" && isSecretsSpace(resource.space) && resource.path === secretPath.permissionPaths[base2] && resource.actions.includes(requiredAction)
18389
+ )
18390
+ );
18391
+ });
18392
+ }
18393
+ };
18394
+
18200
18395
  // src/TinyCloudNode.ts
18201
18396
  var DEFAULT_HOST = "https://node.tinycloud.xyz";
18397
+ var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
18202
18398
  var _TinyCloudNode = class _TinyCloudNode {
18203
18399
  /**
18204
18400
  * Create a new TinyCloudNode instance.
@@ -18228,6 +18424,31 @@ var _TinyCloudNode = class _TinyCloudNode {
18228
18424
  this.auth = null;
18229
18425
  this.tc = null;
18230
18426
  this._chainId = 1;
18427
+ this.runtimePermissionGrants = [];
18428
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
18429
+ return this.wasmBindings.invoke(
18430
+ this.selectInvocationSession(session, service, path, action),
18431
+ service,
18432
+ path,
18433
+ action,
18434
+ facts
18435
+ );
18436
+ };
18437
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
18438
+ if (!this.wasmBindings.invokeAny) {
18439
+ throw new Error("WASM binding does not support invokeAny");
18440
+ }
18441
+ const grant = this.findGrantForOperations(
18442
+ entries.map((entry) => ({
18443
+ spaceId: entry.spaceId,
18444
+ service: this.invocationServiceName(entry.service),
18445
+ path: entry.path,
18446
+ action: entry.action
18447
+ }))
18448
+ );
18449
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
18450
+ };
18451
+ this.explicitHost = config.host;
18231
18452
  this.config = {
18232
18453
  ...config,
18233
18454
  host: config.host ?? DEFAULT_HOST
@@ -18262,7 +18483,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18262
18483
  this._sharingService = new SharingService({
18263
18484
  hosts: [this.config.host],
18264
18485
  // session: undefined - not needed for receive()
18265
- invoke: this.wasmBindings.invoke,
18486
+ invoke: this.invokeWithRuntimePermissions,
18266
18487
  fetch: globalThis.fetch.bind(globalThis),
18267
18488
  keyProvider: this._keyProvider,
18268
18489
  registry: this._capabilityRegistry,
@@ -18309,7 +18530,6 @@ var _TinyCloudNode = class _TinyCloudNode {
18309
18530
  * @internal
18310
18531
  */
18311
18532
  setupAuth(config) {
18312
- const host = this.config.host;
18313
18533
  this.auth = new NodeUserAuthorization({
18314
18534
  signer: this.signer,
18315
18535
  signStrategy: { type: "auto-sign" },
@@ -18317,8 +18537,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18317
18537
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
18318
18538
  domain: this.siweDomain,
18319
18539
  spacePrefix: config.prefix,
18320
- sessionExpirationMs: config.sessionExpirationMs ?? 60 * 60 * 1e3,
18321
- tinycloudHosts: [host],
18540
+ sessionExpirationMs: config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
18541
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
18542
+ tinycloudRegistryUrl: config.tinycloudRegistryUrl,
18543
+ tinycloudFallbackHosts: config.tinycloudFallbackHosts,
18322
18544
  autoCreateSpace: config.autoCreateSpace,
18323
18545
  enablePublicSpace: config.enablePublicSpace ?? true,
18324
18546
  spaceCreationHandler: config.spaceCreationHandler,
@@ -18329,9 +18551,15 @@ var _TinyCloudNode = class _TinyCloudNode {
18329
18551
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
18330
18552
  });
18331
18553
  this.tc = new TinyCloud(this.auth, {
18332
- invokeAny: this.wasmBindings.invokeAny
18554
+ invokeAny: this.invokeAnyWithRuntimePermissions
18333
18555
  });
18334
18556
  }
18557
+ syncResolvedHostFromAuth() {
18558
+ const host = this.auth?.hosts[0];
18559
+ if (host) {
18560
+ this.config.host = host;
18561
+ }
18562
+ }
18335
18563
  /**
18336
18564
  * Install or replace the manifest that drives the SIWE recap at
18337
18565
  * sign-in. Takes effect on the next `signIn()` call — the current
@@ -18369,6 +18597,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18369
18597
  get capabilityRequest() {
18370
18598
  return this.auth?.capabilityRequest;
18371
18599
  }
18600
+ get hosts() {
18601
+ const authHosts = this.auth?.hosts ?? [];
18602
+ return authHosts.length > 0 ? authHosts : [this.config.host];
18603
+ }
18372
18604
  /**
18373
18605
  * Get the primary identity DID for this user.
18374
18606
  * - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
@@ -18439,8 +18671,14 @@ var _TinyCloudNode = class _TinyCloudNode {
18439
18671
  this._sql = void 0;
18440
18672
  this._duckdb = void 0;
18441
18673
  this._hooks = void 0;
18674
+ this._vault = void 0;
18675
+ this._baseSecrets = void 0;
18676
+ this._secrets = void 0;
18677
+ this._spaceService = void 0;
18442
18678
  this._serviceContext = void 0;
18679
+ this.runtimePermissionGrants = [];
18443
18680
  await this.tc.signIn(options);
18681
+ this.syncResolvedHostFromAuth();
18444
18682
  this.initializeServices();
18445
18683
  await this.writeManifestRegistryRecords();
18446
18684
  this.notificationHandler.success("Successfully signed in");
@@ -18460,7 +18698,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18460
18698
  throw new Error("Manifest registry write requires wallet mode");
18461
18699
  }
18462
18700
  const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE);
18463
- await this.auth.hostOwnedSpace(accountSpaceId);
18701
+ await this.ensureOwnedSpaceHosted(accountSpaceId);
18464
18702
  const accountKV = this.spaces.get(accountSpaceId).kv;
18465
18703
  for (const record of request.registryRecords) {
18466
18704
  const result = await accountKV.put(record.key, {
@@ -18475,6 +18713,39 @@ var _TinyCloudNode = class _TinyCloudNode {
18475
18713
  }
18476
18714
  }
18477
18715
  }
18716
+ async ensureOwnedSpaceHosted(spaceId) {
18717
+ if (!this.auth) {
18718
+ throw new Error("Owned space hosting requires wallet mode");
18719
+ }
18720
+ const session = this.auth.tinyCloudSession;
18721
+ if (!session) {
18722
+ throw new Error("Owned space hosting requires an active session");
18723
+ }
18724
+ const host = this.hosts[0] ?? this.config.host;
18725
+ if (!host) {
18726
+ throw new Error("Owned space hosting requires a TinyCloud host");
18727
+ }
18728
+ const activation = await activateSessionWithHost2(host, session.delegationHeader);
18729
+ if (activation.success && !activation.skipped?.includes(spaceId)) {
18730
+ return;
18731
+ }
18732
+ if (!activation.success && activation.status !== 404) {
18733
+ throw new Error(
18734
+ `Failed to check owned space ${spaceId}: ${activation.error ?? activation.status}`
18735
+ );
18736
+ }
18737
+ const created = await this.auth.hostOwnedSpace(spaceId);
18738
+ if (!created) {
18739
+ throw new Error(`Failed to create owned space: ${spaceId}`);
18740
+ }
18741
+ await new Promise((resolve) => setTimeout(resolve, 100));
18742
+ const retry = await activateSessionWithHost2(host, session.delegationHeader);
18743
+ if (!retry.success || retry.skipped?.includes(spaceId)) {
18744
+ throw new Error(
18745
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.error ?? "space was skipped"}`
18746
+ );
18747
+ }
18748
+ }
18478
18749
  /**
18479
18750
  * Restore a previously established session from stored delegation data.
18480
18751
  *
@@ -18490,7 +18761,12 @@ var _TinyCloudNode = class _TinyCloudNode {
18490
18761
  this._sql = void 0;
18491
18762
  this._duckdb = void 0;
18492
18763
  this._hooks = void 0;
18764
+ this._vault = void 0;
18765
+ this._baseSecrets = void 0;
18766
+ this._secrets = void 0;
18767
+ this._spaceService = void 0;
18493
18768
  this._serviceContext = void 0;
18769
+ this.runtimePermissionGrants = [];
18494
18770
  if (sessionData.address) {
18495
18771
  this._address = sessionData.address;
18496
18772
  }
@@ -18498,8 +18774,8 @@ var _TinyCloudNode = class _TinyCloudNode {
18498
18774
  this._chainId = sessionData.chainId;
18499
18775
  }
18500
18776
  this._serviceContext = new ServiceContext2({
18501
- invoke: this.wasmBindings.invoke,
18502
- invokeAny: this.wasmBindings.invokeAny,
18777
+ invoke: this.invokeWithRuntimePermissions,
18778
+ invokeAny: this.invokeAnyWithRuntimePermissions,
18503
18779
  fetch: globalThis.fetch.bind(globalThis),
18504
18780
  hosts: [this.config.host]
18505
18781
  });
@@ -18523,41 +18799,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18523
18799
  jwk: sessionData.jwk
18524
18800
  };
18525
18801
  this._serviceContext.setSession(serviceSession);
18526
- const wasm = this.wasmBindings;
18527
- const vaultCrypto = createVaultCrypto({
18528
- vault_encrypt: wasm.vault_encrypt,
18529
- vault_decrypt: wasm.vault_decrypt,
18530
- vault_derive_key: wasm.vault_derive_key,
18531
- vault_x25519_from_seed: wasm.vault_x25519_from_seed,
18532
- vault_x25519_dh: wasm.vault_x25519_dh,
18533
- vault_random_bytes: wasm.vault_random_bytes,
18534
- vault_sha256: wasm.vault_sha256
18535
- });
18536
- const self2 = this;
18537
- this._vault = new DataVaultService({
18538
- spaceId: sessionData.spaceId,
18539
- crypto: vaultCrypto,
18540
- tc: {
18541
- kv: this._kv,
18542
- ensurePublicSpace: async () => {
18543
- try {
18544
- await self2.ensurePublicSpace();
18545
- return { ok: true, data: void 0 };
18546
- } catch (error) {
18547
- return { ok: false, error: { code: "STORAGE_ERROR", message: error instanceof Error ? error.message : String(error), service: "vault" } };
18548
- }
18549
- },
18550
- get publicKV() {
18551
- return self2._publicKV ?? self2.tc.publicKV;
18552
- },
18553
- readPublicSpace: (host, spaceId, key2) => TinyCloud.readPublicSpace(host, spaceId, key2),
18554
- makePublicSpaceId: TinyCloud.makePublicSpaceId,
18555
- did: this.did,
18556
- address: sessionData.address ?? this._address ?? "",
18557
- chainId: sessionData.chainId ?? this._chainId,
18558
- hosts: [this.config.host]
18559
- }
18560
- });
18802
+ this._vault = this.createVaultService(sessionData.spaceId, this._kv);
18561
18803
  this._vault.initialize(this._serviceContext);
18562
18804
  this._serviceContext.registerService("vault", this._vault);
18563
18805
  this.initializeV2Services(serviceSession);
@@ -18592,7 +18834,6 @@ var _TinyCloudNode = class _TinyCloudNode {
18592
18834
  throw new Error("Wallet already connected. Cannot connect another wallet.");
18593
18835
  }
18594
18836
  const prefix = options?.prefix ?? "default";
18595
- const host = this.config.host;
18596
18837
  if (!_TinyCloudNode.nodeDefaults) {
18597
18838
  throw new Error(
18598
18839
  "connectWallet() requires PrivateKeySigner. Use connectSigner() instead, or import from '@tinycloud/node-sdk' (not '/core') for automatic Node.js defaults."
@@ -18606,8 +18847,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18606
18847
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
18607
18848
  domain: this.siweDomain,
18608
18849
  spacePrefix: prefix,
18609
- sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
18610
- tinycloudHosts: [host],
18850
+ sessionExpirationMs: this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
18851
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
18852
+ tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
18853
+ tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
18611
18854
  autoCreateSpace: this.config.autoCreateSpace,
18612
18855
  enablePublicSpace: this.config.enablePublicSpace ?? true,
18613
18856
  spaceCreationHandler: this.config.spaceCreationHandler,
@@ -18618,7 +18861,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18618
18861
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
18619
18862
  });
18620
18863
  this.tc = new TinyCloud(this.auth, {
18621
- invokeAny: this.wasmBindings.invokeAny
18864
+ invokeAny: this.invokeAnyWithRuntimePermissions
18622
18865
  });
18623
18866
  this.config.prefix = prefix;
18624
18867
  }
@@ -18640,7 +18883,6 @@ var _TinyCloudNode = class _TinyCloudNode {
18640
18883
  throw new Error("Signer already connected. Cannot connect another signer.");
18641
18884
  }
18642
18885
  const prefix = options?.prefix ?? "default";
18643
- const host = this.config.host;
18644
18886
  this.signer = signer;
18645
18887
  this.auth = new NodeUserAuthorization({
18646
18888
  signer: this.signer,
@@ -18649,8 +18891,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18649
18891
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
18650
18892
  domain: this.siweDomain,
18651
18893
  spacePrefix: prefix,
18652
- sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
18653
- tinycloudHosts: [host],
18894
+ sessionExpirationMs: this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
18895
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
18896
+ tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
18897
+ tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
18654
18898
  autoCreateSpace: this.config.autoCreateSpace,
18655
18899
  enablePublicSpace: this.config.enablePublicSpace ?? true,
18656
18900
  spaceCreationHandler: this.config.spaceCreationHandler,
@@ -18661,7 +18905,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18661
18905
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
18662
18906
  });
18663
18907
  this.tc = new TinyCloud(this.auth, {
18664
- invokeAny: this.wasmBindings.invokeAny
18908
+ invokeAny: this.invokeAnyWithRuntimePermissions
18665
18909
  });
18666
18910
  this.config.prefix = prefix;
18667
18911
  }
@@ -18674,10 +18918,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18674
18918
  if (!session) {
18675
18919
  return;
18676
18920
  }
18677
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
18921
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
18678
18922
  this._serviceContext = new ServiceContext2({
18679
- invoke: this.wasmBindings.invoke,
18680
- invokeAny: this.wasmBindings.invokeAny,
18923
+ invoke: this.invokeWithRuntimePermissions,
18924
+ invokeAny: this.invokeAnyWithRuntimePermissions,
18681
18925
  fetch: globalThis.fetch.bind(globalThis),
18682
18926
  hosts: [this.config.host]
18683
18927
  });
@@ -18707,6 +18951,28 @@ var _TinyCloudNode = class _TinyCloudNode {
18707
18951
  };
18708
18952
  this._serviceContext.setSession(serviceSession);
18709
18953
  this.tc.serviceContext.setSession(serviceSession);
18954
+ this._vault = this.createVaultService(session.spaceId, this._kv);
18955
+ this._vault.initialize(this._serviceContext);
18956
+ this._serviceContext.registerService("vault", this._vault);
18957
+ this.initializeV2Services(serviceSession);
18958
+ }
18959
+ createSpaceScopedKVService(spaceId) {
18960
+ const kvService = new KVService2({});
18961
+ if (this._serviceContext) {
18962
+ const spaceScopedContext = new ServiceContext2({
18963
+ invoke: this._serviceContext.invoke,
18964
+ fetch: this._serviceContext.fetch,
18965
+ hosts: this._serviceContext.hosts
18966
+ });
18967
+ const session = this._serviceContext.session;
18968
+ if (session) {
18969
+ spaceScopedContext.setSession({ ...session, spaceId });
18970
+ }
18971
+ kvService.initialize(spaceScopedContext);
18972
+ }
18973
+ return kvService;
18974
+ }
18975
+ createVaultService(spaceId, kv) {
18710
18976
  const wasm = this.wasmBindings;
18711
18977
  const vaultCrypto = createVaultCrypto({
18712
18978
  vault_encrypt: wasm.vault_encrypt,
@@ -18718,11 +18984,11 @@ var _TinyCloudNode = class _TinyCloudNode {
18718
18984
  vault_sha256: wasm.vault_sha256
18719
18985
  });
18720
18986
  const self2 = this;
18721
- this._vault = new DataVaultService({
18722
- spaceId: session.spaceId,
18987
+ return new DataVaultService({
18988
+ spaceId,
18723
18989
  crypto: vaultCrypto,
18724
18990
  tc: {
18725
- kv: this._kv,
18991
+ kv,
18726
18992
  ensurePublicSpace: async () => {
18727
18993
  try {
18728
18994
  await self2.ensurePublicSpace();
@@ -18734,17 +19000,14 @@ var _TinyCloudNode = class _TinyCloudNode {
18734
19000
  get publicKV() {
18735
19001
  return self2._publicKV ?? self2.tc.publicKV;
18736
19002
  },
18737
- readPublicSpace: (host, spaceId, key2) => TinyCloud.readPublicSpace(host, spaceId, key2),
19003
+ readPublicSpace: (host, targetSpaceId, key2) => TinyCloud.readPublicSpace(host, targetSpaceId, key2),
18738
19004
  makePublicSpaceId: TinyCloud.makePublicSpaceId,
18739
19005
  did: this.did,
18740
- address: this._address,
19006
+ address: this._address ?? "",
18741
19007
  chainId: this._chainId,
18742
19008
  hosts: [this.config.host]
18743
19009
  }
18744
19010
  });
18745
- this._vault.initialize(this._serviceContext);
18746
- this._serviceContext.registerService("vault", this._vault);
18747
- this.initializeV2Services(serviceSession);
18748
19011
  }
18749
19012
  /**
18750
19013
  * Initialize the v2 delegation system services.
@@ -18828,7 +19091,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18828
19091
  this._delegationManager = new DelegationManager({
18829
19092
  hosts: [this.config.host],
18830
19093
  session: serviceSession,
18831
- invoke: this.wasmBindings.invoke,
19094
+ invoke: this.invokeWithRuntimePermissions,
18832
19095
  fetch: globalThis.fetch.bind(globalThis)
18833
19096
  });
18834
19097
  this._spaceService = new SpaceService({
@@ -18839,20 +19102,15 @@ var _TinyCloudNode = class _TinyCloudNode {
18839
19102
  capabilityRegistry: this._capabilityRegistry,
18840
19103
  userDid: this.did,
18841
19104
  createKVService: (spaceId) => {
18842
- const kvService = new KVService2({});
19105
+ return this.createSpaceScopedKVService(spaceId);
19106
+ },
19107
+ createVaultService: (spaceId) => {
19108
+ const kvService = this.createSpaceScopedKVService(spaceId);
19109
+ const vaultService = this.createVaultService(spaceId, kvService);
18843
19110
  if (this._serviceContext) {
18844
- const spaceScopedContext = new ServiceContext2({
18845
- invoke: this._serviceContext.invoke,
18846
- fetch: this._serviceContext.fetch,
18847
- hosts: this._serviceContext.hosts
18848
- });
18849
- const session = this._serviceContext.session;
18850
- if (session) {
18851
- spaceScopedContext.setSession({ ...session, spaceId });
18852
- }
18853
- kvService.initialize(spaceScopedContext);
19111
+ vaultService.initialize(this._serviceContext);
18854
19112
  }
18855
- return kvService;
19113
+ return vaultService;
18856
19114
  },
18857
19115
  // Enable space.delegations.create() via SIWE-based delegation
18858
19116
  createDelegation: async (params) => {
@@ -18909,7 +19167,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18909
19167
  * @internal
18910
19168
  */
18911
19169
  getSessionExpiry() {
18912
- const expirationMs = this.config.sessionExpirationMs ?? 60 * 60 * 1e3;
19170
+ const expirationMs = this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS;
18913
19171
  return new Date(Date.now() + expirationMs);
18914
19172
  }
18915
19173
  /**
@@ -19066,6 +19324,34 @@ var _TinyCloudNode = class _TinyCloudNode {
19066
19324
  }
19067
19325
  return this._sql;
19068
19326
  }
19327
+ /**
19328
+ * Get an SQL service scoped to a specific space.
19329
+ *
19330
+ * Mirrors {@link SpaceService}'s per-space KV factory: clones the active
19331
+ * service context and overrides its session's spaceId so that subsequent
19332
+ * `sql/<dbName>/<action>` invocations route to that space. Useful when
19333
+ * the caller already holds a delegation covering the target space (e.g.
19334
+ * via {@link grantRuntimePermissions} or {@link useRuntimeDelegation})
19335
+ * but the SDK's per-space SQL surface isn't otherwise exposed.
19336
+ *
19337
+ * Does NOT auto-create the space.
19338
+ *
19339
+ * @param spaceId - Full space URI (`tinycloud:pkh:eip155:<chain>:<addr>:<name>`).
19340
+ */
19341
+ sqlForSpace(spaceId) {
19342
+ if (!this._serviceContext || !this._serviceContext.session) {
19343
+ throw new Error("Not signed in. Call signIn() first.");
19344
+ }
19345
+ const sql = new SQLService2({});
19346
+ const spaceScopedContext = new ServiceContext2({
19347
+ invoke: this._serviceContext.invoke,
19348
+ fetch: this._serviceContext.fetch,
19349
+ hosts: this._serviceContext.hosts
19350
+ });
19351
+ spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
19352
+ sql.initialize(spaceScopedContext);
19353
+ return sql;
19354
+ }
19069
19355
  /**
19070
19356
  * DuckDB database operations on this user's space.
19071
19357
  */
@@ -19089,6 +19375,33 @@ var _TinyCloudNode = class _TinyCloudNode {
19089
19375
  }
19090
19376
  return this._vault;
19091
19377
  }
19378
+ /**
19379
+ * App-facing secrets API backed by the `secrets` space vault.
19380
+ */
19381
+ get secrets() {
19382
+ if (!this._spaceService) {
19383
+ throw new Error("Not signed in. Call signIn() first.");
19384
+ }
19385
+ if (!this._secrets) {
19386
+ this._secrets = new NodeSecretsService({
19387
+ getService: () => this.getBaseSecrets(),
19388
+ getManifest: () => this.manifest,
19389
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
19390
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
19391
+ getUnlockSigner: () => this.signer ?? void 0
19392
+ });
19393
+ }
19394
+ return this._secrets;
19395
+ }
19396
+ getBaseSecrets() {
19397
+ if (!this._spaceService) {
19398
+ throw new Error("Not signed in. Call signIn() first.");
19399
+ }
19400
+ if (!this._baseSecrets) {
19401
+ this._baseSecrets = new SecretsService(() => this.space("secrets").vault);
19402
+ }
19403
+ return this._baseSecrets;
19404
+ }
19092
19405
  /**
19093
19406
  * Hooks write stream subscription API.
19094
19407
  */
@@ -19159,6 +19472,171 @@ var _TinyCloudNode = class _TinyCloudNode {
19159
19472
  }
19160
19473
  };
19161
19474
  }
19475
+ /**
19476
+ * Check whether the current session or an approved runtime delegation covers
19477
+ * every requested permission.
19478
+ */
19479
+ hasRuntimePermissions(permissions) {
19480
+ const session = this.auth?.tinyCloudSession;
19481
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
19482
+ return false;
19483
+ }
19484
+ const expanded = this.expandPermissionEntries(permissions);
19485
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
19486
+ return true;
19487
+ }
19488
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
19489
+ }
19490
+ /**
19491
+ * Return installed runtime permission delegations. When `permissions` is
19492
+ * provided, only delegations currently covering those permissions are
19493
+ * returned. Base-session manifest permissions are not represented here.
19494
+ */
19495
+ getRuntimePermissionDelegations(permissions) {
19496
+ this.pruneExpiredRuntimePermissionGrants();
19497
+ if (permissions === void 0) {
19498
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
19499
+ }
19500
+ const session = this.auth?.tinyCloudSession;
19501
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
19502
+ return [];
19503
+ }
19504
+ const expanded = this.expandPermissionEntries(permissions);
19505
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
19506
+ (grant) => grant.delegation
19507
+ );
19508
+ }
19509
+ /**
19510
+ * Install a portable runtime permission delegation into this SDK instance so
19511
+ * matching service calls and downstream `delegateTo()` calls can use it.
19512
+ */
19513
+ async useRuntimeDelegation(delegation) {
19514
+ const session = this.auth?.tinyCloudSession;
19515
+ if (!session) {
19516
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
19517
+ }
19518
+ if (delegation.expiry.getTime() <= Date.now()) {
19519
+ throw new SessionExpiredError(delegation.expiry);
19520
+ }
19521
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
19522
+ if (!expectedDids.has(delegation.delegateDID)) {
19523
+ throw new Error(
19524
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
19525
+ );
19526
+ }
19527
+ const targetHost = delegation.host ?? this.config.host;
19528
+ const activateResult = await activateSessionWithHost2(
19529
+ targetHost,
19530
+ delegation.delegationHeader
19531
+ );
19532
+ if (!activateResult.success) {
19533
+ throw new Error(
19534
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
19535
+ );
19536
+ }
19537
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
19538
+ (grant) => grant.delegation.cid !== delegation.cid
19539
+ );
19540
+ this.runtimePermissionGrants.push(
19541
+ this.runtimeGrantFromDelegation(delegation, session)
19542
+ );
19543
+ }
19544
+ /**
19545
+ * Store additional permissions as narrow delegations to the current session
19546
+ * key. Future service invocations automatically use a stored delegation when
19547
+ * its `(space, service, path, action)` covers the request.
19548
+ */
19549
+ async grantRuntimePermissions(permissions, options) {
19550
+ if (!Array.isArray(permissions) || permissions.length === 0) {
19551
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
19552
+ }
19553
+ const session = this.auth?.tinyCloudSession;
19554
+ if (!session) {
19555
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
19556
+ }
19557
+ const sessionExpiry = extractSiweExpiration(session.siwe);
19558
+ if (sessionExpiry !== void 0) {
19559
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
19560
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
19561
+ throw new SessionExpiredError(sessionExpiry);
19562
+ }
19563
+ }
19564
+ const expanded = this.expandPermissionEntries(permissions);
19565
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
19566
+ return [];
19567
+ }
19568
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
19569
+ if (existingGrants.length > 0) {
19570
+ return existingGrants.map((grant) => grant.delegation);
19571
+ }
19572
+ if (!this.signer) {
19573
+ throw new Error(
19574
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
19575
+ );
19576
+ }
19577
+ const bySpace = /* @__PURE__ */ new Map();
19578
+ for (const entry of expanded) {
19579
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
19580
+ const current = bySpace.get(spaceId) ?? [];
19581
+ current.push(entry);
19582
+ bySpace.set(spaceId, current);
19583
+ }
19584
+ const now = /* @__PURE__ */ new Date();
19585
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
19586
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
19587
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
19588
+ expiresAt = sessionExpiry;
19589
+ }
19590
+ const delegations = [];
19591
+ for (const [spaceId, entries] of bySpace) {
19592
+ const abilities = this.permissionsToAbilities(entries);
19593
+ const prepared = this.wasmBindings.prepareSession({
19594
+ abilities,
19595
+ address: this.wasmBindings.ensureEip55(session.address),
19596
+ chainId: session.chainId,
19597
+ domain: this.siweDomain,
19598
+ issuedAt: now.toISOString(),
19599
+ expirationTime: expiresAt.toISOString(),
19600
+ spaceId,
19601
+ jwk: session.jwk
19602
+ });
19603
+ const signature2 = await this.signer.signMessage(prepared.siwe);
19604
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
19605
+ ...prepared,
19606
+ signature: signature2
19607
+ });
19608
+ const activateResult = await activateSessionWithHost2(
19609
+ this.config.host,
19610
+ delegatedSession.delegationHeader
19611
+ );
19612
+ if (!activateResult.success) {
19613
+ throw new Error(
19614
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
19615
+ );
19616
+ }
19617
+ const delegation = this.runtimeDelegationFromSession(
19618
+ delegatedSession,
19619
+ entries,
19620
+ spaceId,
19621
+ session,
19622
+ expiresAt
19623
+ );
19624
+ this.runtimePermissionGrants.push({
19625
+ session: {
19626
+ delegationHeader: delegatedSession.delegationHeader,
19627
+ delegationCid: delegatedSession.delegationCid,
19628
+ spaceId,
19629
+ verificationMethod: session.verificationMethod,
19630
+ jwk: session.jwk
19631
+ },
19632
+ delegation,
19633
+ operations: this.permissionOperations(entries, spaceId),
19634
+ expiresAt
19635
+ });
19636
+ delegations.push(delegation);
19637
+ }
19638
+ return delegations;
19639
+ }
19162
19640
  /**
19163
19641
  * Get the DelegationManager for delegation CRUD operations.
19164
19642
  *
@@ -19227,6 +19705,12 @@ var _TinyCloudNode = class _TinyCloudNode {
19227
19705
  get spaceService() {
19228
19706
  return this.spaces;
19229
19707
  }
19708
+ /**
19709
+ * Get a Space object by short name or full URI.
19710
+ */
19711
+ space(nameOrUri) {
19712
+ return this.spaces.get(nameOrUri);
19713
+ }
19230
19714
  /**
19231
19715
  * Get the SharingService for creating and receiving v2 sharing links.
19232
19716
  *
@@ -19294,7 +19778,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19294
19778
  ];
19295
19779
  const abilities = { kv: { "": kvActions } };
19296
19780
  const now = /* @__PURE__ */ new Date();
19297
- const expiryMs = 60 * 60 * 1e3;
19781
+ const expiryMs = EXPIRY3.EPHEMERAL_MS;
19298
19782
  const expirationTime = new Date(now.getTime() + expiryMs);
19299
19783
  const prepared = this.wasmBindings.prepareSession({
19300
19784
  abilities,
@@ -19341,7 +19825,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19341
19825
  if (this._serviceContext) {
19342
19826
  const publicKV = new KVService2({ prefix: "" });
19343
19827
  const publicContext = new ServiceContext2({
19344
- invoke: this.wasmBindings.invoke,
19828
+ invoke: this.invokeWithRuntimePermissions,
19345
19829
  fetch: this._serviceContext.fetch,
19346
19830
  hosts: this._serviceContext.hosts
19347
19831
  });
@@ -19429,8 +19913,9 @@ var _TinyCloudNode = class _TinyCloudNode {
19429
19913
  * Issue a delegation using the capability-chain flow.
19430
19914
  *
19431
19915
  * When every requested permission is a subset of the current
19432
- * session's recap, the delegation is signed by the session key via
19433
- * WASM no wallet prompt. When at least one is NOT derivable, a
19916
+ * session's recap, or of one installed runtime permission delegation,
19917
+ * the delegation is signed by the session key via WASM no wallet
19918
+ * prompt. When at least one is NOT derivable, a
19434
19919
  * {@link PermissionNotInManifestError} is raised (carrying the
19435
19920
  * missing entries) so the caller can trigger an escalation flow
19436
19921
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -19480,10 +19965,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19480
19965
  "delegateTo requires a non-empty permissions array"
19481
19966
  );
19482
19967
  }
19483
- const expandedEntries = permissions.map((entry) => ({
19484
- ...entry,
19485
- actions: expandActionShortNames(entry.service, entry.actions)
19486
- }));
19968
+ const expandedEntries = this.expandPermissionEntries(permissions);
19487
19969
  const now = /* @__PURE__ */ new Date();
19488
19970
  const expiryMs = resolveExpiryMs(options?.expiry);
19489
19971
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -19510,6 +19992,23 @@ var _TinyCloudNode = class _TinyCloudNode {
19510
19992
  );
19511
19993
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
19512
19994
  if (!subset) {
19995
+ const runtimeGrant = this.findGrantForOperations(
19996
+ this.permissionEntriesToOperations(expandedEntries, session)
19997
+ );
19998
+ if (runtimeGrant) {
19999
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
20000
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
20001
+ throw new SessionExpiredError(runtimeGrant.expiresAt);
20002
+ }
20003
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
20004
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
20005
+ did,
20006
+ expandedEntries,
20007
+ runtimeExpiration,
20008
+ runtimeGrant
20009
+ );
20010
+ return { delegation: delegation2, prompted: false };
20011
+ }
19513
20012
  throw new PermissionNotInManifestError(missing, granted);
19514
20013
  }
19515
20014
  const delegation = await this.createDelegationViaWasmPath(
@@ -19654,6 +20153,41 @@ var _TinyCloudNode = class _TinyCloudNode {
19654
20153
  host: this.config.host
19655
20154
  };
19656
20155
  }
20156
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
20157
+ const result = this.createDelegationWrapper({
20158
+ session: grant.session,
20159
+ delegateDID: did,
20160
+ spaceId: grant.session.spaceId,
20161
+ abilities: this.permissionsToAbilities(entries),
20162
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
20163
+ });
20164
+ const primary = result.resources[0];
20165
+ const delegationHeader = { Authorization: result.delegation };
20166
+ const targetHost = grant.delegation.host ?? this.config.host;
20167
+ const activateResult = await activateSessionWithHost2(
20168
+ targetHost,
20169
+ delegationHeader
20170
+ );
20171
+ if (!activateResult.success) {
20172
+ throw new Error(
20173
+ `Failed to activate delegation with host: ${activateResult.error}`
20174
+ );
20175
+ }
20176
+ return {
20177
+ cid: result.cid,
20178
+ delegationHeader,
20179
+ spaceId: grant.session.spaceId,
20180
+ path: primary.path,
20181
+ actions: primary.actions,
20182
+ resources: result.resources,
20183
+ disableSubDelegation: false,
20184
+ expiry: result.expiry,
20185
+ delegateDID: did,
20186
+ ownerAddress: grant.delegation.ownerAddress,
20187
+ chainId: grant.delegation.chainId,
20188
+ host: targetHost
20189
+ };
20190
+ }
19657
20191
  resolvePermissionSpace(space, session) {
19658
20192
  if (space === void 0) {
19659
20193
  return this.wasmBindings.makeSpaceId(
@@ -19670,6 +20204,220 @@ var _TinyCloudNode = class _TinyCloudNode {
19670
20204
  }
19671
20205
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
19672
20206
  }
20207
+ expandPermissionEntries(permissions) {
20208
+ return expandPermissionEntriesCore(permissions);
20209
+ }
20210
+ shortServiceName(service) {
20211
+ const short = SERVICE_LONG_TO_SHORT[service];
20212
+ if (short === void 0) {
20213
+ throw new Error(
20214
+ `unknown service '${service}' \u2014 no short-form mapping`
20215
+ );
20216
+ }
20217
+ return short;
20218
+ }
20219
+ permissionsToAbilities(entries) {
20220
+ const abilities = {};
20221
+ for (const entry of entries) {
20222
+ const service = this.shortServiceName(entry.service);
20223
+ abilities[service] ?? (abilities[service] = {});
20224
+ const existing = abilities[service][entry.path] ?? [];
20225
+ const seen = new Set(existing);
20226
+ for (const action of entry.actions) {
20227
+ if (!seen.has(action)) {
20228
+ existing.push(action);
20229
+ seen.add(action);
20230
+ }
20231
+ }
20232
+ abilities[service][entry.path] = existing;
20233
+ }
20234
+ return abilities;
20235
+ }
20236
+ permissionOperations(entries, spaceId) {
20237
+ return entries.flatMap((entry) => {
20238
+ const service = this.shortServiceName(entry.service);
20239
+ return entry.actions.map((action) => ({
20240
+ spaceId,
20241
+ service,
20242
+ path: entry.path,
20243
+ action
20244
+ }));
20245
+ });
20246
+ }
20247
+ sessionCoversPermissionEntries(session, entries) {
20248
+ try {
20249
+ const granted = parseRecapCapabilities(
20250
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
20251
+ session.siwe
20252
+ );
20253
+ return isCapabilitySubset(entries, granted).subset;
20254
+ } catch {
20255
+ return false;
20256
+ }
20257
+ }
20258
+ permissionEntriesToOperations(entries, session) {
20259
+ return entries.flatMap((entry) => {
20260
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
20261
+ const service = this.shortServiceName(entry.service);
20262
+ return entry.actions.map((action) => ({
20263
+ spaceId,
20264
+ service,
20265
+ path: entry.path,
20266
+ action
20267
+ }));
20268
+ });
20269
+ }
20270
+ findRuntimeGrantsForPermissionEntries(entries, session) {
20271
+ const grants = [];
20272
+ const operations = this.permissionEntriesToOperations(entries, session);
20273
+ if (operations.length === 0) {
20274
+ return grants;
20275
+ }
20276
+ for (const operation of operations) {
20277
+ const grant = this.findGrantForOperation(operation);
20278
+ if (!grant) {
20279
+ return [];
20280
+ }
20281
+ if (!grants.includes(grant)) {
20282
+ grants.push(grant);
20283
+ }
20284
+ }
20285
+ return grants;
20286
+ }
20287
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
20288
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
20289
+ const primary = resources[0];
20290
+ return {
20291
+ cid: delegatedSession.delegationCid,
20292
+ delegationHeader: delegatedSession.delegationHeader,
20293
+ spaceId,
20294
+ path: primary.path,
20295
+ actions: primary.actions,
20296
+ resources,
20297
+ disableSubDelegation: false,
20298
+ expiry: expiresAt,
20299
+ delegateDID: session.verificationMethod,
20300
+ ownerAddress: session.address,
20301
+ chainId: session.chainId,
20302
+ host: this.config.host
20303
+ };
20304
+ }
20305
+ runtimeGrantFromDelegation(delegation, session) {
20306
+ const operations = this.operationsFromDelegation(delegation);
20307
+ return {
20308
+ session: {
20309
+ delegationHeader: delegation.delegationHeader,
20310
+ delegationCid: delegation.cid,
20311
+ spaceId: delegation.spaceId,
20312
+ verificationMethod: session.verificationMethod,
20313
+ jwk: session.jwk
20314
+ },
20315
+ delegation,
20316
+ operations,
20317
+ expiresAt: delegation.expiry
20318
+ };
20319
+ }
20320
+ delegatedResourcesForEntries(entries, spaceId) {
20321
+ return entries.map((entry) => ({
20322
+ service: this.shortServiceName(entry.service),
20323
+ space: spaceId,
20324
+ path: entry.path,
20325
+ actions: [...entry.actions]
20326
+ }));
20327
+ }
20328
+ operationsFromDelegation(delegation) {
20329
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
20330
+ return resources.flatMap(
20331
+ (resource) => resource.actions.map((action) => ({
20332
+ spaceId: resource.space,
20333
+ service: this.invocationServiceName(resource.service),
20334
+ path: resource.path,
20335
+ action
20336
+ }))
20337
+ );
20338
+ }
20339
+ flatDelegationResources(delegation) {
20340
+ const byService = /* @__PURE__ */ new Map();
20341
+ for (const action of delegation.actions) {
20342
+ const service = this.shortServiceName(action.split("/")[0]);
20343
+ const actions = byService.get(service) ?? [];
20344
+ actions.push(action);
20345
+ byService.set(service, actions);
20346
+ }
20347
+ return [...byService.entries()].map(([service, actions]) => ({
20348
+ service,
20349
+ space: delegation.spaceId,
20350
+ path: delegation.path,
20351
+ actions
20352
+ }));
20353
+ }
20354
+ selectInvocationSession(fallback, service, path, action) {
20355
+ const grant = this.findGrantForOperation({
20356
+ spaceId: fallback.spaceId,
20357
+ service: this.invocationServiceName(service),
20358
+ path,
20359
+ action
20360
+ });
20361
+ return grant?.session ?? fallback;
20362
+ }
20363
+ findGrantForOperations(operations) {
20364
+ if (operations.length === 0) {
20365
+ return void 0;
20366
+ }
20367
+ this.pruneExpiredRuntimePermissionGrants();
20368
+ return this.runtimePermissionGrants.find((grant) => {
20369
+ return operations.every(
20370
+ (operation) => grant.operations.some(
20371
+ (granted) => this.operationCovers(granted, operation)
20372
+ )
20373
+ );
20374
+ });
20375
+ }
20376
+ findGrantForOperation(operation) {
20377
+ return this.findGrantForOperations([operation]);
20378
+ }
20379
+ pruneExpiredRuntimePermissionGrants() {
20380
+ const now = Date.now();
20381
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
20382
+ (grant) => grant.expiresAt.getTime() > now
20383
+ );
20384
+ }
20385
+ operationCovers(granted, requested) {
20386
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
20387
+ }
20388
+ actionContains(grantedAction, requestedAction) {
20389
+ if (grantedAction === requestedAction) {
20390
+ return true;
20391
+ }
20392
+ if (grantedAction.endsWith("/*")) {
20393
+ const prefix = grantedAction.slice(0, -2);
20394
+ return requestedAction.startsWith(`${prefix}/`);
20395
+ }
20396
+ return false;
20397
+ }
20398
+ invocationServiceName(service) {
20399
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
20400
+ }
20401
+ pathContains(grantedPath, requestedPath) {
20402
+ if (grantedPath === "" || grantedPath === "/") {
20403
+ return true;
20404
+ }
20405
+ if (grantedPath.endsWith("/**")) {
20406
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
20407
+ }
20408
+ if (grantedPath.endsWith("/*")) {
20409
+ const prefix = grantedPath.slice(0, -2);
20410
+ if (!requestedPath.startsWith(prefix)) {
20411
+ return false;
20412
+ }
20413
+ const remainder = requestedPath.slice(prefix.length);
20414
+ return !remainder.includes("/") || remainder === "/";
20415
+ }
20416
+ if (grantedPath.endsWith("/")) {
20417
+ return requestedPath.startsWith(grantedPath);
20418
+ }
20419
+ return grantedPath === requestedPath;
20420
+ }
19673
20421
  /**
19674
20422
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
19675
20423
  * {@link PermissionEntry}. Shares the implementation with the public
@@ -20191,15 +20939,18 @@ import {
20191
20939
  ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
20192
20940
  DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
20193
20941
  DEFAULT_MANIFEST_VERSION,
20942
+ VAULT_PERMISSION_SERVICE,
20194
20943
  PermissionNotInManifestError as PermissionNotInManifestError2,
20195
20944
  SessionExpiredError as SessionExpiredError2,
20196
20945
  ManifestValidationError,
20197
20946
  composeManifestRequest as composeManifestRequest2,
20198
- resolveManifest,
20947
+ resolveManifest as resolveManifest2,
20199
20948
  validateManifest,
20200
20949
  loadManifest,
20201
20950
  isCapabilitySubset as isCapabilitySubset2,
20202
- expandActionShortNames as expandActionShortNames2,
20951
+ expandActionShortNames,
20952
+ expandPermissionEntries,
20953
+ expandPermissionEntry,
20203
20954
  parseExpiry as parseExpiry2,
20204
20955
  resourceCapabilitiesToSpaceAbilitiesMap as resourceCapabilitiesToSpaceAbilitiesMap2
20205
20956
  } from "@tinycloud/sdk-core";
@@ -20232,7 +20983,11 @@ import {
20232
20983
  DataVaultService as DataVaultService2,
20233
20984
  VaultHeaders,
20234
20985
  VaultPublicSpaceKVActions,
20235
- createVaultCrypto as createVaultCrypto2
20986
+ createVaultCrypto as createVaultCrypto2,
20987
+ SecretsService as SecretsService2,
20988
+ SECRET_NAME_RE,
20989
+ canonicalizeSecretScope,
20990
+ resolveSecretPath as resolveSecretPath2
20236
20991
  } from "@tinycloud/sdk-core";
20237
20992
  import { HooksService as HooksService3 } from "@tinycloud/sdk-core";
20238
20993
  import {
@@ -20289,8 +21044,10 @@ export {
20289
21044
  PrefixedKVService,
20290
21045
  PrivateKeySigner,
20291
21046
  ProtocolMismatchError,
21047
+ SECRET_NAME_RE,
20292
21048
  SQLAction,
20293
21049
  SQLService3 as SQLService,
21050
+ SecretsService2 as SecretsService,
20294
21051
  ServiceContext3 as ServiceContext,
20295
21052
  SessionExpiredError2 as SessionExpiredError,
20296
21053
  SharingService2 as SharingService,
@@ -20301,11 +21058,13 @@ export {
20301
21058
  TinyCloud2 as TinyCloud,
20302
21059
  TinyCloudNode,
20303
21060
  UnsupportedFeatureError2 as UnsupportedFeatureError,
21061
+ VAULT_PERMISSION_SERVICE,
20304
21062
  VaultHeaders,
20305
21063
  VaultPublicSpaceKVActions,
20306
21064
  VersionCheckError,
20307
21065
  WasmKeyProvider,
20308
21066
  buildSpaceUri,
21067
+ canonicalizeSecretScope,
20309
21068
  checkNodeInfo2 as checkNodeInfo,
20310
21069
  composeManifestRequest2 as composeManifestRequest,
20311
21070
  createCapabilityKeyRegistry,
@@ -20316,13 +21075,16 @@ export {
20316
21075
  defaultSignStrategy,
20317
21076
  defaultSpaceCreationHandler,
20318
21077
  deserializeDelegation,
20319
- expandActionShortNames2 as expandActionShortNames,
21078
+ expandActionShortNames,
21079
+ expandPermissionEntries,
21080
+ expandPermissionEntry,
20320
21081
  isCapabilitySubset2 as isCapabilitySubset,
20321
21082
  loadManifest,
20322
21083
  makePublicSpaceId2 as makePublicSpaceId,
20323
21084
  parseExpiry2 as parseExpiry,
20324
21085
  parseSpaceUri,
20325
- resolveManifest,
21086
+ resolveManifest2 as resolveManifest,
21087
+ resolveSecretPath2 as resolveSecretPath,
20326
21088
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
20327
21089
  serializeDelegation,
20328
21090
  validateManifest