@tinycloud/node-sdk 2.4.0-beta.8 → 2.4.0

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
@@ -17173,6 +17173,11 @@ import {
17173
17173
  UnsupportedFeatureError,
17174
17174
  makePublicSpaceId,
17175
17175
  ACCOUNT_REGISTRY_SPACE,
17176
+ BOOTSTRAP_SESSION_REQUESTS,
17177
+ SECRET_RECORDS_SCHEMA,
17178
+ SECRETS_SPACE as SECRETS_SPACE2,
17179
+ TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
17180
+ bootstrapSteps,
17176
17181
  ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
17177
17182
  PermissionNotInManifestError,
17178
17183
  SessionExpiredError,
@@ -17186,6 +17191,7 @@ import {
17186
17191
  verifyDidKeyEd25519Signature,
17187
17192
  canonicalizeAddress as canonicalizeAddress2,
17188
17193
  pkhDid as pkhDid2,
17194
+ resolveTinyCloudHosts as resolveTinyCloudHosts2,
17189
17195
  principalDidEquals as principalDidEquals2,
17190
17196
  parseNetworkId as parseNetworkId2
17191
17197
  } from "@tinycloud/sdk-core";
@@ -17212,6 +17218,7 @@ import {
17212
17218
  } from "@tinycloud/sdk-core";
17213
17219
 
17214
17220
  // src/authorization/strategies.ts
17221
+ import { createOpenKeyCallbackSigningStrategy } from "@tinycloud/sdk-core";
17215
17222
  var defaultSignStrategy = { type: "auto-sign" };
17216
17223
 
17217
17224
  // src/storage/MemorySessionStorage.ts
@@ -17309,6 +17316,7 @@ var NodeUserAuthorization = class {
17309
17316
  constructor(config) {
17310
17317
  this.extensions = [];
17311
17318
  this._nodeFeatures = [];
17319
+ this._lastActivationSkippedSpaceIds = [];
17312
17320
  this.wasm = config.wasmBindings;
17313
17321
  this.signer = config.signer;
17314
17322
  this.signStrategy = config.signStrategy ?? defaultSignStrategy;
@@ -17331,7 +17339,7 @@ var NodeUserAuthorization = class {
17331
17339
  "": [
17332
17340
  "tinycloud.sql/read",
17333
17341
  "tinycloud.sql/write",
17334
- "tinycloud.sql/ddl",
17342
+ "tinycloud.sql/schema",
17335
17343
  "tinycloud.sql/admin",
17336
17344
  "tinycloud.sql/export"
17337
17345
  ]
@@ -17423,12 +17431,48 @@ var NodeUserAuthorization = class {
17423
17431
  * `siwe` is the load-bearing one because `extractSiweExpiration` returns
17424
17432
  * undefined for missing SIWEs and the SDK then treats the session as
17425
17433
  * expired-at-epoch-zero.
17434
+ *
17435
+ * @param hosts - The TinyCloud hosts this session was created against,
17436
+ * as persisted in {@link PersistedSessionData.tinycloudHosts}. When
17437
+ * present (and non-empty) they are adopted directly so the restored
17438
+ * session resolves to the same node as the original sign-in without
17439
+ * re-running registry/fallback resolution. When absent (old session)
17440
+ * hosts are resolved lazily on the first host-needing call via
17441
+ * {@link ensureTinyCloudHosts}.
17426
17442
  */
17427
- setRestoredTinyCloudSession(session) {
17443
+ setRestoredTinyCloudSession(session, hosts) {
17428
17444
  const address = canonicalizeAddress(session.address);
17429
17445
  this._tinyCloudSession = { ...session, address };
17430
17446
  this._address = address;
17431
17447
  this._chainId = session.chainId;
17448
+ if ((!this.tinycloudHosts || this.tinycloudHosts.length === 0) && hosts && hosts.length > 0) {
17449
+ this.tinycloudHosts = [...hosts];
17450
+ }
17451
+ }
17452
+ /**
17453
+ * Ensure `tinycloudHosts` are resolved before a host-needing call.
17454
+ *
17455
+ * Fresh sign-in resolves hosts up front; a restored session may not have
17456
+ * (old persisted sessions predate {@link PersistedSessionData.tinycloudHosts}).
17457
+ * This guard makes a restored session resolve the node exactly like a fresh
17458
+ * sign-in — persisted hosts are preferred (already set by
17459
+ * {@link setRestoredTinyCloudSession}), otherwise the registry/fallback
17460
+ * resolution runs lazily here. Idempotent: {@link resolveTinyCloudHostsForSignIn}
17461
+ * early-returns when hosts are already set.
17462
+ *
17463
+ * Throws if hosts are unset and the restored session has no address/chainId
17464
+ * to resolve from — a real failure that must surface, not be masked.
17465
+ */
17466
+ async ensureTinyCloudHosts() {
17467
+ if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
17468
+ return;
17469
+ }
17470
+ if (this._address === void 0 || this._chainId === void 0) {
17471
+ throw new Error(
17472
+ "Cannot resolve TinyCloud hosts: no address/chainId available. Sign in or restore a session first."
17473
+ );
17474
+ }
17475
+ await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId);
17432
17476
  }
17433
17477
  async resolveTinyCloudHostsForSignIn(address, chainId) {
17434
17478
  if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
@@ -17453,6 +17497,9 @@ var NodeUserAuthorization = class {
17453
17497
  get nodeFeatures() {
17454
17498
  return this._nodeFeatures;
17455
17499
  }
17500
+ get lastActivationSkippedSpaceIds() {
17501
+ return [...this._lastActivationSkippedSpaceIds];
17502
+ }
17456
17503
  /**
17457
17504
  * Compute the `abilities` map the WASM `prepareSession` call should
17458
17505
  * see at sign-in time.
@@ -17625,6 +17672,7 @@ var NodeUserAuthorization = class {
17625
17672
  if (!this._tinyCloudSession || !this._address || !this._chainId) {
17626
17673
  throw new Error("Must be signed in to host space");
17627
17674
  }
17675
+ await this.ensureTinyCloudHosts();
17628
17676
  const host = this.primaryTinyCloudHost;
17629
17677
  const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
17630
17678
  const peerId = await fetchPeerId(host, spaceId);
@@ -17667,12 +17715,14 @@ var NodeUserAuthorization = class {
17667
17715
  if (!this._tinyCloudSession) {
17668
17716
  throw new Error("Must be signed in to ensure space exists");
17669
17717
  }
17718
+ await this.ensureTinyCloudHosts();
17670
17719
  const host = this.primaryTinyCloudHost;
17671
17720
  const primarySpaceId = this._tinyCloudSession.spaceId;
17672
17721
  const result = await activateSessionWithHost(
17673
17722
  host,
17674
17723
  this._tinyCloudSession.delegationHeader
17675
17724
  );
17725
+ this.recordActivationSkippedSpaces(result, primarySpaceId);
17676
17726
  const handler = this.spaceCreationHandler ?? (this.autoCreateSpace ? new AutoApproveSpaceCreationHandler() : void 0);
17677
17727
  const creationContext = {
17678
17728
  spaceId: primarySpaceId,
@@ -17760,6 +17810,66 @@ var NodeUserAuthorization = class {
17760
17810
  }
17761
17811
  throw new Error(`Failed to activate session: ${result.error}`);
17762
17812
  }
17813
+ recordActivationSkippedSpaces(result, primarySpaceId) {
17814
+ if (result.success) {
17815
+ this._lastActivationSkippedSpaceIds = result.skipped ?? [];
17816
+ return;
17817
+ }
17818
+ this._lastActivationSkippedSpaceIds = result.status === 404 ? [primarySpaceId] : [];
17819
+ }
17820
+ async createBootstrapSession(options) {
17821
+ if (!this._address || !this._chainId) {
17822
+ throw new Error("Must be signed in before creating bootstrap sessions");
17823
+ }
17824
+ const address = this._address;
17825
+ const chainId = this._chainId;
17826
+ const keyId = `bootstrap-${Date.now()}-${Math.random().toString(36).slice(2)}`;
17827
+ this.sessionManager.createSessionKey(keyId);
17828
+ const jwkString = this.sessionManager.jwk(keyId);
17829
+ if (!jwkString) {
17830
+ throw new Error("Failed to create bootstrap session key");
17831
+ }
17832
+ const jwk = JSON.parse(jwkString);
17833
+ const now = /* @__PURE__ */ new Date();
17834
+ const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
17835
+ const abilities = resourceCapabilitiesToAbilitiesMap(
17836
+ options.capabilityRequest.resources
17837
+ );
17838
+ const prepared = this.wasm.prepareSession({
17839
+ abilities,
17840
+ ...options.rawAbilities !== void 0 ? { rawAbilities: options.rawAbilities } : {},
17841
+ address,
17842
+ chainId,
17843
+ domain: this.domain,
17844
+ issuedAt: now.toISOString(),
17845
+ expirationTime: expirationTime.toISOString(),
17846
+ spaceId: options.spaceId,
17847
+ jwk,
17848
+ ...this.buildSiweOverrides()
17849
+ });
17850
+ const signature2 = await this.requestSignature({
17851
+ address,
17852
+ chainId,
17853
+ message: prepared.siwe,
17854
+ type: "siwe"
17855
+ });
17856
+ const session = this.wasm.completeSessionSetup({
17857
+ ...prepared,
17858
+ signature: signature2
17859
+ });
17860
+ return {
17861
+ address,
17862
+ chainId,
17863
+ sessionKey: keyId,
17864
+ spaceId: options.spaceId,
17865
+ delegationCid: session.delegationCid,
17866
+ delegationHeader: session.delegationHeader,
17867
+ verificationMethod: this.sessionManager.getDID(keyId),
17868
+ jwk,
17869
+ siwe: prepared.siwe,
17870
+ signature: signature2
17871
+ };
17872
+ }
17763
17873
  /**
17764
17874
  * Sign in and create a new session.
17765
17875
  *
@@ -17848,7 +17958,8 @@ var NodeUserAuthorization = class {
17848
17958
  },
17849
17959
  expiresAt: expirationTime.toISOString(),
17850
17960
  createdAt: now.toISOString(),
17851
- version: "1.0"
17961
+ version: "1.0",
17962
+ tinycloudHosts: this.tinycloudHosts
17852
17963
  };
17853
17964
  await this.sessionStorage.save(address, persistedData);
17854
17965
  this._session = clientSession;
@@ -18015,7 +18126,8 @@ var NodeUserAuthorization = class {
18015
18126
  },
18016
18127
  expiresAt,
18017
18128
  createdAt,
18018
- version: "1.0"
18129
+ version: "1.0",
18130
+ tinycloudHosts: this.tinycloudHosts
18019
18131
  };
18020
18132
  await this.sessionStorage.save(address, persistedData);
18021
18133
  this._session = clientSession;
@@ -18117,12 +18229,13 @@ import {
18117
18229
  ServiceContext
18118
18230
  } from "@tinycloud/sdk-core";
18119
18231
  var DelegatedAccess = class {
18120
- constructor(session, delegation, host, invoke2, telemetry) {
18232
+ constructor(session, delegation, host, invoke2, invokeAny2, telemetry) {
18121
18233
  this.session = session;
18122
18234
  this._delegation = delegation;
18123
18235
  this.host = host;
18124
18236
  this._serviceContext = new ServiceContext({
18125
18237
  invoke: invoke2,
18238
+ invokeAny: invokeAny2,
18126
18239
  fetch: globalThis.fetch.bind(globalThis),
18127
18240
  hosts: [host],
18128
18241
  telemetry
@@ -18529,6 +18642,9 @@ var NETWORK_CREATE_ACTION2 = "tinycloud.encryption/network.create";
18529
18642
  var DECRYPT_ACTION2 = "tinycloud.encryption/decrypt";
18530
18643
  var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
18531
18644
  var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
18645
+ function isOpenKeyAutoSignStrategy(strategy) {
18646
+ return strategy?.openKeyAutoSign === true;
18647
+ }
18532
18648
  function didPrincipalMatches2(actual, expected) {
18533
18649
  try {
18534
18650
  return principalDidEquals2(actual, expected);
@@ -18753,9 +18869,10 @@ var _TinyCloudNode = class _TinyCloudNode {
18753
18869
  * @internal
18754
18870
  */
18755
18871
  setupAuth(config) {
18872
+ const useBootstrapSignInRequest = this.shouldUseBootstrapSignInRequest(config);
18756
18873
  this.auth = new NodeUserAuthorization({
18757
18874
  signer: this.signer,
18758
- signStrategy: { type: "auto-sign" },
18875
+ signStrategy: config.signStrategy ?? { type: "auto-sign" },
18759
18876
  wasmBindings: this.wasmBindings,
18760
18877
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
18761
18878
  domain: this.siweDomain,
@@ -18764,20 +18881,23 @@ var _TinyCloudNode = class _TinyCloudNode {
18764
18881
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
18765
18882
  tinycloudRegistryUrl: config.tinycloudRegistryUrl,
18766
18883
  tinycloudFallbackHosts: config.tinycloudFallbackHosts,
18767
- autoCreateSpace: config.autoCreateSpace,
18884
+ autoCreateSpace: useBootstrapSignInRequest ? false : config.autoCreateSpace,
18768
18885
  enablePublicSpace: config.enablePublicSpace ?? true,
18769
- spaceCreationHandler: config.spaceCreationHandler,
18886
+ spaceCreationHandler: useBootstrapSignInRequest ? void 0 : config.spaceCreationHandler,
18770
18887
  nonce: config.nonce,
18771
18888
  siweConfig: config.siweConfig,
18772
- manifest: config.manifest,
18773
- capabilityRequest: config.capabilityRequest,
18774
- includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
18889
+ manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
18890
+ capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
18891
+ includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
18775
18892
  });
18776
18893
  this.tc = new TinyCloud(this.auth, {
18777
18894
  invokeAny: this.invokeAnyWithRuntimePermissions,
18778
18895
  telemetry: config.telemetry
18779
18896
  });
18780
18897
  }
18898
+ shouldUseBootstrapSignInRequest(config) {
18899
+ return config.autoBootstrapAccount !== false && config.manifest === void 0 && config.capabilityRequest === void 0 && (config.prefix ?? "default") === "default" && isOpenKeyAutoSignStrategy(config.signStrategy);
18900
+ }
18781
18901
  syncResolvedHostFromAuth() {
18782
18902
  const host = this.auth?.hosts[0];
18783
18903
  if (host) {
@@ -18892,7 +19012,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18892
19012
  getAccountDb: () => this.accountSpaceId ? this.sqlForSpace(this.accountSpaceId).db("account") : void 0,
18893
19013
  ensureAccountSpaceHosted: async () => {
18894
19014
  if (this.accountSpaceId && this.auth) {
18895
- await this.ensureOwnedSpaceHosted(this.accountSpaceId);
19015
+ await this.ensureOwnedSpaceHostedById(this.accountSpaceId);
18896
19016
  }
18897
19017
  }
18898
19018
  });
@@ -18936,11 +19056,14 @@ var _TinyCloudNode = class _TinyCloudNode {
18936
19056
  await this.tc.signIn(options);
18937
19057
  this.syncResolvedHostFromAuth();
18938
19058
  this.initializeServices();
19059
+ const bootstrapped = await this.bootstrapAccountIfNeeded();
18939
19060
  await this.ensureRequestedEncryptionNetworks();
18940
- if (this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
18941
- await this.ensureOwnedSpaceHosted(this.ownedSpaceId("secrets"));
19061
+ if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
19062
+ await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(SECRETS_SPACE2));
19063
+ }
19064
+ if (!bootstrapped) {
19065
+ this.scheduleAccountRegistrySync();
18942
19066
  }
18943
- this.scheduleAccountRegistrySync();
18944
19067
  this.notificationHandler.success("Successfully signed in");
18945
19068
  }
18946
19069
  ownedSpaceId(name) {
@@ -18949,6 +19072,208 @@ var _TinyCloudNode = class _TinyCloudNode {
18949
19072
  }
18950
19073
  return this.wasmBindings.makeSpaceId(this._address, this._chainId, name);
18951
19074
  }
19075
+ async bootstrapAccountIfNeeded() {
19076
+ if (this.config.autoBootstrapAccount === false) {
19077
+ return false;
19078
+ }
19079
+ if (!this.auth || !this._address) {
19080
+ return false;
19081
+ }
19082
+ const steps = bootstrapSteps(this._address, this._chainId);
19083
+ if (!await this.isFreshBootstrapAccount(steps)) {
19084
+ return false;
19085
+ }
19086
+ await this.runAccountBootstrap(steps);
19087
+ return true;
19088
+ }
19089
+ async isFreshBootstrapAccount(steps) {
19090
+ const enshrinedSpaceIds = /* @__PURE__ */ new Set();
19091
+ for (const step of steps) {
19092
+ if (step.kind === "session") {
19093
+ enshrinedSpaceIds.add(step.spaceId);
19094
+ }
19095
+ }
19096
+ const skipped = this.auth.lastActivationSkippedSpaceIds;
19097
+ if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
19098
+ return true;
19099
+ }
19100
+ try {
19101
+ const indexed = await this.account.index.spaces.list();
19102
+ if (indexed.ok && indexed.data.length === 0) {
19103
+ return true;
19104
+ }
19105
+ } catch {
19106
+ }
19107
+ try {
19108
+ const spaces = await this.account.spaces.list();
19109
+ return spaces.ok && spaces.data.length === 0;
19110
+ } catch {
19111
+ return false;
19112
+ }
19113
+ }
19114
+ async runAccountBootstrap(steps) {
19115
+ if (!this.auth || !this._address) {
19116
+ throw new Error("Account bootstrap requires an active wallet session");
19117
+ }
19118
+ const host = this.hosts[0] ?? this.config.host;
19119
+ if (!host) {
19120
+ throw new Error("Account bootstrap requires a TinyCloud host");
19121
+ }
19122
+ const auth = this.auth;
19123
+ const sessions = /* @__PURE__ */ new Map();
19124
+ const rawAbilitiesBySpace = /* @__PURE__ */ new Map();
19125
+ const primarySession = auth.tinyCloudSession;
19126
+ const defaultSpaceId = this.ownedSpaceId("default");
19127
+ const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === BOOTSTRAP_SESSION_REQUESTS.default;
19128
+ for (const step of steps) {
19129
+ if (step.kind !== "session") continue;
19130
+ if (step.space === "default" && canReusePrimaryBootstrapSession && primarySession) {
19131
+ sessions.set(step.space, primarySession);
19132
+ continue;
19133
+ }
19134
+ const rawAbilities = step.rawAbilities;
19135
+ if (rawAbilities) {
19136
+ rawAbilitiesBySpace.set(step.space, rawAbilities);
19137
+ }
19138
+ const session = await auth.createBootstrapSession({
19139
+ spaceId: step.spaceId,
19140
+ capabilityRequest: step.request ?? BOOTSTRAP_SESSION_REQUESTS[step.space],
19141
+ rawAbilities
19142
+ });
19143
+ sessions.set(step.space, session);
19144
+ }
19145
+ for (const step of steps) {
19146
+ if (step.kind !== "host") continue;
19147
+ const hosted = await auth.hostOwnedSpace(step.spaceId);
19148
+ if (!hosted) {
19149
+ throw new Error(`Failed to host bootstrap space: ${step.spaceId}`);
19150
+ }
19151
+ }
19152
+ await new Promise((resolve) => setTimeout(resolve, 100));
19153
+ for (const step of steps) {
19154
+ if (step.kind !== "activate") continue;
19155
+ const session = sessions.get(step.space);
19156
+ if (!session) {
19157
+ throw new Error(`Missing bootstrap session for ${step.space}`);
19158
+ }
19159
+ const activated = await activateSessionWithHost2(host, session.delegationHeader);
19160
+ if (!activated.success || activated.skipped?.includes(step.spaceId)) {
19161
+ throw new Error(
19162
+ `Failed to activate bootstrap session for ${step.spaceId}: ${activated.error ?? "space was skipped"}`
19163
+ );
19164
+ }
19165
+ this.registerBootstrapRuntimeGrant(
19166
+ session,
19167
+ BOOTSTRAP_SESSION_REQUESTS[step.space],
19168
+ rawAbilitiesBySpace.get(step.space)
19169
+ );
19170
+ }
19171
+ for (const step of steps) {
19172
+ if (step.kind === "account-index-schema") {
19173
+ const ensured = await this.account.index.ensure();
19174
+ if (!ensured.ok) {
19175
+ throw new Error(`Failed to create account index schema: ${ensured.error.message}`);
19176
+ }
19177
+ }
19178
+ if (step.kind === "seed-spaces") {
19179
+ for (const space of step.spaces) {
19180
+ const registered = await this.account.spaces.register({
19181
+ spaceId: space.spaceId,
19182
+ name: space.name,
19183
+ ownerDid: this.did,
19184
+ type: "owned",
19185
+ permissions: ["*"],
19186
+ status: "active"
19187
+ });
19188
+ if (!registered.ok) {
19189
+ throw new Error(
19190
+ `Failed to seed account space ${space.spaceId}: ${registered.error.message}`
19191
+ );
19192
+ }
19193
+ }
19194
+ }
19195
+ if (step.kind === "seed-applications") {
19196
+ const registered = await this.account.applications.register(
19197
+ step.manifests.length > 0 ? [...step.manifests] : TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST
19198
+ );
19199
+ if (!registered.ok) {
19200
+ throw new Error(`Failed to seed bootstrap applications: ${registered.error.message}`);
19201
+ }
19202
+ }
19203
+ if (step.kind === "encryption-network-create") {
19204
+ await this.ensureEncryptionNetwork(step.networkId);
19205
+ }
19206
+ if (step.kind === "secret-records-schema") {
19207
+ const db = this.sqlForSpace(step.spaceId).db(step.database);
19208
+ const migrated = await db.migrations.apply({
19209
+ namespace: "tinycloud.secrets.records",
19210
+ migrations: [
19211
+ {
19212
+ id: "001_initial",
19213
+ sql: [...SECRET_RECORDS_SCHEMA]
19214
+ }
19215
+ ]
19216
+ });
19217
+ if (!migrated.ok) {
19218
+ throw new Error(
19219
+ `Failed to create secret_records schema: ${migrated.error.message}`
19220
+ );
19221
+ }
19222
+ }
19223
+ }
19224
+ }
19225
+ registerBootstrapRuntimeGrant(session, request, rawAbilities) {
19226
+ const operations = [];
19227
+ for (const resource of request.resources) {
19228
+ const service = resource.service.startsWith("tinycloud.") ? this.shortServiceName(resource.service) : resource.service;
19229
+ const spaceId = resource.space.startsWith("tinycloud:") ? resource.space : this.ownedSpaceId(resource.space);
19230
+ for (const action of resource.actions) {
19231
+ operations.push({
19232
+ spaceId,
19233
+ service,
19234
+ path: resource.path,
19235
+ action
19236
+ });
19237
+ }
19238
+ }
19239
+ for (const [resource, actions2] of Object.entries(rawAbilities ?? {})) {
19240
+ for (const action of actions2) {
19241
+ operations.push({
19242
+ resource,
19243
+ service: "encryption",
19244
+ path: resource,
19245
+ action
19246
+ });
19247
+ }
19248
+ }
19249
+ const expiresAt = extractSiweExpiration(session.siwe) ?? this.getSessionExpiry();
19250
+ const actions = [...new Set(operations.map((operation) => operation.action))];
19251
+ this.runtimePermissionGrants.push({
19252
+ session: {
19253
+ delegationHeader: session.delegationHeader,
19254
+ delegationCid: session.delegationCid,
19255
+ spaceId: session.spaceId,
19256
+ verificationMethod: session.verificationMethod,
19257
+ jwk: session.jwk
19258
+ },
19259
+ delegation: {
19260
+ cid: session.delegationCid,
19261
+ delegationHeader: session.delegationHeader,
19262
+ delegateDID: session.verificationMethod,
19263
+ delegatorDID: this.did,
19264
+ spaceId: session.spaceId,
19265
+ path: "",
19266
+ actions,
19267
+ expiry: expiresAt,
19268
+ allowSubDelegation: true,
19269
+ ownerAddress: session.address,
19270
+ chainId: session.chainId,
19271
+ host: this.config.host
19272
+ },
19273
+ operations,
19274
+ expiresAt
19275
+ });
19276
+ }
18952
19277
  async writeManifestRegistryRecords() {
18953
19278
  const request = this.capabilityRequest;
18954
19279
  if (!request || request.registryRecords.length === 0) {
@@ -18958,7 +19283,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18958
19283
  throw new Error("Manifest registry write requires wallet mode");
18959
19284
  }
18960
19285
  const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE);
18961
- await this.ensureOwnedSpaceHosted(accountSpaceId);
19286
+ await this.ensureOwnedSpaceHostedById(accountSpaceId);
18962
19287
  const result = await this.account.applications.register(request.manifests);
18963
19288
  if (!result.ok) {
18964
19289
  throw new Error(
@@ -18968,6 +19293,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18968
19293
  }
18969
19294
  scheduleAccountRegistrySync() {
18970
19295
  void this.withAccountRegistryRetry(async () => {
19296
+ void this.account.index.ensure();
18971
19297
  await this.writeManifestRegistryRecords();
18972
19298
  const spaces = await this.account.spaces.syncAccessible();
18973
19299
  if (!spaces.ok) {
@@ -19019,7 +19345,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19019
19345
  await this.ensureEncryptionNetwork(networkId);
19020
19346
  }
19021
19347
  }
19022
- async ensureOwnedSpaceHosted(spaceId) {
19348
+ async ensureOwnedSpaceHostedById(spaceId) {
19023
19349
  if (!this.auth) {
19024
19350
  throw new Error("Owned space hosting requires wallet mode");
19025
19351
  }
@@ -19062,7 +19388,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19062
19388
  * caller is the root authority of their own owned spaces, so no additional
19063
19389
  * delegation is required.
19064
19390
  *
19065
- * Unlike {@link ensureOwnedSpaceHosted}, this always submits the host
19391
+ * Unlike {@link ensureOwnedSpaceHostedById}, this always submits the host
19066
19392
  * delegation rather than inferring hosting from session activation: a space
19067
19393
  * the current session has never referenced is reported neither as
19068
19394
  * `activated` nor `skipped`, so activation-based detection would wrongly
@@ -19107,6 +19433,91 @@ var _TinyCloudNode = class _TinyCloudNode {
19107
19433
  });
19108
19434
  return spaceId;
19109
19435
  }
19436
+ /**
19437
+ * Ensure one of this user's owned spaces (e.g. `"secrets"`) is hosted on the
19438
+ * server.
19439
+ *
19440
+ * At sign-in, a full-authority session auto-hosts the owner's `secrets`
19441
+ * space, but a session created with a manifest / capabilityRequest does not.
19442
+ * Such a session can therefore hold valid `tinycloud.kv/*` capabilities for
19443
+ * the owned `secrets` space yet still fail its first scoped
19444
+ * `secrets.put(...)` with `404 Space not found`, because the space was never
19445
+ * registered on the node.
19446
+ *
19447
+ * Calling this resolves `name` to the owner's owned-space URI
19448
+ * (`tinycloud:pkh:eip155:<chain>:<addr>:<name>`). It first consults the
19449
+ * account-space spaces registry (`account/spaces/{space_id}`, the canonical
19450
+ * KV source of truth, fronted by a best-effort SQLite index): if the space is
19451
+ * already registered/hosted it returns the URI WITHOUT submitting a host
19452
+ * delegation, avoiding a redundant host-SIWE signature prompt for owners who
19453
+ * already use the space. Only when the space is absent — or the registry
19454
+ * check fails for any reason (e.g. a cold SQLite index reporting
19455
+ * `no such table: spaces`) — does it fall through to {@link hostOwnedSpace}.
19456
+ *
19457
+ * The registry check is purely an optimization: any failure falls back to
19458
+ * hosting, and the host SIWE is idempotent server-side, so re-hosting an
19459
+ * existing space remains a safe no-op. Must be called after {@link signIn}.
19460
+ *
19461
+ * @param name - The owned space name (e.g. `"secrets"`).
19462
+ * @returns The hosted owned-space URI.
19463
+ */
19464
+ async ensureOwnedSpaceHosted(name) {
19465
+ if (!this.auth || !this.auth.tinyCloudSession) {
19466
+ throw new Error("Not signed in. Call signIn() first.");
19467
+ }
19468
+ const spaceId = this.ownedSpaceId(name);
19469
+ if (await this.isOwnedSpaceRegistered(spaceId)) {
19470
+ return spaceId;
19471
+ }
19472
+ const hosted = await this.hostOwnedSpace(name);
19473
+ try {
19474
+ await this.account.spaces.register({
19475
+ spaceId,
19476
+ name,
19477
+ ownerDid: this.did,
19478
+ type: "owned",
19479
+ permissions: ["*"],
19480
+ status: "active"
19481
+ });
19482
+ } catch {
19483
+ }
19484
+ return hosted;
19485
+ }
19486
+ /**
19487
+ * Check whether an owned space is already registered/hosted by consulting the
19488
+ * account spaces registry.
19489
+ *
19490
+ * Source of truth is the canonical KV registry record
19491
+ * `account/spaces/{space_id}`, read here via `account.spaces.get(spaceId)`.
19492
+ * The KV path is used (rather than `syncAccessible()`) because it works under
19493
+ * a manifest/recap session with NO extra prompt: the composed manifest recap
19494
+ * already grants `tinycloud.kv get/list` on the account space `spaces/`
19495
+ * prefix, whereas `syncAccessible()` depends on `tinycloud.space/list`, which
19496
+ * a recap session does not hold. Before reading, it consults the fast SQLite
19497
+ * index (`account.index.spaces.list()`) as a best-effort short-circuit; on a
19498
+ * cold index (`no such table: spaces`) or any other index failure it falls
19499
+ * back to the canonical KV read.
19500
+ *
19501
+ * This is a best-effort optimization. ANY failure of the check path (missing
19502
+ * table, KV error, missing record, thrown exception) resolves to `false` so
19503
+ * the caller falls through to hosting — per the directive, "if it fails in any
19504
+ * way then create the space".
19505
+ */
19506
+ async isOwnedSpaceRegistered(spaceId) {
19507
+ try {
19508
+ const indexed = await this.account.index.spaces.list();
19509
+ if (indexed.ok && indexed.data.some((space) => space.spaceId === spaceId)) {
19510
+ return true;
19511
+ }
19512
+ } catch {
19513
+ }
19514
+ try {
19515
+ const record = await this.account.spaces.get(spaceId);
19516
+ return record.ok;
19517
+ } catch {
19518
+ return false;
19519
+ }
19520
+ }
19110
19521
  /**
19111
19522
  * Restore a previously established session from stored delegation data.
19112
19523
  *
@@ -19136,6 +19547,14 @@ var _TinyCloudNode = class _TinyCloudNode {
19136
19547
  if (sessionData.chainId) {
19137
19548
  this._chainId = sessionData.chainId;
19138
19549
  }
19550
+ const resolvedHost = await this.resolveRestoredHost(
19551
+ sessionData.tinycloudHosts,
19552
+ restoredAddress,
19553
+ sessionData.chainId
19554
+ );
19555
+ if (resolvedHost) {
19556
+ this.config.host = resolvedHost;
19557
+ }
19139
19558
  this._serviceContext = new ServiceContext2({
19140
19559
  invoke: this.invokeWithRuntimePermissions,
19141
19560
  invokeAny: this.invokeAnyWithRuntimePermissions,
@@ -19181,12 +19600,45 @@ var _TinyCloudNode = class _TinyCloudNode {
19181
19600
  signature: sessionData.signature ?? ""
19182
19601
  };
19183
19602
  if (this.auth) {
19184
- this.auth.setRestoredTinyCloudSession(tcSession);
19603
+ this.auth.setRestoredTinyCloudSession(
19604
+ tcSession,
19605
+ this.config.host ? [this.config.host] : void 0
19606
+ );
19185
19607
  } else {
19186
19608
  this._restoredTcSession = tcSession;
19187
19609
  }
19188
19610
  }
19189
19611
  }
19612
+ /**
19613
+ * Resolve the host a restored session should target.
19614
+ *
19615
+ * Mirrors fresh sign-in host resolution but for the restore path:
19616
+ * an explicit/pinned host always wins, then the hosts the session was
19617
+ * persisted with, then a lazy registry/fallback resolution for sessions
19618
+ * that predate the persisted `tinycloudHosts` field. Returns `undefined`
19619
+ * only when there's nothing to resolve from (no explicit host, no
19620
+ * persisted hosts, and no address/chainId) — in which case the existing
19621
+ * `config.host` (default) is left in place.
19622
+ *
19623
+ * Resolution failures are surfaced, not swallowed: a genuinely broken
19624
+ * registry lookup throws rather than silently falling back to a wrong host.
19625
+ */
19626
+ async resolveRestoredHost(persistedHosts, address, chainId) {
19627
+ if (this.explicitHost) {
19628
+ return this.explicitHost;
19629
+ }
19630
+ if (persistedHosts && persistedHosts.length > 0) {
19631
+ return persistedHosts[0];
19632
+ }
19633
+ if (address === void 0 || chainId === void 0) {
19634
+ return void 0;
19635
+ }
19636
+ const resolved = await resolveTinyCloudHosts2(pkhDid2(address, chainId), {
19637
+ registryUrl: this.config.tinycloudRegistryUrl,
19638
+ fallbackHosts: this.config.tinycloudFallbackHosts
19639
+ });
19640
+ return resolved.hosts[0];
19641
+ }
19190
19642
  /**
19191
19643
  * Resolve the currently-active TinyCloudSession, preferring the auth
19192
19644
  * layer's value (wallet mode) and falling back to the node-level
@@ -19231,9 +19683,11 @@ var _TinyCloudNode = class _TinyCloudNode {
19231
19683
  );
19232
19684
  }
19233
19685
  this.signer = _TinyCloudNode.nodeDefaults.createSigner(privateKey);
19686
+ const authConfig = { ...this.config, prefix };
19687
+ const useBootstrapSignInRequest = this.shouldUseBootstrapSignInRequest(authConfig);
19234
19688
  this.auth = new NodeUserAuthorization({
19235
19689
  signer: this.signer,
19236
- signStrategy: { type: "auto-sign" },
19690
+ signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
19237
19691
  wasmBindings: this.wasmBindings,
19238
19692
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
19239
19693
  domain: this.siweDomain,
@@ -19242,14 +19696,14 @@ var _TinyCloudNode = class _TinyCloudNode {
19242
19696
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
19243
19697
  tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
19244
19698
  tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
19245
- autoCreateSpace: this.config.autoCreateSpace,
19699
+ autoCreateSpace: useBootstrapSignInRequest ? false : this.config.autoCreateSpace,
19246
19700
  enablePublicSpace: this.config.enablePublicSpace ?? true,
19247
- spaceCreationHandler: this.config.spaceCreationHandler,
19701
+ spaceCreationHandler: useBootstrapSignInRequest ? void 0 : this.config.spaceCreationHandler,
19248
19702
  nonce: this.config.nonce,
19249
19703
  siweConfig: this.config.siweConfig,
19250
- manifest: this.config.manifest,
19251
- capabilityRequest: this.config.capabilityRequest,
19252
- includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
19704
+ manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
19705
+ capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
19706
+ includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
19253
19707
  });
19254
19708
  this.tc = new TinyCloud(this.auth, {
19255
19709
  invokeAny: this.invokeAnyWithRuntimePermissions,
@@ -19276,9 +19730,11 @@ var _TinyCloudNode = class _TinyCloudNode {
19276
19730
  }
19277
19731
  const prefix = options?.prefix ?? "default";
19278
19732
  this.signer = signer;
19733
+ const authConfig = { ...this.config, prefix };
19734
+ const useBootstrapSignInRequest = this.shouldUseBootstrapSignInRequest(authConfig);
19279
19735
  this.auth = new NodeUserAuthorization({
19280
19736
  signer: this.signer,
19281
- signStrategy: { type: "auto-sign" },
19737
+ signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
19282
19738
  wasmBindings: this.wasmBindings,
19283
19739
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
19284
19740
  domain: this.siweDomain,
@@ -19287,14 +19743,14 @@ var _TinyCloudNode = class _TinyCloudNode {
19287
19743
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
19288
19744
  tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
19289
19745
  tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
19290
- autoCreateSpace: this.config.autoCreateSpace,
19746
+ autoCreateSpace: useBootstrapSignInRequest ? false : this.config.autoCreateSpace,
19291
19747
  enablePublicSpace: this.config.enablePublicSpace ?? true,
19292
- spaceCreationHandler: this.config.spaceCreationHandler,
19748
+ spaceCreationHandler: useBootstrapSignInRequest ? void 0 : this.config.spaceCreationHandler,
19293
19749
  nonce: this.config.nonce,
19294
19750
  siweConfig: this.config.siweConfig,
19295
- manifest: this.config.manifest,
19296
- capabilityRequest: this.config.capabilityRequest,
19297
- includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
19751
+ manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
19752
+ capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
19753
+ includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
19298
19754
  });
19299
19755
  this.tc = new TinyCloud(this.auth, {
19300
19756
  invokeAny: this.invokeAnyWithRuntimePermissions,
@@ -21504,6 +21960,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21504
21960
  delegation,
21505
21961
  targetHost,
21506
21962
  this.wasmBindings.invoke,
21963
+ this.wasmBindings.invokeAny,
21507
21964
  this.config.telemetry
21508
21965
  );
21509
21966
  }
@@ -21569,6 +22026,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21569
22026
  delegation,
21570
22027
  targetHost,
21571
22028
  this.wasmBindings.invoke,
22029
+ this.wasmBindings.invokeAny,
21572
22030
  this.config.telemetry
21573
22031
  );
21574
22032
  }
@@ -21700,7 +22158,14 @@ import {
21700
22158
  pkhDid as pkhDid3,
21701
22159
  principalDid,
21702
22160
  principalDidEquals as principalDidEquals3,
21703
- parseCanonicalNetworkId
22161
+ parseCanonicalNetworkId,
22162
+ TinyCloudDebugLogger,
22163
+ tinyCloudDebugLogger,
22164
+ enableTinyCloudDebug,
22165
+ disableTinyCloudDebug,
22166
+ getTinyCloudDebugLogs,
22167
+ clearTinyCloudDebugLogs,
22168
+ installTinyCloudDebugGlobals
21704
22169
  } from "@tinycloud/sdk-core";
21705
22170
 
21706
22171
  // src/storage/FileSessionStorage.ts
@@ -22034,6 +22499,7 @@ export {
22034
22499
  SpaceErrorCodes,
22035
22500
  SpaceService2 as SpaceService,
22036
22501
  TinyCloud2 as TinyCloud,
22502
+ TinyCloudDebugLogger,
22037
22503
  TinyCloudNode,
22038
22504
  UnsupportedFeatureError2 as UnsupportedFeatureError,
22039
22505
  VAULT_PERMISSION_SERVICE,
@@ -22058,8 +22524,10 @@ export {
22058
22524
  canonicalizeSecretScope,
22059
22525
  checkDecryptInvocationInput,
22060
22526
  checkNodeInfo2 as checkNodeInfo,
22527
+ clearTinyCloudDebugLogs,
22061
22528
  composeManifestRequest2 as composeManifestRequest,
22062
22529
  createCapabilityKeyRegistry,
22530
+ createOpenKeyCallbackSigningStrategy,
22063
22531
  createSharingService,
22064
22532
  createSpaceService,
22065
22533
  createVaultCrypto2 as createVaultCrypto,
@@ -22071,7 +22539,9 @@ export {
22071
22539
  deserializeDelegation,
22072
22540
  didCacheKey,
22073
22541
  didEquals,
22542
+ disableTinyCloudDebug,
22074
22543
  discoverNetwork,
22544
+ enableTinyCloudDebug,
22075
22545
  encryptToNetwork,
22076
22546
  encryptionBase64Decode,
22077
22547
  encryptionBase64Encode,
@@ -22083,9 +22553,11 @@ export {
22083
22553
  expandPermissionEntries2 as expandPermissionEntries,
22084
22554
  expandPermissionEntry,
22085
22555
  generateRandomReceiverKey,
22556
+ getTinyCloudDebugLogs,
22086
22557
  grantAuthRequest,
22087
22558
  hexDecode,
22088
22559
  hexEncode,
22560
+ installTinyCloudDebugGlobals,
22089
22561
  isCapabilitySubset2 as isCapabilitySubset,
22090
22562
  isEvmAddress,
22091
22563
  isNetworkId,
@@ -22107,6 +22579,7 @@ export {
22107
22579
  resolveSecretPath2 as resolveSecretPath,
22108
22580
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
22109
22581
  serializeDelegation,
22582
+ tinyCloudDebugLogger,
22110
22583
  validateEnvelope,
22111
22584
  validateManifest,
22112
22585
  verifyDecryptResponse