@tinycloud/node-sdk 3.7.0-beta.1 → 3.7.0-beta.3

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.
@@ -1400,14 +1400,30 @@ declare class TinyCloudNode {
1400
1400
  */
1401
1401
  private readonly storageFull;
1402
1402
  /**
1403
- * Subscribe to `storage.full`. It fires once, on the first write any
1404
- * service had rejected because the owner's TinyCloud storage is full
1403
+ * Subscribe to `storage.full`. It fires on the first write any service had
1404
+ * rejected because the owner's TinyCloud storage is full
1405
1405
  * (`STORAGE_QUOTA_EXCEEDED` or `STORAGE_LIMIT_REACHED`), so an app can show
1406
- * one read-only banner. Reads keep working. It fires again only after
1407
- * `storage.status()` has reported space again for every owner whose write
1408
- * was rejected. Returns the unsubscribe function.
1406
+ * one read-only banner. Reads keep working. For the signed-in account's own
1407
+ * storage it fires once per episode: the episode ends only when a write to
1408
+ * that storage succeeds, or on sign-out. A `storage.status()` read does not
1409
+ * end it. A rejection on another owner's delegated space fires once per
1410
+ * owner, until that owner's `storage.status()` reports space again. Returns
1411
+ * the unsubscribe function.
1409
1412
  */
1410
1413
  on(event: "storage.full", handler: (event: StorageFullEvent) => void): () => void;
1414
+ /**
1415
+ * Storage episode state for the UI mount. @internal
1416
+ *
1417
+ * `current()` returns the own-storage rejection that started the episode, or
1418
+ * undefined. `subscribe` calls its listener when the own episode starts or
1419
+ * ends; read `current()` inside it. `subscribeDelegated` calls its listener
1420
+ * once per delegated owner found full, until that owner is cleared.
1421
+ */
1422
+ get storageEpisode(): {
1423
+ current(): StorageFullEvent | undefined;
1424
+ subscribe(listener: () => void): () => void;
1425
+ subscribeDelegated(listener: () => void): () => void;
1426
+ };
1411
1427
  /** Owner key of the account the current session's own storage belongs to. */
1412
1428
  private currentStorageOwner;
1413
1429
  /**
@@ -1435,6 +1451,13 @@ declare class TinyCloudNode {
1435
1451
  /** SIWE domain — uses config override or defaults to app.tinycloud.xyz */
1436
1452
  private get siweDomain();
1437
1453
  private readonly invokeWithRuntimePermissions;
1454
+ /**
1455
+ * The session (key, issuer, proof) that signs an invokeAny call for `entries`:
1456
+ * the explicit revocation authority, else the covering non-primary runtime
1457
+ * grant, else the caller's session. Everything that re-signs the resulting
1458
+ * invocation must use this session's key.
1459
+ */
1460
+ private selectInvokeAnySession;
1438
1461
  private readonly invokeAnyWithRuntimePermissions;
1439
1462
  /**
1440
1463
  * Create a new TinyCloudNode instance.
@@ -1400,14 +1400,30 @@ declare class TinyCloudNode {
1400
1400
  */
1401
1401
  private readonly storageFull;
1402
1402
  /**
1403
- * Subscribe to `storage.full`. It fires once, on the first write any
1404
- * service had rejected because the owner's TinyCloud storage is full
1403
+ * Subscribe to `storage.full`. It fires on the first write any service had
1404
+ * rejected because the owner's TinyCloud storage is full
1405
1405
  * (`STORAGE_QUOTA_EXCEEDED` or `STORAGE_LIMIT_REACHED`), so an app can show
1406
- * one read-only banner. Reads keep working. It fires again only after
1407
- * `storage.status()` has reported space again for every owner whose write
1408
- * was rejected. Returns the unsubscribe function.
1406
+ * one read-only banner. Reads keep working. For the signed-in account's own
1407
+ * storage it fires once per episode: the episode ends only when a write to
1408
+ * that storage succeeds, or on sign-out. A `storage.status()` read does not
1409
+ * end it. A rejection on another owner's delegated space fires once per
1410
+ * owner, until that owner's `storage.status()` reports space again. Returns
1411
+ * the unsubscribe function.
1409
1412
  */
1410
1413
  on(event: "storage.full", handler: (event: StorageFullEvent) => void): () => void;
1414
+ /**
1415
+ * Storage episode state for the UI mount. @internal
1416
+ *
1417
+ * `current()` returns the own-storage rejection that started the episode, or
1418
+ * undefined. `subscribe` calls its listener when the own episode starts or
1419
+ * ends; read `current()` inside it. `subscribeDelegated` calls its listener
1420
+ * once per delegated owner found full, until that owner is cleared.
1421
+ */
1422
+ get storageEpisode(): {
1423
+ current(): StorageFullEvent | undefined;
1424
+ subscribe(listener: () => void): () => void;
1425
+ subscribeDelegated(listener: () => void): () => void;
1426
+ };
1411
1427
  /** Owner key of the account the current session's own storage belongs to. */
1412
1428
  private currentStorageOwner;
1413
1429
  /**
@@ -1435,6 +1451,13 @@ declare class TinyCloudNode {
1435
1451
  /** SIWE domain — uses config override or defaults to app.tinycloud.xyz */
1436
1452
  private get siweDomain();
1437
1453
  private readonly invokeWithRuntimePermissions;
1454
+ /**
1455
+ * The session (key, issuer, proof) that signs an invokeAny call for `entries`:
1456
+ * the explicit revocation authority, else the covering non-primary runtime
1457
+ * grant, else the caller's session. Everything that re-signs the resulting
1458
+ * invocation must use this session's key.
1459
+ */
1460
+ private selectInvokeAnySession;
1438
1461
  private readonly invokeAnyWithRuntimePermissions;
1439
1462
  /**
1440
1463
  * Create a new TinyCloudNode instance.
@@ -1,7 +1,7 @@
1
1
  import { ReplicationOptions, PermissionEntry, TinyCloudSession, KVReplicaStorage, ServiceContext, IWasmBindings, WasmRecapEntry } from '@tinycloud/sdk-core';
2
2
  import { ReplicaConsentAnchor, AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
3
3
  import { ReplicaSessionStamp, ReplicaDenyInput } from '@tinycloud/sdk-services/kv/replication';
4
- import { T as TinyCloudNode } from './TinyCloudNode-DrLNh4bs.cjs';
4
+ import { T as TinyCloudNode } from './TinyCloudNode-DD7qjyLU.cjs';
5
5
 
6
6
  /** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */
7
7
  declare function assertValidReplicationConfig(replication: (ReplicationOptions & {
@@ -1,7 +1,7 @@
1
1
  import { ReplicationOptions, PermissionEntry, TinyCloudSession, KVReplicaStorage, ServiceContext, IWasmBindings, WasmRecapEntry } from '@tinycloud/sdk-core';
2
2
  import { ReplicaConsentAnchor, AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
3
3
  import { ReplicaSessionStamp, ReplicaDenyInput } from '@tinycloud/sdk-services/kv/replication';
4
- import { T as TinyCloudNode } from './TinyCloudNode-BHiKCQ4W.js';
4
+ import { T as TinyCloudNode } from './TinyCloudNode-Cw00iiSP.js';
5
5
 
6
6
  /** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */
7
7
  declare function assertValidReplicationConfig(replication: (ReplicationOptions & {
package/dist/core.cjs CHANGED
@@ -3854,7 +3854,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3854
3854
  * and remembers whose storage is full so sign-in and listing skip their
3855
3855
  * best-effort writes. Lives as long as this instance, across sign-ins.
3856
3856
  */
3857
- this.storageFull = new import_sdk_core11.StorageFullMonitor((spaceId) => spaceOwnerKey(spaceId) ?? spaceId);
3857
+ this.storageFull = new import_sdk_core11.StorageFullMonitor(
3858
+ (spaceId) => spaceOwnerKey(spaceId) ?? spaceId,
3859
+ () => this.currentStorageOwner()
3860
+ );
3858
3861
  this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
3859
3862
  const operation = {
3860
3863
  spaceId: session.spaceId,
@@ -3886,17 +3889,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3886
3889
  throw new Error("WASM binding does not support invokeAny");
3887
3890
  }
3888
3891
  const routedEntries = entries;
3889
- const operations = routedEntries.flatMap((entry) => {
3890
- const operation = this.operationFromInvokeAnyEntry(entry);
3891
- return operation ? [operation] : [];
3892
- });
3893
- const authorityCid = routedEntries.length === 1 ? routedEntries[0]?.revokeAuthorityCid : void 0;
3894
- const explicitGrant = authorityCid === void 0 ? void 0 : this.runtimePermissionGrants.find((grant2) => grant2.delegation.cid === authorityCid);
3895
- if (authorityCid !== void 0 && !explicitGrant) {
3896
- throw new Error(`Explicit revocation authority ${authorityCid} is not installed.`);
3897
- }
3898
- const grant = explicitGrant ?? this.findGrantForOperations(operations);
3899
- const invocationSession = explicitGrant ? explicitGrant.session : !grant || grant.provenance === "primary" ? session : grant.session;
3892
+ const { session: invocationSession, grant } = this.selectInvokeAnySession(session, entries);
3900
3893
  const caveatPreservingEntries = routedEntries.map(({ revokeAuthorityCid: _cid, ...entry }) => {
3901
3894
  const requested = this.operationFromInvokeAnyEntry(entry);
3902
3895
  const granted = grant && requested && grant.operations.find(
@@ -4020,12 +4013,15 @@ var _TinyCloudNode = class _TinyCloudNode {
4020
4013
  return this._bootstrapStatus;
4021
4014
  }
4022
4015
  /**
4023
- * Subscribe to `storage.full`. It fires once, on the first write any
4024
- * service had rejected because the owner's TinyCloud storage is full
4016
+ * Subscribe to `storage.full`. It fires on the first write any service had
4017
+ * rejected because the owner's TinyCloud storage is full
4025
4018
  * (`STORAGE_QUOTA_EXCEEDED` or `STORAGE_LIMIT_REACHED`), so an app can show
4026
- * one read-only banner. Reads keep working. It fires again only after
4027
- * `storage.status()` has reported space again for every owner whose write
4028
- * was rejected. Returns the unsubscribe function.
4019
+ * one read-only banner. Reads keep working. For the signed-in account's own
4020
+ * storage it fires once per episode: the episode ends only when a write to
4021
+ * that storage succeeds, or on sign-out. A `storage.status()` read does not
4022
+ * end it. A rejection on another owner's delegated space fires once per
4023
+ * owner, until that owner's `storage.status()` reports space again. Returns
4024
+ * the unsubscribe function.
4029
4025
  */
4030
4026
  on(event, handler) {
4031
4027
  if (event !== "storage.full") {
@@ -4033,6 +4029,21 @@ var _TinyCloudNode = class _TinyCloudNode {
4033
4029
  }
4034
4030
  return this.storageFull.on(handler);
4035
4031
  }
4032
+ /**
4033
+ * Storage episode state for the UI mount. @internal
4034
+ *
4035
+ * `current()` returns the own-storage rejection that started the episode, or
4036
+ * undefined. `subscribe` calls its listener when the own episode starts or
4037
+ * ends; read `current()` inside it. `subscribeDelegated` calls its listener
4038
+ * once per delegated owner found full, until that owner is cleared.
4039
+ */
4040
+ get storageEpisode() {
4041
+ return {
4042
+ current: () => this.storageFull.rejection,
4043
+ subscribe: (listener) => this.storageFull.onEpisode(listener),
4044
+ subscribeDelegated: (listener) => this.storageFull.onDelegated(listener)
4045
+ };
4046
+ }
4036
4047
  /** Owner key of the account the current session's own storage belongs to. */
4037
4048
  currentStorageOwner() {
4038
4049
  return spaceOwnerKey(this._serviceContext?.session?.spaceId ?? this.currentTinyCloudSession()?.spaceId);
@@ -4134,6 +4145,27 @@ var _TinyCloudNode = class _TinyCloudNode {
4134
4145
  get siweDomain() {
4135
4146
  return this.config.domain ?? "app.tinycloud.xyz";
4136
4147
  }
4148
+ /**
4149
+ * The session (key, issuer, proof) that signs an invokeAny call for `entries`:
4150
+ * the explicit revocation authority, else the covering non-primary runtime
4151
+ * grant, else the caller's session. Everything that re-signs the resulting
4152
+ * invocation must use this session's key.
4153
+ */
4154
+ selectInvokeAnySession(session, entries) {
4155
+ const routedEntries = entries;
4156
+ const operations = routedEntries.flatMap((entry) => {
4157
+ const operation = this.operationFromInvokeAnyEntry(entry);
4158
+ return operation ? [operation] : [];
4159
+ });
4160
+ const authorityCid = routedEntries.length === 1 ? routedEntries[0]?.revokeAuthorityCid : void 0;
4161
+ const explicitGrant = authorityCid === void 0 ? void 0 : this.runtimePermissionGrants.find((grant2) => grant2.delegation.cid === authorityCid);
4162
+ if (authorityCid !== void 0 && !explicitGrant) {
4163
+ throw new Error(`Explicit revocation authority ${authorityCid} is not installed.`);
4164
+ }
4165
+ const grant = explicitGrant ?? this.findGrantForOperations(operations);
4166
+ const invocationSession = explicitGrant ? explicitGrant.session : !grant || grant.provenance === "primary" ? session : grant.session;
4167
+ return { session: invocationSession, grant };
4168
+ }
4137
4169
  /**
4138
4170
  * SharingService for receive-only access (no session required), bound to the
4139
4171
  * current service graph. This lets session-only users receive sharing links
@@ -6553,7 +6585,13 @@ var _TinyCloudNode = class _TinyCloudNode {
6553
6585
  async bindInvocationAudience(authorization, audience) {
6554
6586
  const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
6555
6587
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
6556
- return rewriteInvocationAudience(authorization, audience, session.jwk);
6588
+ const payload = JSON.parse(
6589
+ new TextDecoder().decode(base64UrlDecode(authorization.split(".")[1] ?? ""))
6590
+ );
6591
+ const did = (value) => String(value).split("#", 1)[0];
6592
+ const signer = [session, ...this.runtimePermissionGrants.map((grant) => grant.session)].find((candidate) => did(candidate.verificationMethod) === did(payload.iss));
6593
+ if (!signer) throw new Error("invocation issuer does not match a held session");
6594
+ return rewriteInvocationAudience(authorization, audience, signer.jwk);
6557
6595
  }
6558
6596
  /**
6559
6597
  * Connect a wallet to upgrade from session-only mode to wallet mode.
@@ -6888,23 +6926,25 @@ var _TinyCloudNode = class _TinyCloudNode {
6888
6926
  throw new Error("WASM binding does not support invocation CID computation");
6889
6927
  }
6890
6928
  const session = binding?.session ?? this.requireEncryptionSession();
6929
+ const entries = [
6930
+ {
6931
+ resource: input.networkId,
6932
+ service: "encryption",
6933
+ path: input.networkId,
6934
+ action: input.action
6935
+ }
6936
+ ];
6891
6937
  const headers = this.invokeAnyWithRuntimePermissions(
6892
6938
  session,
6893
- [
6894
- {
6895
- resource: input.networkId,
6896
- service: "encryption",
6897
- path: input.networkId,
6898
- action: input.action
6899
- }
6900
- ],
6939
+ entries,
6901
6940
  [input.facts]
6902
6941
  );
6942
+ const signingSession = this.selectInvokeAnySession(session, entries).session;
6903
6943
  const authorization = authorizationHeader(headers);
6904
6944
  const audienceBound = await rewriteInvocationAudience(
6905
6945
  authorization,
6906
6946
  input.targetNode,
6907
- session.jwk
6947
+ signingSession.jwk
6908
6948
  );
6909
6949
  binding?.graph.assertActive();
6910
6950
  return {
@@ -6957,7 +6997,11 @@ var _TinyCloudNode = class _TinyCloudNode {
6957
6997
  path: networkId,
6958
6998
  actions: ["tinycloud.encryption/decrypt"]
6959
6999
  }) : void 0;
6960
- throw new import_sdk_core11.DecryptTransportResponseError(response.status, permissionHint);
7000
+ throw new import_sdk_core11.DecryptTransportResponseError(
7001
+ response.status,
7002
+ permissionHint,
7003
+ response.status === 404 && networkId === this.getDefaultEncryptionNetworkId() && await response.text().catch(() => "") === "network not found" ? `Network not found: ${networkId}. Sign in again with a current SDK to create the account's encryption network.` : void 0
7004
+ );
6961
7005
  }
6962
7006
  return await response.json();
6963
7007
  }