@tinycloud/node-sdk 3.8.0-beta.1 → 3.8.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.
@@ -1,6 +1,6 @@
1
1
  import { NamedKeyClient } from '@tinycloud/sdk-services/encryption/named-keys';
2
2
  export { DecryptManyResult, KeyCallOptions, KeyDescriptor, KeyEncryptOptions, NamedKeyClient, NamedKeyEnvelope, NamedKeyError, NamedKeyErrorCode, PublicKeyDescriptor, buildKeyAdminFacts, buildKeyDescribeFacts } from '@tinycloud/sdk-services/encryption/named-keys';
3
- import { T as TinyCloudNode } from './TinyCloudNode-DDfx8Fx5.cjs';
3
+ import { T as TinyCloudNode } from './TinyCloudNode-CVO41O12.cjs';
4
4
  import '@tinycloud/sdk-core';
5
5
  import '@tinycloud/sdk-services/kv/replication';
6
6
  import '@tinycloud/sdk-services';
@@ -1,6 +1,6 @@
1
1
  import { NamedKeyClient } from '@tinycloud/sdk-services/encryption/named-keys';
2
2
  export { DecryptManyResult, KeyCallOptions, KeyDescriptor, KeyEncryptOptions, NamedKeyClient, NamedKeyEnvelope, NamedKeyError, NamedKeyErrorCode, PublicKeyDescriptor, buildKeyAdminFacts, buildKeyDescribeFacts } from '@tinycloud/sdk-services/encryption/named-keys';
3
- import { T as TinyCloudNode } from './TinyCloudNode-Ckqu_fqH.js';
3
+ import { T as TinyCloudNode } from './TinyCloudNode-B6VOBrCb.js';
4
4
  import '@tinycloud/sdk-core';
5
5
  import '@tinycloud/sdk-services/kv/replication';
6
6
  import '@tinycloud/sdk-services';
package/dist/index.cjs CHANGED
@@ -17414,6 +17414,7 @@ __export(authority_exports, {
17414
17414
  assertValidReplicationConfig: () => assertValidReplicationConfig,
17415
17415
  augmentSignInEntriesWithReplication: () => augmentSignInEntriesWithReplication,
17416
17416
  createReplicationAuthority: () => createReplicationAuthority,
17417
+ eraseReplicaLocalCopy: () => eraseReplicaLocalCopy,
17417
17418
  establishReplicaSessionStamp: () => establishReplicaSessionStamp,
17418
17419
  hasUnrestrictedGetCoverage: () => hasUnrestrictedGetCoverage,
17419
17420
  mintNodeReplicationGrant: () => mintNodeReplicationGrant,
@@ -17444,6 +17445,38 @@ function reportReplicaDenial(result, reason, onEvent, context) {
17444
17445
  } catch {
17445
17446
  }
17446
17447
  }
17448
+ async function eraseReplicaLocalCopy(lifecycle, trigger, denyStatus, handlers) {
17449
+ if (lifecycle?.kind !== "indexeddb" || lifecycle.eraseLocalCopy === void 0) return void 0;
17450
+ let outcome = { status: "failed", code: "STORAGE_ERROR", marker: false };
17451
+ for (let attempt = 0; attempt <= ERASE_RETRY_DELAYS_MS.length; attempt++) {
17452
+ if (attempt > 0) await new Promise((resolve2) => setTimeout(resolve2, ERASE_RETRY_DELAYS_MS[attempt - 1]));
17453
+ try {
17454
+ outcome = await lifecycle.eraseLocalCopy({ signal: AbortSignal.timeout(ERASE_ATTEMPT_MS) });
17455
+ } catch {
17456
+ outcome = { status: "failed", code: "STORAGE_ERROR", marker: false };
17457
+ }
17458
+ if (outcome.status !== "failed") break;
17459
+ }
17460
+ const keysKept = denyStatus === "marker" || denyStatus === "unguarded";
17461
+ const purge = outcome.status === "erased" ? { ok: true } : { ok: false, retryNow: outcome.status === "failed" };
17462
+ if (outcome.status === "erased" || outcome.status === "refused" && !keysKept) return { outcome, purge };
17463
+ const logoutPending = outcome.status === "failed" && outcome.marker;
17464
+ const event = {
17465
+ type: "replica.purge.failed",
17466
+ trigger,
17467
+ reason: logoutPending ? "logout_pending" : "gate_unavailable",
17468
+ code: outcome.code,
17469
+ retryNow: outcome.status === "failed"
17470
+ };
17471
+ for (const handler of handlers) {
17472
+ try {
17473
+ handler(event);
17474
+ } catch (error) {
17475
+ console.error('Error in "replica.purge.failed" handler:', error);
17476
+ }
17477
+ }
17478
+ return logoutPending ? { outcome, purge, logoutPending } : { outcome, purge };
17479
+ }
17447
17480
  function verifiedOwnerConsentAnchor(session, wasmBindings, exactVerifiedRecap2) {
17448
17481
  if (typeof session.siwe !== "string" || typeof session.signature !== "string" || typeof session.address !== "string" || typeof session.chainId !== "number") return null;
17449
17482
  try {
@@ -17702,7 +17735,7 @@ function createReplicationAuthority(host) {
17702
17735
  };
17703
17736
  return authority;
17704
17737
  }
17705
- var import_siwe3, import_sdk_core12, import_replica, parseUcanGrant, KV_GET2, KV_SYNC2, KV_SERVICE2, AUTHORITY_EXPIRY_MARGIN_MS;
17738
+ var import_siwe3, import_sdk_core12, import_replica, parseUcanGrant, ERASE_RETRY_DELAYS_MS, ERASE_ATTEMPT_MS, KV_GET2, KV_SYNC2, KV_SERVICE2, AUTHORITY_EXPIRY_MARGIN_MS;
17706
17739
  var init_authority = __esm({
17707
17740
  "src/replication/authority.ts"() {
17708
17741
  "use strict";
@@ -17712,6 +17745,8 @@ var init_authority = __esm({
17712
17745
  init_config();
17713
17746
  init_authority_sign_in();
17714
17747
  parseUcanGrant = import_replica.parseUcanGrant;
17748
+ ERASE_RETRY_DELAYS_MS = [150, 450];
17749
+ ERASE_ATTEMPT_MS = 3e3;
17715
17750
  KV_GET2 = import_sdk_core12.KV.GET;
17716
17751
  KV_SYNC2 = import_sdk_core12.KV.SYNC;
17717
17752
  KV_SERVICE2 = "tinycloud.kv";
@@ -21565,6 +21600,7 @@ function replicaRestoreRefusal(error) {
21565
21600
  case "purge_pending":
21566
21601
  case "logout_pending":
21567
21602
  case "gate_unavailable":
21603
+ case "wallet_provider_required":
21568
21604
  return error.gate;
21569
21605
  }
21570
21606
  }
@@ -21599,6 +21635,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21599
21635
  this.auth = null;
21600
21636
  this.tc = null;
21601
21637
  this._chainId = 1;
21638
+ this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
21602
21639
  /** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
21603
21640
  this.establishReplicaStamp = async (session) => {
21604
21641
  const replication = this.config.replication;
@@ -21805,6 +21842,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21805
21842
  return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
21806
21843
  },
21807
21844
  clearPending: async () => (await runtime).control.clearPending(),
21845
+ purgeLocalCopy: async () => (await this.eraseReplicaLocalCopy("purge_local_copy"))?.purge ?? { ok: false, retryNow: false },
21808
21846
  close: async () => (await runtime).control.close()
21809
21847
  };
21810
21848
  }
@@ -21859,18 +21897,14 @@ var _TinyCloudNode = class _TinyCloudNode {
21859
21897
  get freshEncryptionNetworkCreated() {
21860
21898
  return this._freshEncryptionNetworkCreated;
21861
21899
  }
21862
- /**
21863
- * Subscribe to `storage.full`. It fires on the first write any service had
21864
- * rejected because the owner's TinyCloud storage is full
21865
- * (`STORAGE_QUOTA_EXCEEDED` or `STORAGE_LIMIT_REACHED`), so an app can show
21866
- * one read-only banner. Reads keep working. For the signed-in account's own
21867
- * storage it fires once per episode: the episode ends only when a write to
21868
- * that storage succeeds, or on sign-out. A `storage.status()` read does not
21869
- * end it. A rejection on another owner's delegated space fires once per
21870
- * owner, until that owner's `storage.status()` reports space again. Returns
21871
- * the unsubscribe function.
21872
- */
21873
21900
  on(event, handler) {
21901
+ if (event === "replica.purge.failed") {
21902
+ const listener = handler;
21903
+ this.replicaPurgeFailedHandlers.add(listener);
21904
+ return () => {
21905
+ this.replicaPurgeFailedHandlers.delete(listener);
21906
+ };
21907
+ }
21874
21908
  if (event !== "storage.full") {
21875
21909
  throw new Error(`Unknown TinyCloud event: ${String(event)}`);
21876
21910
  }
@@ -22351,6 +22385,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22351
22385
  const leavingAuth = this.auth;
22352
22386
  const leavingContext = this._serviceContext;
22353
22387
  let denied;
22388
+ let erased;
22354
22389
  try {
22355
22390
  this.teardownSession();
22356
22391
  } finally {
@@ -22361,6 +22396,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22361
22396
  try {
22362
22397
  await this.accountRegistryTail;
22363
22398
  await this.quiesceReplicaRuntime();
22399
+ if (denied) erased = await this.eraseReplicaLocalCopy("sign_out", denied.status);
22364
22400
  } finally {
22365
22401
  if (leavingAddress) {
22366
22402
  await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
@@ -22368,8 +22404,37 @@ var _TinyCloudNode = class _TinyCloudNode {
22368
22404
  }
22369
22405
  }
22370
22406
  }
22371
- if (denied?.status === "unguarded") {
22372
- throw new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`);
22407
+ if (denied?.status === "unguarded" && erased?.outcome.status !== "erased") {
22408
+ throw Object.assign(
22409
+ new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`),
22410
+ {
22411
+ code: "REPLICA_PURGE_FAILED",
22412
+ reason: "gate_unavailable",
22413
+ retryNow: erased?.outcome.status === "failed"
22414
+ }
22415
+ );
22416
+ }
22417
+ }
22418
+ /**
22419
+ * Erases the browser local copy after a deny (retry, event and next-load marker live in the lazy
22420
+ * replication authority). Never rejects; `undefined` when replication is off or the storage keeps
22421
+ * no browser database.
22422
+ */
22423
+ async eraseReplicaLocalCopy(trigger, denyStatus) {
22424
+ const replication = this.config.replication;
22425
+ if (!replication?.enabled) return void 0;
22426
+ try {
22427
+ const { eraseReplicaLocalCopy: eraseReplicaLocalCopy2 } = await getNodeReplicationLoaders().authority();
22428
+ const erased = await eraseReplicaLocalCopy2(
22429
+ replication.storage.lifecycle,
22430
+ trigger,
22431
+ denyStatus,
22432
+ this.replicaPurgeFailedHandlers
22433
+ );
22434
+ if (erased?.logoutPending) this.replicaRestoreGate = "logout_pending";
22435
+ return erased;
22436
+ } catch {
22437
+ return void 0;
22373
22438
  }
22374
22439
  }
22375
22440
  /**
@@ -22533,6 +22598,10 @@ var _TinyCloudNode = class _TinyCloudNode {
22533
22598
  const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? (0, import_sdk_core10.pkhDid)(this.address, this._chainId) : void 0);
22534
22599
  if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
22535
22600
  const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
22601
+ if (denial) {
22602
+ await this.quiesceReplicaRuntime();
22603
+ await this.eraseReplicaLocalCopy("account_switch", denial.status);
22604
+ }
22536
22605
  this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
22537
22606
  }
22538
22607
  if (address !== this._address || chainId !== this._chainId) {