@tinycloud/node-sdk 2.4.0 → 2.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -17722,7 +17722,7 @@ var NodeUserAuthorization = class {
17722
17722
  * Create the space on the TinyCloud server (host delegation).
17723
17723
  * This registers the user as the owner of the space.
17724
17724
  */
17725
- async hostSpace(targetSpaceId) {
17725
+ async hostSpace(targetSpaceId, purpose) {
17726
17726
  if (!this._tinyCloudSession || !this._address || !this._chainId) {
17727
17727
  throw new Error("Must be signed in to host space");
17728
17728
  }
@@ -17738,7 +17738,7 @@ var NodeUserAuthorization = class {
17738
17738
  spaceId,
17739
17739
  peerId
17740
17740
  });
17741
- const signature2 = await this.signMessage(siwe);
17741
+ const signature2 = await this.signMessage(siwe, purpose);
17742
17742
  const headers = this.wasm.siweToDelegationHeaders({ siwe, signature: signature2 });
17743
17743
  const result = await (0, import_sdk_core2.submitHostDelegation)(host, headers);
17744
17744
  return result.success;
@@ -17754,8 +17754,8 @@ var NodeUserAuthorization = class {
17754
17754
  * Create a specific owned space on the server via host delegation.
17755
17755
  * Used by manifest registry setup for the account space.
17756
17756
  */
17757
- async hostOwnedSpace(spaceId) {
17758
- return this.hostSpace(spaceId);
17757
+ async hostOwnedSpace(spaceId, purpose) {
17758
+ return this.hostSpace(spaceId, purpose);
17759
17759
  }
17760
17760
  /**
17761
17761
  * Ensure the user's space exists on the TinyCloud server.
@@ -17905,7 +17905,8 @@ var NodeUserAuthorization = class {
17905
17905
  address,
17906
17906
  chainId,
17907
17907
  message: prepared.siwe,
17908
- type: "siwe"
17908
+ type: "siwe",
17909
+ purpose: "bootstrap-session"
17909
17910
  });
17910
17911
  const session = this.wasm.completeSessionSetup({
17911
17912
  ...prepared,
@@ -17969,7 +17970,8 @@ var NodeUserAuthorization = class {
17969
17970
  address,
17970
17971
  chainId,
17971
17972
  message: prepared.siwe,
17972
- type: "siwe"
17973
+ type: "siwe",
17974
+ purpose: "sign-in"
17973
17975
  });
17974
17976
  const session = this.wasm.completeSessionSetup({
17975
17977
  ...prepared,
@@ -18057,7 +18059,7 @@ var NodeUserAuthorization = class {
18057
18059
  /**
18058
18060
  * Sign a message with the connected signer.
18059
18061
  */
18060
- async signMessage(message) {
18062
+ async signMessage(message, purpose) {
18061
18063
  if (!this._address) {
18062
18064
  this._address = (0, import_sdk_core2.canonicalizeAddress)(await this.signer.getAddress());
18063
18065
  }
@@ -18068,7 +18070,8 @@ var NodeUserAuthorization = class {
18068
18070
  address: this._address,
18069
18071
  chainId: this._chainId,
18070
18072
  message,
18071
- type: "message"
18073
+ type: "message",
18074
+ ...purpose ? { purpose } : {}
18072
18075
  });
18073
18076
  }
18074
18077
  /**
@@ -18540,12 +18543,12 @@ function displayActionUrn(action) {
18540
18543
  function secretActionName(action) {
18541
18544
  return action;
18542
18545
  }
18543
- function secretPermissionEntries(name, options, action, encryptionNetworkId) {
18546
+ function secretPermissionEntries(name, options, action, space, encryptionNetworkId) {
18544
18547
  const entries = [];
18545
18548
  const path = action === "list" ? (0, import_sdk_core6.resolveSecretListPrefix)(options) : (0, import_sdk_core6.resolveSecretPath)(name, options).permissionPaths.vault;
18546
18549
  entries.push({
18547
18550
  service: "tinycloud.kv",
18548
- space: SECRETS_SPACE,
18551
+ space,
18549
18552
  path,
18550
18553
  actions: [secretActionName(action)],
18551
18554
  skipPrefix: true
@@ -18560,11 +18563,23 @@ function secretPermissionEntries(name, options, action, encryptionNetworkId) {
18560
18563
  }
18561
18564
  return entries;
18562
18565
  }
18566
+ function normalizeSpace(space, resolveSpace) {
18567
+ if (!space) return void 0;
18568
+ if (space.startsWith("tinycloud:")) return space;
18569
+ return resolveSpace?.(space) ?? space;
18570
+ }
18571
+ function spaceMatches(granted, requested, resolveSpace) {
18572
+ if (!granted || !requested) return false;
18573
+ return normalizeSpace(granted, resolveSpace) === normalizeSpace(requested, resolveSpace);
18574
+ }
18563
18575
  var NodeSecretsService = class {
18564
18576
  constructor(config) {
18565
18577
  this.config = config;
18566
18578
  this.shouldRestoreUnlock = false;
18567
18579
  }
18580
+ get space() {
18581
+ return this.config.space ?? SECRETS_SPACE;
18582
+ }
18568
18583
  get vault() {
18569
18584
  return this.service.vault;
18570
18585
  }
@@ -18617,6 +18632,7 @@ var NodeSecretsService = class {
18617
18632
  name,
18618
18633
  options,
18619
18634
  action,
18635
+ this.space,
18620
18636
  action === "get" ? this.config.getEncryptionNetworkId?.() : void 0
18621
18637
  );
18622
18638
  } catch (error) {
@@ -18666,7 +18682,7 @@ var NodeSecretsService = class {
18666
18682
  (entry) => manifests.some((candidate) => {
18667
18683
  const resolved = (0, import_sdk_core6.resolveManifest)(candidate);
18668
18684
  return resolved.resources.some(
18669
- (resource) => resource.service === entry.service && resource.space === entry.space && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
18685
+ (resource) => resource.service === entry.service && spaceMatches(resource.space, entry.space, this.config.resolveSpace) && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
18670
18686
  );
18671
18687
  })
18672
18688
  );
@@ -18683,6 +18699,15 @@ var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core7.EXPIRY.SESSION_MS;
18683
18699
  function isOpenKeyAutoSignStrategy(strategy) {
18684
18700
  return strategy?.openKeyAutoSign === true;
18685
18701
  }
18702
+ function isInteractiveSigner(config) {
18703
+ if (config.privateKey) {
18704
+ return false;
18705
+ }
18706
+ if (isOpenKeyAutoSignStrategy(config.signStrategy)) {
18707
+ return false;
18708
+ }
18709
+ return config.signer !== void 0;
18710
+ }
18686
18711
  function didPrincipalMatches2(actual, expected) {
18687
18712
  try {
18688
18713
  return (0, import_sdk_core7.principalDidEquals)(actual, expected);
@@ -18690,6 +18715,20 @@ function didPrincipalMatches2(actual, expected) {
18690
18715
  return actual === expected;
18691
18716
  }
18692
18717
  }
18718
+ function sharingActionsToAbilities(path, actions) {
18719
+ var _a;
18720
+ const abilities = {};
18721
+ for (const action of actions) {
18722
+ const slash = action.indexOf("/");
18723
+ if (slash === -1) return void 0;
18724
+ const shortService = import_sdk_core7.SERVICE_LONG_TO_SHORT[action.slice(0, slash)];
18725
+ if (shortService === void 0) return void 0;
18726
+ abilities[shortService] ?? (abilities[shortService] = {});
18727
+ (_a = abilities[shortService])[path] ?? (_a[path] = []);
18728
+ abilities[shortService][path].push(action);
18729
+ }
18730
+ return Object.keys(abilities).length > 0 ? abilities : void 0;
18731
+ }
18693
18732
  function base64UrlEncode(bytes) {
18694
18733
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
18695
18734
  let output = "";
@@ -18802,7 +18841,24 @@ var _TinyCloudNode = class _TinyCloudNode {
18802
18841
  this.auth = null;
18803
18842
  this.tc = null;
18804
18843
  this._chainId = 1;
18844
+ this._baseSecrets = /* @__PURE__ */ new Map();
18845
+ this._secrets = /* @__PURE__ */ new Map();
18805
18846
  this.runtimePermissionGrants = [];
18847
+ /**
18848
+ * True when the last signIn() detected an interactive signer and skipped
18849
+ * client-side bootstrap. Apps can read this to know whether bootstrap was
18850
+ * deferred to the server (OpenKey) or requires a separate user action.
18851
+ */
18852
+ this._bootstrapSkipped = false;
18853
+ /**
18854
+ * Outcome of the last signIn()'s account-bootstrap attempt. `skipped` is
18855
+ * true when bootstrap did not complete (interactive signer, auto-sign
18856
+ * denied, or a bootstrap step failed); `reason` carries the cause so apps
18857
+ * can surface a "finish account setup" call-to-action.
18858
+ */
18859
+ this._bootstrapStatus = {
18860
+ skipped: false
18861
+ };
18806
18862
  this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
18807
18863
  return this.wasmBindings.invoke(
18808
18864
  this.selectInvocationSession(session, service, path, action),
@@ -18895,6 +18951,15 @@ var _TinyCloudNode = class _TinyCloudNode {
18895
18951
  static registerNodeDefaults(defaults) {
18896
18952
  _TinyCloudNode.nodeDefaults = defaults;
18897
18953
  }
18954
+ /** Whether the last signIn() skipped client-side bootstrap because the
18955
+ * signer is interactive (browser wallet / EIP-1193 provider). */
18956
+ get bootstrapSkipped() {
18957
+ return this._bootstrapSkipped;
18958
+ }
18959
+ /** Outcome of the last signIn()'s account-bootstrap attempt. */
18960
+ get bootstrapStatus() {
18961
+ return this._bootstrapStatus;
18962
+ }
18898
18963
  get nodeFeatures() {
18899
18964
  return this.auth?.nodeFeatures ?? [];
18900
18965
  }
@@ -19064,6 +19129,13 @@ var _TinyCloudNode = class _TinyCloudNode {
19064
19129
  get session() {
19065
19130
  return this.auth?.tinyCloudSession;
19066
19131
  }
19132
+ /**
19133
+ * Get the currently active session in the shape callers can persist and later
19134
+ * pass back to {@link restoreSession}.
19135
+ */
19136
+ get restorableSession() {
19137
+ return this.currentTinyCloudSession();
19138
+ }
19067
19139
  /**
19068
19140
  * Sign in and create a new session.
19069
19141
  * This creates the user's space if it doesn't exist.
@@ -19086,8 +19158,8 @@ var _TinyCloudNode = class _TinyCloudNode {
19086
19158
  this._hooks = void 0;
19087
19159
  this._vault = void 0;
19088
19160
  this._encryption = void 0;
19089
- this._baseSecrets = void 0;
19090
- this._secrets = void 0;
19161
+ this._baseSecrets.clear();
19162
+ this._secrets.clear();
19091
19163
  this._spaceService = void 0;
19092
19164
  this._serviceContext = void 0;
19093
19165
  this.runtimePermissionGrants = [];
@@ -19111,17 +19183,38 @@ var _TinyCloudNode = class _TinyCloudNode {
19111
19183
  return this.wasmBindings.makeSpaceId(this._address, this._chainId, name);
19112
19184
  }
19113
19185
  async bootstrapAccountIfNeeded() {
19186
+ this._bootstrapSkipped = false;
19187
+ this._bootstrapStatus = { skipped: false };
19114
19188
  if (this.config.autoBootstrapAccount === false) {
19115
19189
  return false;
19116
19190
  }
19117
19191
  if (!this.auth || !this._address) {
19118
19192
  return false;
19119
19193
  }
19194
+ if (isInteractiveSigner(this.config)) {
19195
+ console.debug(
19196
+ "[TinyCloudNode] bootstrap skipped: interactive signer detected. Server-side bootstrap (OpenKey) is expected to have provisioned the account."
19197
+ );
19198
+ this._bootstrapSkipped = true;
19199
+ this._bootstrapStatus = { skipped: true, reason: "interactive-signer" };
19200
+ return false;
19201
+ }
19120
19202
  const steps = (0, import_sdk_core7.bootstrapSteps)(this._address, this._chainId);
19121
19203
  if (!await this.isFreshBootstrapAccount(steps)) {
19122
19204
  return false;
19123
19205
  }
19124
- await this.runAccountBootstrap(steps);
19206
+ try {
19207
+ await this.runAccountBootstrap(steps);
19208
+ } catch (err) {
19209
+ const reason = err instanceof Error ? err.message : String(err);
19210
+ this._bootstrapSkipped = true;
19211
+ this._bootstrapStatus = { skipped: true, reason };
19212
+ this.notificationHandler.warning(
19213
+ `Account bootstrap did not complete: ${reason}`
19214
+ );
19215
+ console.warn(`[TinyCloudNode] account bootstrap failed: ${reason}`);
19216
+ return false;
19217
+ }
19125
19218
  return true;
19126
19219
  }
19127
19220
  async isFreshBootstrapAccount(steps) {
@@ -19173,16 +19266,23 @@ var _TinyCloudNode = class _TinyCloudNode {
19173
19266
  if (rawAbilities) {
19174
19267
  rawAbilitiesBySpace.set(step.space, rawAbilities);
19175
19268
  }
19176
- const session = await auth.createBootstrapSession({
19177
- spaceId: step.spaceId,
19178
- capabilityRequest: step.request ?? import_sdk_core7.BOOTSTRAP_SESSION_REQUESTS[step.space],
19179
- rawAbilities
19180
- });
19269
+ let session;
19270
+ try {
19271
+ session = await auth.createBootstrapSession({
19272
+ spaceId: step.spaceId,
19273
+ capabilityRequest: step.request ?? import_sdk_core7.BOOTSTRAP_SESSION_REQUESTS[step.space],
19274
+ rawAbilities
19275
+ });
19276
+ } catch (err) {
19277
+ throw new Error(
19278
+ `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
19279
+ );
19280
+ }
19181
19281
  sessions.set(step.space, session);
19182
19282
  }
19183
19283
  for (const step of steps) {
19184
19284
  if (step.kind !== "host") continue;
19185
- const hosted = await auth.hostOwnedSpace(step.spaceId);
19285
+ const hosted = await auth.hostOwnedSpace(step.spaceId, "bootstrap-host");
19186
19286
  if (!hosted) {
19187
19287
  throw new Error(`Failed to host bootstrap space: ${step.spaceId}`);
19188
19288
  }
@@ -19573,8 +19673,8 @@ var _TinyCloudNode = class _TinyCloudNode {
19573
19673
  this._hooks = void 0;
19574
19674
  this._vault = void 0;
19575
19675
  this._encryption = void 0;
19576
- this._baseSecrets = void 0;
19577
- this._secrets = void 0;
19676
+ this._baseSecrets.clear();
19677
+ this._secrets.clear();
19578
19678
  this._spaceService = void 0;
19579
19679
  this._serviceContext = void 0;
19580
19680
  this.runtimePermissionGrants = [];
@@ -19864,6 +19964,20 @@ var _TinyCloudNode = class _TinyCloudNode {
19864
19964
  getDefaultEncryptionNetworkId(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
19865
19965
  return `urn:tinycloud:encryption:${this.did}:${name}`;
19866
19966
  }
19967
+ getEncryptionNetworkIdForSpace(spaceId, name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
19968
+ const ownerDid = this.ownerDidFromSpaceId(spaceId) ?? this.did;
19969
+ return `urn:tinycloud:encryption:${ownerDid}:${name}`;
19970
+ }
19971
+ ownerDidFromSpaceId(spaceId) {
19972
+ if (!spaceId.startsWith("tinycloud:")) return void 0;
19973
+ const body = spaceId.slice("tinycloud:".length);
19974
+ const lastSeparator = body.lastIndexOf(":");
19975
+ if (lastSeparator <= 0) return void 0;
19976
+ const owner = body.slice(0, lastSeparator);
19977
+ if (owner.startsWith("did:")) return owner;
19978
+ if (!owner.includes(":")) return void 0;
19979
+ return `did:${owner}`;
19980
+ }
19867
19981
  requireServiceSession() {
19868
19982
  const session = this._serviceContext?.session;
19869
19983
  if (!session) {
@@ -20051,7 +20165,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20051
20165
  spaceId,
20052
20166
  crypto: vaultCrypto,
20053
20167
  encryption: {
20054
- networkId: this.getDefaultEncryptionNetworkId(),
20168
+ networkId: this.getEncryptionNetworkIdForSpace(spaceId),
20055
20169
  service: this.getEncryptionService(),
20056
20170
  decryptCapabilityProof: () => ({
20057
20171
  proofs: [this.requireServiceSession().delegationCid]
@@ -20182,6 +20296,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20182
20296
  }
20183
20297
  return vaultService;
20184
20298
  },
20299
+ createSecretsService: (spaceId) => {
20300
+ return this.secretsForSpace(spaceId);
20301
+ },
20185
20302
  // Enable space.delegations.create() via SIWE-based delegation
20186
20303
  createDelegation: async (params) => {
20187
20304
  try {
@@ -20304,11 +20421,10 @@ var _TinyCloudNode = class _TinyCloudNode {
20304
20421
  try {
20305
20422
  const host = this.config.host;
20306
20423
  const now = /* @__PURE__ */ new Date();
20307
- const abilities = {
20308
- kv: {
20309
- [params.path]: params.actions
20310
- }
20311
- };
20424
+ const abilities = sharingActionsToAbilities(params.path, params.actions);
20425
+ if (!abilities) {
20426
+ return void 0;
20427
+ }
20312
20428
  const prepared = this.wasmBindings.prepareSession({
20313
20429
  abilities,
20314
20430
  address: this.wasmBindings.ensureEip55(session.address),
@@ -20564,27 +20680,49 @@ var _TinyCloudNode = class _TinyCloudNode {
20564
20680
  if (!this._spaceService) {
20565
20681
  throw new Error("Not signed in. Call signIn() first.");
20566
20682
  }
20567
- if (!this._secrets) {
20568
- this._secrets = new NodeSecretsService({
20569
- getService: () => this.getBaseSecrets(),
20683
+ return this.secretsForSpace("secrets");
20684
+ }
20685
+ /**
20686
+ * App-facing secrets API backed by the requested space's vault.
20687
+ */
20688
+ secretsForSpace(spaceId) {
20689
+ if (!this._spaceService) {
20690
+ throw new Error("Not signed in. Call signIn() first.");
20691
+ }
20692
+ const resolvedSpace = spaceId.startsWith("tinycloud:") ? spaceId : this.ownedSpaceId(spaceId);
20693
+ let secrets = this._secrets.get(resolvedSpace);
20694
+ if (!secrets) {
20695
+ secrets = new NodeSecretsService({
20696
+ getService: () => this.getBaseSecrets(resolvedSpace),
20697
+ space: resolvedSpace,
20570
20698
  getManifest: () => this.manifest,
20571
20699
  hasPermissions: (permissions) => this.hasRuntimePermissions(permissions),
20572
20700
  grantPermissions: (additional) => this.grantRuntimePermissions(additional),
20573
20701
  canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
20574
- getEncryptionNetworkId: () => this.getDefaultEncryptionNetworkId(),
20702
+ getEncryptionNetworkId: () => this.getEncryptionNetworkIdForSpace(resolvedSpace),
20703
+ resolveSpace: (space) => space.startsWith("tinycloud:") ? space : this.ownedSpaceId(space),
20575
20704
  getUnlockSigner: () => this.signer ?? void 0
20576
20705
  });
20706
+ this._secrets.set(resolvedSpace, secrets);
20577
20707
  }
20578
- return this._secrets;
20708
+ return secrets;
20579
20709
  }
20580
- getBaseSecrets() {
20710
+ getBaseSecrets(spaceId) {
20581
20711
  if (!this._spaceService) {
20582
20712
  throw new Error("Not signed in. Call signIn() first.");
20583
20713
  }
20584
- if (!this._baseSecrets) {
20585
- this._baseSecrets = new import_sdk_core7.SecretsService(() => this.space("secrets").vault);
20714
+ const resolvedSpace = spaceId.startsWith("tinycloud:") ? spaceId : this.ownedSpaceId(spaceId);
20715
+ let secrets = this._baseSecrets.get(resolvedSpace);
20716
+ if (!secrets) {
20717
+ const kvService = this.createSpaceScopedKVService(resolvedSpace);
20718
+ const vaultService = this.createVaultService(resolvedSpace, kvService);
20719
+ if (this._serviceContext) {
20720
+ vaultService.initialize(this._serviceContext);
20721
+ }
20722
+ secrets = new import_sdk_core7.SecretsService(() => vaultService);
20723
+ this._baseSecrets.set(resolvedSpace, secrets);
20586
20724
  }
20587
- return this._baseSecrets;
20725
+ return secrets;
20588
20726
  }
20589
20727
  /**
20590
20728
  * Hooks write stream subscription API.