@unicitylabs/sphere-sdk 0.4.3 → 0.4.4

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.
@@ -354,6 +354,7 @@ function createLocalStorageProvider(config) {
354
354
  var DB_NAME = "sphere-storage";
355
355
  var DB_VERSION = 1;
356
356
  var STORE_NAME = "kv";
357
+ var connectionSeq = 0;
357
358
  var IndexedDBStorageProvider = class {
358
359
  id = "indexeddb-storage";
359
360
  name = "IndexedDB Storage";
@@ -365,6 +366,8 @@ var IndexedDBStorageProvider = class {
365
366
  identity = null;
366
367
  status = "disconnected";
367
368
  db = null;
369
+ /** Monotonic connection ID for tracing open/close pairs */
370
+ connId = 0;
368
371
  constructor(config) {
369
372
  this.prefix = config?.prefix ?? "sphere_";
370
373
  this.dbName = config?.dbName ?? DB_NAME;
@@ -377,7 +380,8 @@ var IndexedDBStorageProvider = class {
377
380
  if (this.status === "connected" && this.db) return;
378
381
  for (let attempt = 0; attempt < 2; attempt++) {
379
382
  this.status = "connecting";
380
- console.log(`[IndexedDBStorage] connect: opening db=${this.dbName}${attempt > 0 ? " (retry)" : ""}`);
383
+ const t0 = Date.now();
384
+ console.log(`[IndexedDBStorage] connect: opening db=${this.dbName}, attempt=${attempt + 1}/2`);
381
385
  try {
382
386
  this.db = await Promise.race([
383
387
  this.openDatabase(),
@@ -386,11 +390,11 @@ var IndexedDBStorageProvider = class {
386
390
  )
387
391
  ]);
388
392
  this.status = "connected";
389
- console.log(`[IndexedDBStorage] connect: connected to db=${this.dbName}`);
393
+ console.log(`[IndexedDBStorage] connect: connected db=${this.dbName} connId=${this.connId} (${Date.now() - t0}ms)`);
390
394
  return;
391
395
  } catch (error) {
396
+ console.warn(`[IndexedDBStorage] connect: open failed db=${this.dbName} attempt=${attempt + 1} (${Date.now() - t0}ms):`, error);
392
397
  if (attempt === 0) {
393
- console.warn(`[IndexedDBStorage] connect: open failed, retrying in 1s...`);
394
398
  this.status = "disconnected";
395
399
  await new Promise((r) => setTimeout(r, 1e3));
396
400
  continue;
@@ -401,7 +405,8 @@ var IndexedDBStorageProvider = class {
401
405
  }
402
406
  }
403
407
  async disconnect() {
404
- console.log(`[IndexedDBStorage] disconnect: closing db=${this.dbName}, wasConnected=${!!this.db}`);
408
+ const cid = this.connId;
409
+ console.log(`[IndexedDBStorage] disconnect: db=${this.dbName} connId=${cid} wasConnected=${!!this.db}`);
405
410
  if (this.db) {
406
411
  this.db.close();
407
412
  this.db = null;
@@ -458,37 +463,36 @@ var IndexedDBStorageProvider = class {
458
463
  }
459
464
  async clear(prefix) {
460
465
  if (!prefix) {
461
- console.log(`[IndexedDBStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
462
- if (this.db) {
463
- this.db.close();
464
- this.db = null;
465
- }
466
- this.status = "disconnected";
467
- await new Promise((resolve) => {
468
- try {
469
- const req = indexedDB.deleteDatabase(this.dbName);
470
- const timer = setTimeout(() => {
471
- console.warn(`[IndexedDBStorage] clear: deleteDatabase timed out for db=${this.dbName}`);
472
- resolve();
473
- }, 5e3);
474
- req.onsuccess = () => {
475
- clearTimeout(timer);
476
- console.log(`[IndexedDBStorage] clear: deleted db=${this.dbName}`);
477
- resolve();
478
- };
479
- req.onerror = () => {
480
- clearTimeout(timer);
481
- console.warn(`[IndexedDBStorage] clear: error deleting db=${this.dbName}`, req.error);
482
- resolve();
483
- };
484
- req.onblocked = () => {
485
- console.warn(`[IndexedDBStorage] clear: deleteDatabase blocked for db=${this.dbName}, waiting...`);
486
- };
487
- } catch {
488
- resolve();
466
+ const t0 = Date.now();
467
+ const prevConnId = this.connId;
468
+ console.log(`[IndexedDBStorage] clear: starting db=${this.dbName} connId=${prevConnId} status=${this.status} hasDb=${!!this.db}`);
469
+ try {
470
+ if (!this.db || this.status !== "connected") {
471
+ if (this.db) {
472
+ console.log(`[IndexedDBStorage] clear: closing stale handle connId=${prevConnId}`);
473
+ this.db.close();
474
+ this.db = null;
475
+ }
476
+ console.log(`[IndexedDBStorage] clear: opening fresh connection for wipe`);
477
+ this.db = await Promise.race([
478
+ this.openDatabase(),
479
+ new Promise(
480
+ (_, reject) => setTimeout(() => reject(new Error("open timed out")), 3e3)
481
+ )
482
+ ]);
483
+ this.status = "connected";
489
484
  }
490
- });
491
- this.log("Database deleted:", this.dbName);
485
+ await this.idbClear();
486
+ console.log(`[IndexedDBStorage] clear: store cleared db=${this.dbName} connId=${this.connId} (${Date.now() - t0}ms)`);
487
+ } catch (err) {
488
+ console.warn(`[IndexedDBStorage] clear: failed db=${this.dbName} (${Date.now() - t0}ms)`, err);
489
+ } finally {
490
+ if (this.db) {
491
+ this.db.close();
492
+ this.db = null;
493
+ }
494
+ this.status = "disconnected";
495
+ }
492
496
  return;
493
497
  }
494
498
  this.ensureConnected();
@@ -554,9 +558,22 @@ var IndexedDBStorageProvider = class {
554
558
  return new Promise((resolve, reject) => {
555
559
  const request = indexedDB.open(this.dbName, DB_VERSION);
556
560
  request.onerror = () => reject(request.error);
557
- request.onsuccess = () => resolve(request.result);
561
+ request.onsuccess = () => {
562
+ const db = request.result;
563
+ const cid = ++connectionSeq;
564
+ this.connId = cid;
565
+ db.onversionchange = () => {
566
+ console.log(`[IndexedDBStorage] onversionchange: auto-closing db=${this.dbName} connId=${cid}`);
567
+ db.close();
568
+ if (this.db === db) {
569
+ this.db = null;
570
+ this.status = "disconnected";
571
+ }
572
+ };
573
+ resolve(db);
574
+ };
558
575
  request.onblocked = () => {
559
- console.warn("[IndexedDBStorageProvider] open blocked by another connection");
576
+ console.warn(`[IndexedDBStorage] open blocked by another connection, db=${this.dbName}`);
560
577
  };
561
578
  request.onupgradeneeded = (event) => {
562
579
  const db = event.target.result;
@@ -635,18 +652,23 @@ var DB_NAME2 = "sphere-token-storage";
635
652
  var DB_VERSION2 = 1;
636
653
  var STORE_TOKENS = "tokens";
637
654
  var STORE_META = "meta";
655
+ var connectionSeq2 = 0;
638
656
  var IndexedDBTokenStorageProvider = class {
639
657
  id = "indexeddb-token-storage";
640
658
  name = "IndexedDB Token Storage";
641
659
  type = "local";
642
660
  dbNamePrefix;
643
661
  dbName;
662
+ debug;
644
663
  db = null;
645
664
  status = "disconnected";
646
665
  identity = null;
666
+ /** Monotonic connection ID for tracing open/close pairs */
667
+ connId = 0;
647
668
  constructor(config) {
648
669
  this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME2;
649
670
  this.dbName = this.dbNamePrefix;
671
+ this.debug = config?.debug ?? false;
650
672
  }
651
673
  setIdentity(identity) {
652
674
  this.identity = identity;
@@ -654,28 +676,31 @@ var IndexedDBTokenStorageProvider = class {
654
676
  const addressId = getAddressId(identity.directAddress);
655
677
  this.dbName = `${this.dbNamePrefix}-${addressId}`;
656
678
  }
657
- console.log(`[IndexedDBTokenStorage] setIdentity \u2192 db=${this.dbName}`);
679
+ console.log(`[IndexedDBTokenStorage] setIdentity: db=${this.dbName}`);
658
680
  }
659
681
  async initialize() {
682
+ const prevConnId = this.connId;
683
+ const t0 = Date.now();
660
684
  try {
661
685
  if (this.db) {
662
- console.log(`[IndexedDBTokenStorage] initialize: closing existing connection before re-open (db=${this.dbName})`);
686
+ console.log(`[IndexedDBTokenStorage] initialize: closing existing connId=${prevConnId} before re-open (db=${this.dbName})`);
663
687
  this.db.close();
664
688
  this.db = null;
665
689
  }
666
690
  console.log(`[IndexedDBTokenStorage] initialize: opening db=${this.dbName}`);
667
691
  this.db = await this.openDatabase();
668
692
  this.status = "connected";
669
- console.log(`[IndexedDBTokenStorage] initialize: connected to db=${this.dbName}`);
693
+ console.log(`[IndexedDBTokenStorage] initialize: connected db=${this.dbName} connId=${this.connId} (${Date.now() - t0}ms)`);
670
694
  return true;
671
695
  } catch (error) {
672
- console.error("[IndexedDBTokenStorage] Failed to initialize:", error);
696
+ console.error(`[IndexedDBTokenStorage] initialize: failed db=${this.dbName} (${Date.now() - t0}ms):`, error);
673
697
  this.status = "error";
674
698
  return false;
675
699
  }
676
700
  }
677
701
  async shutdown() {
678
- console.log(`[IndexedDBTokenStorage] shutdown: closing db=${this.dbName}, wasConnected=${!!this.db}`);
702
+ const cid = this.connId;
703
+ console.log(`[IndexedDBTokenStorage] shutdown: db=${this.dbName} connId=${cid} wasConnected=${!!this.db}`);
679
704
  if (this.db) {
680
705
  this.db.close();
681
706
  this.db = null;
@@ -833,56 +858,32 @@ var IndexedDBTokenStorageProvider = class {
833
858
  return meta !== null;
834
859
  }
835
860
  async clear() {
861
+ const t0 = Date.now();
836
862
  try {
837
- console.log(`[IndexedDBTokenStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
838
863
  if (this.db) {
839
864
  this.db.close();
840
865
  this.db = null;
841
866
  }
842
867
  this.status = "disconnected";
843
- const dbNames = [this.dbName];
844
- if (typeof indexedDB.databases === "function") {
845
- try {
846
- const dbs = await Promise.race([
847
- indexedDB.databases(),
848
- new Promise(
849
- (_, reject) => setTimeout(() => reject(new Error("timeout")), 1500)
850
- )
851
- ]);
852
- for (const dbInfo of dbs) {
853
- if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
854
- dbNames.push(dbInfo.name);
855
- }
856
- }
857
- } catch {
858
- }
868
+ const dbNames = /* @__PURE__ */ new Set([this.dbName]);
869
+ for (const name of await this.findPrefixedDatabases()) {
870
+ dbNames.add(name);
859
871
  }
860
- console.log(`[IndexedDBTokenStorage] clear: deleting ${dbNames.length} database(s):`, dbNames);
861
- await Promise.all(dbNames.map(
862
- (name) => new Promise((resolve) => {
863
- try {
864
- const req = indexedDB.deleteDatabase(name);
865
- req.onsuccess = () => {
866
- console.log(`[IndexedDBTokenStorage] clear: deleted db=${name}`);
867
- resolve();
868
- };
869
- req.onerror = () => {
870
- console.warn(`[IndexedDBTokenStorage] clear: error deleting db=${name}`, req.error);
871
- resolve();
872
- };
873
- req.onblocked = () => {
874
- console.warn(`[IndexedDBTokenStorage] clear: deleteDatabase blocked for db=${name}`);
875
- resolve();
876
- };
877
- } catch {
878
- resolve();
879
- }
880
- })
881
- ));
882
- console.log(`[IndexedDBTokenStorage] clear: done`);
883
- return true;
872
+ console.log(`[IndexedDBTokenStorage] clear: clearing ${dbNames.size} database(s) (${[...dbNames].join(", ")})`);
873
+ const results = await Promise.allSettled(
874
+ [...dbNames].map((name) => this.clearDatabaseStores(name))
875
+ );
876
+ const failed = results.filter((r) => r.status === "rejected");
877
+ if (failed.length > 0) {
878
+ console.warn(
879
+ `[IndexedDBTokenStorage] clear: ${failed.length}/${dbNames.size} failed (${Date.now() - t0}ms)`,
880
+ failed.map((r) => r.reason)
881
+ );
882
+ }
883
+ console.log(`[IndexedDBTokenStorage] clear: done ${dbNames.size} database(s) (${Date.now() - t0}ms)`);
884
+ return failed.length === 0;
884
885
  } catch (err) {
885
- console.warn("[IndexedDBTokenStorage] clear() failed:", err);
886
+ console.warn(`[IndexedDBTokenStorage] clear: failed (${Date.now() - t0}ms)`, err);
886
887
  return false;
887
888
  }
888
889
  }
@@ -892,11 +893,23 @@ var IndexedDBTokenStorageProvider = class {
892
893
  openDatabase() {
893
894
  return new Promise((resolve, reject) => {
894
895
  const request = indexedDB.open(this.dbName, DB_VERSION2);
895
- request.onerror = () => {
896
- reject(request.error);
897
- };
896
+ request.onerror = () => reject(request.error);
898
897
  request.onsuccess = () => {
899
- resolve(request.result);
898
+ const db = request.result;
899
+ const cid = ++connectionSeq2;
900
+ this.connId = cid;
901
+ db.onversionchange = () => {
902
+ console.log(`[IndexedDBTokenStorage] onversionchange: auto-closing db=${this.dbName} connId=${cid}`);
903
+ db.close();
904
+ if (this.db === db) {
905
+ this.db = null;
906
+ this.status = "disconnected";
907
+ }
908
+ };
909
+ resolve(db);
910
+ };
911
+ request.onblocked = () => {
912
+ console.warn(`[IndexedDBTokenStorage] open blocked by another connection, db=${this.dbName}`);
900
913
  };
901
914
  request.onupgradeneeded = (event) => {
902
915
  const db = event.target.result;
@@ -961,18 +974,70 @@ var IndexedDBTokenStorageProvider = class {
961
974
  request.onsuccess = () => resolve();
962
975
  });
963
976
  }
964
- clearStore(storeName) {
965
- return new Promise((resolve, reject) => {
966
- if (!this.db) {
967
- resolve();
968
- return;
977
+ /**
978
+ * Find all IndexedDB databases with our prefix.
979
+ * Returns empty array if indexedDB.databases() is unavailable (older browsers).
980
+ */
981
+ async findPrefixedDatabases() {
982
+ if (typeof indexedDB.databases !== "function") return [];
983
+ try {
984
+ const allDbs = await Promise.race([
985
+ indexedDB.databases(),
986
+ new Promise(
987
+ (_, reject) => setTimeout(() => reject(new Error("databases() timed out")), 1500)
988
+ )
989
+ ]);
990
+ return allDbs.map((info) => info.name).filter((name) => !!name && name.startsWith(this.dbNamePrefix));
991
+ } catch {
992
+ return [];
993
+ }
994
+ }
995
+ /**
996
+ * Clear all object stores in a single database.
997
+ * Opens a temporary connection, clears STORE_TOKENS and STORE_META, then closes.
998
+ * Uses IDBObjectStore.clear() which is a normal readwrite transaction — cannot
999
+ * be blocked by other connections (unlike deleteDatabase()).
1000
+ */
1001
+ async clearDatabaseStores(dbName) {
1002
+ const db = await Promise.race([
1003
+ new Promise((resolve, reject) => {
1004
+ const req = indexedDB.open(dbName, DB_VERSION2);
1005
+ req.onerror = () => reject(req.error);
1006
+ req.onsuccess = () => {
1007
+ const db2 = req.result;
1008
+ db2.onversionchange = () => {
1009
+ db2.close();
1010
+ };
1011
+ resolve(db2);
1012
+ };
1013
+ req.onupgradeneeded = (event) => {
1014
+ const db2 = event.target.result;
1015
+ if (!db2.objectStoreNames.contains(STORE_TOKENS)) {
1016
+ db2.createObjectStore(STORE_TOKENS, { keyPath: "id" });
1017
+ }
1018
+ if (!db2.objectStoreNames.contains(STORE_META)) {
1019
+ db2.createObjectStore(STORE_META);
1020
+ }
1021
+ };
1022
+ }),
1023
+ new Promise(
1024
+ (_, reject) => setTimeout(() => reject(new Error(`open timed out: ${dbName}`)), 3e3)
1025
+ )
1026
+ ]);
1027
+ try {
1028
+ for (const storeName of [STORE_TOKENS, STORE_META]) {
1029
+ if (db.objectStoreNames.contains(storeName)) {
1030
+ await new Promise((resolve, reject) => {
1031
+ const tx = db.transaction(storeName, "readwrite");
1032
+ const req = tx.objectStore(storeName).clear();
1033
+ req.onerror = () => reject(req.error);
1034
+ req.onsuccess = () => resolve();
1035
+ });
1036
+ }
969
1037
  }
970
- const transaction = this.db.transaction(storeName, "readwrite");
971
- const store = transaction.objectStore(storeName);
972
- const request = store.clear();
973
- request.onerror = () => reject(request.error);
974
- request.onsuccess = () => resolve();
975
- });
1038
+ } finally {
1039
+ db.close();
1040
+ }
976
1041
  }
977
1042
  };
978
1043
  function createIndexedDBTokenStorageProvider(config) {