@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.
@@ -414,6 +414,7 @@ function createLocalStorageProvider(config) {
414
414
  var DB_NAME = "sphere-storage";
415
415
  var DB_VERSION = 1;
416
416
  var STORE_NAME = "kv";
417
+ var connectionSeq = 0;
417
418
  var IndexedDBStorageProvider = class {
418
419
  id = "indexeddb-storage";
419
420
  name = "IndexedDB Storage";
@@ -425,6 +426,8 @@ var IndexedDBStorageProvider = class {
425
426
  identity = null;
426
427
  status = "disconnected";
427
428
  db = null;
429
+ /** Monotonic connection ID for tracing open/close pairs */
430
+ connId = 0;
428
431
  constructor(config) {
429
432
  this.prefix = config?.prefix ?? "sphere_";
430
433
  this.dbName = config?.dbName ?? DB_NAME;
@@ -437,7 +440,8 @@ var IndexedDBStorageProvider = class {
437
440
  if (this.status === "connected" && this.db) return;
438
441
  for (let attempt = 0; attempt < 2; attempt++) {
439
442
  this.status = "connecting";
440
- console.log(`[IndexedDBStorage] connect: opening db=${this.dbName}${attempt > 0 ? " (retry)" : ""}`);
443
+ const t0 = Date.now();
444
+ console.log(`[IndexedDBStorage] connect: opening db=${this.dbName}, attempt=${attempt + 1}/2`);
441
445
  try {
442
446
  this.db = await Promise.race([
443
447
  this.openDatabase(),
@@ -446,11 +450,11 @@ var IndexedDBStorageProvider = class {
446
450
  )
447
451
  ]);
448
452
  this.status = "connected";
449
- console.log(`[IndexedDBStorage] connect: connected to db=${this.dbName}`);
453
+ console.log(`[IndexedDBStorage] connect: connected db=${this.dbName} connId=${this.connId} (${Date.now() - t0}ms)`);
450
454
  return;
451
455
  } catch (error) {
456
+ console.warn(`[IndexedDBStorage] connect: open failed db=${this.dbName} attempt=${attempt + 1} (${Date.now() - t0}ms):`, error);
452
457
  if (attempt === 0) {
453
- console.warn(`[IndexedDBStorage] connect: open failed, retrying in 1s...`);
454
458
  this.status = "disconnected";
455
459
  await new Promise((r) => setTimeout(r, 1e3));
456
460
  continue;
@@ -461,7 +465,8 @@ var IndexedDBStorageProvider = class {
461
465
  }
462
466
  }
463
467
  async disconnect() {
464
- console.log(`[IndexedDBStorage] disconnect: closing db=${this.dbName}, wasConnected=${!!this.db}`);
468
+ const cid = this.connId;
469
+ console.log(`[IndexedDBStorage] disconnect: db=${this.dbName} connId=${cid} wasConnected=${!!this.db}`);
465
470
  if (this.db) {
466
471
  this.db.close();
467
472
  this.db = null;
@@ -518,37 +523,36 @@ var IndexedDBStorageProvider = class {
518
523
  }
519
524
  async clear(prefix) {
520
525
  if (!prefix) {
521
- console.log(`[IndexedDBStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
522
- if (this.db) {
523
- this.db.close();
524
- this.db = null;
525
- }
526
- this.status = "disconnected";
527
- await new Promise((resolve) => {
528
- try {
529
- const req = indexedDB.deleteDatabase(this.dbName);
530
- const timer = setTimeout(() => {
531
- console.warn(`[IndexedDBStorage] clear: deleteDatabase timed out for db=${this.dbName}`);
532
- resolve();
533
- }, 5e3);
534
- req.onsuccess = () => {
535
- clearTimeout(timer);
536
- console.log(`[IndexedDBStorage] clear: deleted db=${this.dbName}`);
537
- resolve();
538
- };
539
- req.onerror = () => {
540
- clearTimeout(timer);
541
- console.warn(`[IndexedDBStorage] clear: error deleting db=${this.dbName}`, req.error);
542
- resolve();
543
- };
544
- req.onblocked = () => {
545
- console.warn(`[IndexedDBStorage] clear: deleteDatabase blocked for db=${this.dbName}, waiting...`);
546
- };
547
- } catch {
548
- resolve();
526
+ const t0 = Date.now();
527
+ const prevConnId = this.connId;
528
+ console.log(`[IndexedDBStorage] clear: starting db=${this.dbName} connId=${prevConnId} status=${this.status} hasDb=${!!this.db}`);
529
+ try {
530
+ if (!this.db || this.status !== "connected") {
531
+ if (this.db) {
532
+ console.log(`[IndexedDBStorage] clear: closing stale handle connId=${prevConnId}`);
533
+ this.db.close();
534
+ this.db = null;
535
+ }
536
+ console.log(`[IndexedDBStorage] clear: opening fresh connection for wipe`);
537
+ this.db = await Promise.race([
538
+ this.openDatabase(),
539
+ new Promise(
540
+ (_, reject) => setTimeout(() => reject(new Error("open timed out")), 3e3)
541
+ )
542
+ ]);
543
+ this.status = "connected";
549
544
  }
550
- });
551
- this.log("Database deleted:", this.dbName);
545
+ await this.idbClear();
546
+ console.log(`[IndexedDBStorage] clear: store cleared db=${this.dbName} connId=${this.connId} (${Date.now() - t0}ms)`);
547
+ } catch (err) {
548
+ console.warn(`[IndexedDBStorage] clear: failed db=${this.dbName} (${Date.now() - t0}ms)`, err);
549
+ } finally {
550
+ if (this.db) {
551
+ this.db.close();
552
+ this.db = null;
553
+ }
554
+ this.status = "disconnected";
555
+ }
552
556
  return;
553
557
  }
554
558
  this.ensureConnected();
@@ -614,9 +618,22 @@ var IndexedDBStorageProvider = class {
614
618
  return new Promise((resolve, reject) => {
615
619
  const request = indexedDB.open(this.dbName, DB_VERSION);
616
620
  request.onerror = () => reject(request.error);
617
- request.onsuccess = () => resolve(request.result);
621
+ request.onsuccess = () => {
622
+ const db = request.result;
623
+ const cid = ++connectionSeq;
624
+ this.connId = cid;
625
+ db.onversionchange = () => {
626
+ console.log(`[IndexedDBStorage] onversionchange: auto-closing db=${this.dbName} connId=${cid}`);
627
+ db.close();
628
+ if (this.db === db) {
629
+ this.db = null;
630
+ this.status = "disconnected";
631
+ }
632
+ };
633
+ resolve(db);
634
+ };
618
635
  request.onblocked = () => {
619
- console.warn("[IndexedDBStorageProvider] open blocked by another connection");
636
+ console.warn(`[IndexedDBStorage] open blocked by another connection, db=${this.dbName}`);
620
637
  };
621
638
  request.onupgradeneeded = (event) => {
622
639
  const db = event.target.result;
@@ -695,18 +712,23 @@ var DB_NAME2 = "sphere-token-storage";
695
712
  var DB_VERSION2 = 1;
696
713
  var STORE_TOKENS = "tokens";
697
714
  var STORE_META = "meta";
715
+ var connectionSeq2 = 0;
698
716
  var IndexedDBTokenStorageProvider = class {
699
717
  id = "indexeddb-token-storage";
700
718
  name = "IndexedDB Token Storage";
701
719
  type = "local";
702
720
  dbNamePrefix;
703
721
  dbName;
722
+ debug;
704
723
  db = null;
705
724
  status = "disconnected";
706
725
  identity = null;
726
+ /** Monotonic connection ID for tracing open/close pairs */
727
+ connId = 0;
707
728
  constructor(config) {
708
729
  this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME2;
709
730
  this.dbName = this.dbNamePrefix;
731
+ this.debug = config?.debug ?? false;
710
732
  }
711
733
  setIdentity(identity) {
712
734
  this.identity = identity;
@@ -714,28 +736,31 @@ var IndexedDBTokenStorageProvider = class {
714
736
  const addressId = getAddressId(identity.directAddress);
715
737
  this.dbName = `${this.dbNamePrefix}-${addressId}`;
716
738
  }
717
- console.log(`[IndexedDBTokenStorage] setIdentity \u2192 db=${this.dbName}`);
739
+ console.log(`[IndexedDBTokenStorage] setIdentity: db=${this.dbName}`);
718
740
  }
719
741
  async initialize() {
742
+ const prevConnId = this.connId;
743
+ const t0 = Date.now();
720
744
  try {
721
745
  if (this.db) {
722
- console.log(`[IndexedDBTokenStorage] initialize: closing existing connection before re-open (db=${this.dbName})`);
746
+ console.log(`[IndexedDBTokenStorage] initialize: closing existing connId=${prevConnId} before re-open (db=${this.dbName})`);
723
747
  this.db.close();
724
748
  this.db = null;
725
749
  }
726
750
  console.log(`[IndexedDBTokenStorage] initialize: opening db=${this.dbName}`);
727
751
  this.db = await this.openDatabase();
728
752
  this.status = "connected";
729
- console.log(`[IndexedDBTokenStorage] initialize: connected to db=${this.dbName}`);
753
+ console.log(`[IndexedDBTokenStorage] initialize: connected db=${this.dbName} connId=${this.connId} (${Date.now() - t0}ms)`);
730
754
  return true;
731
755
  } catch (error) {
732
- console.error("[IndexedDBTokenStorage] Failed to initialize:", error);
756
+ console.error(`[IndexedDBTokenStorage] initialize: failed db=${this.dbName} (${Date.now() - t0}ms):`, error);
733
757
  this.status = "error";
734
758
  return false;
735
759
  }
736
760
  }
737
761
  async shutdown() {
738
- console.log(`[IndexedDBTokenStorage] shutdown: closing db=${this.dbName}, wasConnected=${!!this.db}`);
762
+ const cid = this.connId;
763
+ console.log(`[IndexedDBTokenStorage] shutdown: db=${this.dbName} connId=${cid} wasConnected=${!!this.db}`);
739
764
  if (this.db) {
740
765
  this.db.close();
741
766
  this.db = null;
@@ -893,56 +918,32 @@ var IndexedDBTokenStorageProvider = class {
893
918
  return meta !== null;
894
919
  }
895
920
  async clear() {
921
+ const t0 = Date.now();
896
922
  try {
897
- console.log(`[IndexedDBTokenStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
898
923
  if (this.db) {
899
924
  this.db.close();
900
925
  this.db = null;
901
926
  }
902
927
  this.status = "disconnected";
903
- const dbNames = [this.dbName];
904
- if (typeof indexedDB.databases === "function") {
905
- try {
906
- const dbs = await Promise.race([
907
- indexedDB.databases(),
908
- new Promise(
909
- (_, reject) => setTimeout(() => reject(new Error("timeout")), 1500)
910
- )
911
- ]);
912
- for (const dbInfo of dbs) {
913
- if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
914
- dbNames.push(dbInfo.name);
915
- }
916
- }
917
- } catch {
918
- }
928
+ const dbNames = /* @__PURE__ */ new Set([this.dbName]);
929
+ for (const name of await this.findPrefixedDatabases()) {
930
+ dbNames.add(name);
919
931
  }
920
- console.log(`[IndexedDBTokenStorage] clear: deleting ${dbNames.length} database(s):`, dbNames);
921
- await Promise.all(dbNames.map(
922
- (name) => new Promise((resolve) => {
923
- try {
924
- const req = indexedDB.deleteDatabase(name);
925
- req.onsuccess = () => {
926
- console.log(`[IndexedDBTokenStorage] clear: deleted db=${name}`);
927
- resolve();
928
- };
929
- req.onerror = () => {
930
- console.warn(`[IndexedDBTokenStorage] clear: error deleting db=${name}`, req.error);
931
- resolve();
932
- };
933
- req.onblocked = () => {
934
- console.warn(`[IndexedDBTokenStorage] clear: deleteDatabase blocked for db=${name}`);
935
- resolve();
936
- };
937
- } catch {
938
- resolve();
939
- }
940
- })
941
- ));
942
- console.log(`[IndexedDBTokenStorage] clear: done`);
943
- return true;
932
+ console.log(`[IndexedDBTokenStorage] clear: clearing ${dbNames.size} database(s) (${[...dbNames].join(", ")})`);
933
+ const results = await Promise.allSettled(
934
+ [...dbNames].map((name) => this.clearDatabaseStores(name))
935
+ );
936
+ const failed = results.filter((r) => r.status === "rejected");
937
+ if (failed.length > 0) {
938
+ console.warn(
939
+ `[IndexedDBTokenStorage] clear: ${failed.length}/${dbNames.size} failed (${Date.now() - t0}ms)`,
940
+ failed.map((r) => r.reason)
941
+ );
942
+ }
943
+ console.log(`[IndexedDBTokenStorage] clear: done ${dbNames.size} database(s) (${Date.now() - t0}ms)`);
944
+ return failed.length === 0;
944
945
  } catch (err) {
945
- console.warn("[IndexedDBTokenStorage] clear() failed:", err);
946
+ console.warn(`[IndexedDBTokenStorage] clear: failed (${Date.now() - t0}ms)`, err);
946
947
  return false;
947
948
  }
948
949
  }
@@ -952,11 +953,23 @@ var IndexedDBTokenStorageProvider = class {
952
953
  openDatabase() {
953
954
  return new Promise((resolve, reject) => {
954
955
  const request = indexedDB.open(this.dbName, DB_VERSION2);
955
- request.onerror = () => {
956
- reject(request.error);
957
- };
956
+ request.onerror = () => reject(request.error);
958
957
  request.onsuccess = () => {
959
- resolve(request.result);
958
+ const db = request.result;
959
+ const cid = ++connectionSeq2;
960
+ this.connId = cid;
961
+ db.onversionchange = () => {
962
+ console.log(`[IndexedDBTokenStorage] onversionchange: auto-closing db=${this.dbName} connId=${cid}`);
963
+ db.close();
964
+ if (this.db === db) {
965
+ this.db = null;
966
+ this.status = "disconnected";
967
+ }
968
+ };
969
+ resolve(db);
970
+ };
971
+ request.onblocked = () => {
972
+ console.warn(`[IndexedDBTokenStorage] open blocked by another connection, db=${this.dbName}`);
960
973
  };
961
974
  request.onupgradeneeded = (event) => {
962
975
  const db = event.target.result;
@@ -1021,18 +1034,70 @@ var IndexedDBTokenStorageProvider = class {
1021
1034
  request.onsuccess = () => resolve();
1022
1035
  });
1023
1036
  }
1024
- clearStore(storeName) {
1025
- return new Promise((resolve, reject) => {
1026
- if (!this.db) {
1027
- resolve();
1028
- return;
1037
+ /**
1038
+ * Find all IndexedDB databases with our prefix.
1039
+ * Returns empty array if indexedDB.databases() is unavailable (older browsers).
1040
+ */
1041
+ async findPrefixedDatabases() {
1042
+ if (typeof indexedDB.databases !== "function") return [];
1043
+ try {
1044
+ const allDbs = await Promise.race([
1045
+ indexedDB.databases(),
1046
+ new Promise(
1047
+ (_, reject) => setTimeout(() => reject(new Error("databases() timed out")), 1500)
1048
+ )
1049
+ ]);
1050
+ return allDbs.map((info) => info.name).filter((name) => !!name && name.startsWith(this.dbNamePrefix));
1051
+ } catch {
1052
+ return [];
1053
+ }
1054
+ }
1055
+ /**
1056
+ * Clear all object stores in a single database.
1057
+ * Opens a temporary connection, clears STORE_TOKENS and STORE_META, then closes.
1058
+ * Uses IDBObjectStore.clear() which is a normal readwrite transaction — cannot
1059
+ * be blocked by other connections (unlike deleteDatabase()).
1060
+ */
1061
+ async clearDatabaseStores(dbName) {
1062
+ const db = await Promise.race([
1063
+ new Promise((resolve, reject) => {
1064
+ const req = indexedDB.open(dbName, DB_VERSION2);
1065
+ req.onerror = () => reject(req.error);
1066
+ req.onsuccess = () => {
1067
+ const db2 = req.result;
1068
+ db2.onversionchange = () => {
1069
+ db2.close();
1070
+ };
1071
+ resolve(db2);
1072
+ };
1073
+ req.onupgradeneeded = (event) => {
1074
+ const db2 = event.target.result;
1075
+ if (!db2.objectStoreNames.contains(STORE_TOKENS)) {
1076
+ db2.createObjectStore(STORE_TOKENS, { keyPath: "id" });
1077
+ }
1078
+ if (!db2.objectStoreNames.contains(STORE_META)) {
1079
+ db2.createObjectStore(STORE_META);
1080
+ }
1081
+ };
1082
+ }),
1083
+ new Promise(
1084
+ (_, reject) => setTimeout(() => reject(new Error(`open timed out: ${dbName}`)), 3e3)
1085
+ )
1086
+ ]);
1087
+ try {
1088
+ for (const storeName of [STORE_TOKENS, STORE_META]) {
1089
+ if (db.objectStoreNames.contains(storeName)) {
1090
+ await new Promise((resolve, reject) => {
1091
+ const tx = db.transaction(storeName, "readwrite");
1092
+ const req = tx.objectStore(storeName).clear();
1093
+ req.onerror = () => reject(req.error);
1094
+ req.onsuccess = () => resolve();
1095
+ });
1096
+ }
1029
1097
  }
1030
- const transaction = this.db.transaction(storeName, "readwrite");
1031
- const store = transaction.objectStore(storeName);
1032
- const request = store.clear();
1033
- request.onerror = () => reject(request.error);
1034
- request.onsuccess = () => resolve();
1035
- });
1098
+ } finally {
1099
+ db.close();
1100
+ }
1036
1101
  }
1037
1102
  };
1038
1103
  function createIndexedDBTokenStorageProvider(config) {