@unicitylabs/sphere-sdk 0.6.2 → 0.6.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.
@@ -758,7 +758,7 @@ var STORE_TOKENS = "tokens";
758
758
  var STORE_META = "meta";
759
759
  var STORE_HISTORY = "history";
760
760
  var connectionSeq2 = 0;
761
- var IndexedDBTokenStorageProvider = class {
761
+ var IndexedDBTokenStorageProvider = class _IndexedDBTokenStorageProvider {
762
762
  id = "indexeddb-token-storage";
763
763
  name = "IndexedDB Token Storage";
764
764
  type = "local";
@@ -1206,6 +1206,16 @@ var IndexedDBTokenStorageProvider = class {
1206
1206
  db.close();
1207
1207
  }
1208
1208
  }
1209
+ /**
1210
+ * Create an independent instance for a different address.
1211
+ * The new instance shares the same config but has its own IDB connection.
1212
+ */
1213
+ createForAddress() {
1214
+ return new _IndexedDBTokenStorageProvider({
1215
+ dbNamePrefix: this.dbNamePrefix,
1216
+ debug: this.debug
1217
+ });
1218
+ }
1209
1219
  };
1210
1220
  function createIndexedDBTokenStorageProvider(config) {
1211
1221
  return new IndexedDBTokenStorageProvider(config);
@@ -1634,6 +1644,8 @@ var NostrTransportProvider = class {
1634
1644
  storage = null;
1635
1645
  /** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
1636
1646
  lastEventTs = 0;
1647
+ /** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
1648
+ fallbackSince = null;
1637
1649
  identity = null;
1638
1650
  keyManager = null;
1639
1651
  status = "disconnected";
@@ -1666,6 +1678,48 @@ var NostrTransportProvider = class {
1666
1678
  };
1667
1679
  this.storage = config.storage ?? null;
1668
1680
  }
1681
+ /**
1682
+ * Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
1683
+ */
1684
+ getWebSocketFactory() {
1685
+ return this.config.createWebSocket;
1686
+ }
1687
+ /**
1688
+ * Get the configured relay URLs.
1689
+ */
1690
+ getConfiguredRelays() {
1691
+ return [...this.config.relays];
1692
+ }
1693
+ /**
1694
+ * Get the storage adapter.
1695
+ */
1696
+ getStorageAdapter() {
1697
+ return this.storage;
1698
+ }
1699
+ /**
1700
+ * Suppress event subscriptions — unsubscribe wallet/chat filters
1701
+ * but keep the connection alive for resolve/identity-binding operations.
1702
+ * Used when MultiAddressTransportMux takes over event handling.
1703
+ */
1704
+ suppressSubscriptions() {
1705
+ if (!this.nostrClient) return;
1706
+ if (this.walletSubscriptionId) {
1707
+ this.nostrClient.unsubscribe(this.walletSubscriptionId);
1708
+ this.walletSubscriptionId = null;
1709
+ }
1710
+ if (this.chatSubscriptionId) {
1711
+ this.nostrClient.unsubscribe(this.chatSubscriptionId);
1712
+ this.chatSubscriptionId = null;
1713
+ }
1714
+ if (this.mainSubscriptionId) {
1715
+ this.nostrClient.unsubscribe(this.mainSubscriptionId);
1716
+ this.mainSubscriptionId = null;
1717
+ }
1718
+ this._subscriptionsSuppressed = true;
1719
+ logger.debug("Nostr", "Subscriptions suppressed \u2014 mux handles event routing");
1720
+ }
1721
+ // Flag to prevent re-subscription after suppressSubscriptions()
1722
+ _subscriptionsSuppressed = false;
1669
1723
  // ===========================================================================
1670
1724
  // BaseProvider Implementation
1671
1725
  // ===========================================================================
@@ -1844,6 +1898,8 @@ var NostrTransportProvider = class {
1844
1898
  // ===========================================================================
1845
1899
  async setIdentity(identity) {
1846
1900
  this.identity = identity;
1901
+ this.processedEventIds.clear();
1902
+ this.lastEventTs = 0;
1847
1903
  const secretKey = Buffer2.from(identity.privateKey, "hex");
1848
1904
  this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
1849
1905
  const nostrPubkey = this.keyManager.getPublicKeyHex();
@@ -1886,6 +1942,9 @@ var NostrTransportProvider = class {
1886
1942
  await this.subscribeToEvents();
1887
1943
  }
1888
1944
  }
1945
+ setFallbackSince(sinceSeconds) {
1946
+ this.fallbackSince = sinceSeconds;
1947
+ }
1889
1948
  /**
1890
1949
  * Get the Nostr-format public key (32 bytes / 64 hex chars)
1891
1950
  * This is the x-coordinate only, without the 02/03 prefix.
@@ -2793,6 +2852,10 @@ var NostrTransportProvider = class {
2793
2852
  chatEoseFired = false;
2794
2853
  async subscribeToEvents() {
2795
2854
  logger.debug("Nostr", "subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
2855
+ if (this._subscriptionsSuppressed) {
2856
+ logger.debug("Nostr", "subscribeToEvents: suppressed \u2014 mux handles event routing");
2857
+ return;
2858
+ }
2796
2859
  if (!this.identity || !this.keyManager || !this.nostrClient) {
2797
2860
  logger.debug("Nostr", "subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
2798
2861
  return;
@@ -2819,7 +2882,13 @@ var NostrTransportProvider = class {
2819
2882
  if (stored) {
2820
2883
  since = parseInt(stored, 10);
2821
2884
  this.lastEventTs = since;
2885
+ this.fallbackSince = null;
2822
2886
  logger.debug("Nostr", "Resuming from stored event timestamp:", since);
2887
+ } else if (this.fallbackSince !== null) {
2888
+ since = this.fallbackSince;
2889
+ this.lastEventTs = since;
2890
+ this.fallbackSince = null;
2891
+ logger.debug("Nostr", "Using fallback since timestamp:", since);
2823
2892
  } else {
2824
2893
  since = Math.floor(Date.now() / 1e3);
2825
2894
  logger.debug("Nostr", "No stored timestamp, starting from now:", since);
@@ -2827,6 +2896,7 @@ var NostrTransportProvider = class {
2827
2896
  } catch (err) {
2828
2897
  logger.debug("Nostr", "Failed to read last event timestamp, falling back to now:", err);
2829
2898
  since = Math.floor(Date.now() / 1e3);
2899
+ this.fallbackSince = null;
2830
2900
  }
2831
2901
  } else {
2832
2902
  since = Math.floor(Date.now() / 1e3) - 86400;
@@ -4847,11 +4917,17 @@ var AsyncSerialQueue = class {
4847
4917
  var WriteBuffer = class {
4848
4918
  /** Full TXF data from save() calls — latest wins */
4849
4919
  txfData = null;
4920
+ /** IPNS context captured at save() time — ensures flush writes to the correct
4921
+ * IPNS record even if identity changes between save() and flush(). */
4922
+ capturedIpnsKeyPair = null;
4923
+ capturedIpnsName = null;
4850
4924
  get isEmpty() {
4851
4925
  return this.txfData === null;
4852
4926
  }
4853
4927
  clear() {
4854
4928
  this.txfData = null;
4929
+ this.capturedIpnsKeyPair = null;
4930
+ this.capturedIpnsName = null;
4855
4931
  }
4856
4932
  /**
4857
4933
  * Merge another buffer's contents into this one (for rollback).
@@ -4860,12 +4936,14 @@ var WriteBuffer = class {
4860
4936
  mergeFrom(other) {
4861
4937
  if (other.txfData && !this.txfData) {
4862
4938
  this.txfData = other.txfData;
4939
+ this.capturedIpnsKeyPair = other.capturedIpnsKeyPair;
4940
+ this.capturedIpnsName = other.capturedIpnsName;
4863
4941
  }
4864
4942
  }
4865
4943
  };
4866
4944
 
4867
4945
  // impl/shared/ipfs/ipfs-storage-provider.ts
4868
- var IpfsStorageProvider = class {
4946
+ var IpfsStorageProvider = class _IpfsStorageProvider {
4869
4947
  id = "ipfs";
4870
4948
  name = "IPFS Storage";
4871
4949
  type = "p2p";
@@ -4910,7 +4988,12 @@ var IpfsStorageProvider = class {
4910
4988
  flushDebounceMs;
4911
4989
  /** Set to true during shutdown to prevent new flushes */
4912
4990
  isShuttingDown = false;
4991
+ /** Stored config for createForAddress() cloning */
4992
+ _config;
4993
+ _statePersistenceCtor;
4913
4994
  constructor(config, statePersistence) {
4995
+ this._config = config;
4996
+ this._statePersistenceCtor = statePersistence;
4914
4997
  const gateways = config?.gateways ?? getIpfsGatewayUrls();
4915
4998
  this.debug = config?.debug ?? false;
4916
4999
  this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? 99 * 365 * 24 * 60 * 60 * 1e3;
@@ -5030,6 +5113,7 @@ var IpfsStorageProvider = class {
5030
5113
  }
5031
5114
  async shutdown() {
5032
5115
  this.isShuttingDown = true;
5116
+ logger.debug("IPFS-Storage", `shutdown: ipnsName=${this.ipnsName?.slice(0, 20)}..., pendingEmpty=${this.pendingBuffer.isEmpty}, capturedIpns=${this.pendingBuffer.capturedIpnsName?.slice(0, 20) ?? "none"}`);
5033
5117
  if (this.flushTimer) {
5034
5118
  clearTimeout(this.flushTimer);
5035
5119
  this.flushTimer = null;
@@ -5062,6 +5146,8 @@ var IpfsStorageProvider = class {
5062
5146
  return { success: false, error: "Not initialized", timestamp: Date.now() };
5063
5147
  }
5064
5148
  this.pendingBuffer.txfData = data;
5149
+ this.pendingBuffer.capturedIpnsKeyPair = this.ipnsKeyPair;
5150
+ this.pendingBuffer.capturedIpnsName = this.ipnsName;
5065
5151
  this.scheduleFlush();
5066
5152
  return { success: true, timestamp: Date.now() };
5067
5153
  }
@@ -5072,8 +5158,12 @@ var IpfsStorageProvider = class {
5072
5158
  * Perform the actual upload + IPNS publish synchronously.
5073
5159
  * Called by executeFlush() and sync() — never by public save().
5074
5160
  */
5075
- async _doSave(data) {
5076
- if (!this.ipnsKeyPair || !this.ipnsName) {
5161
+ async _doSave(data, overrideIpns) {
5162
+ const ipnsKeyPair = overrideIpns?.keyPair ?? this.ipnsKeyPair;
5163
+ const ipnsName = overrideIpns?.name ?? this.ipnsName;
5164
+ const metaAddr = data?._meta?.address;
5165
+ logger.debug("IPFS-Storage", `_doSave: ipnsName=${ipnsName?.slice(0, 20)}..., override=${!!overrideIpns}, meta.address=${metaAddr?.slice(0, 20) ?? "none"}`);
5166
+ if (!ipnsKeyPair || !ipnsName) {
5077
5167
  return { success: false, error: "Not initialized", timestamp: Date.now() };
5078
5168
  }
5079
5169
  this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
@@ -5082,7 +5172,7 @@ var IpfsStorageProvider = class {
5082
5172
  const metaUpdate = {
5083
5173
  ...data._meta,
5084
5174
  version: this.dataVersion,
5085
- ipnsName: this.ipnsName,
5175
+ ipnsName,
5086
5176
  updatedAt: Date.now()
5087
5177
  };
5088
5178
  if (this.remoteCid) {
@@ -5094,13 +5184,13 @@ var IpfsStorageProvider = class {
5094
5184
  const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
5095
5185
  const newSeq = baseSeq + 1n;
5096
5186
  const marshalledRecord = await createSignedRecord(
5097
- this.ipnsKeyPair,
5187
+ ipnsKeyPair,
5098
5188
  cid,
5099
5189
  newSeq,
5100
5190
  this.ipnsLifetimeMs
5101
5191
  );
5102
5192
  const publishResult = await this.httpClient.publishIpns(
5103
- this.ipnsName,
5193
+ ipnsName,
5104
5194
  marshalledRecord
5105
5195
  );
5106
5196
  if (!publishResult.success) {
@@ -5115,14 +5205,14 @@ var IpfsStorageProvider = class {
5115
5205
  this.ipnsSequenceNumber = newSeq;
5116
5206
  this.lastCid = cid;
5117
5207
  this.remoteCid = cid;
5118
- this.cache.setIpnsRecord(this.ipnsName, {
5208
+ this.cache.setIpnsRecord(ipnsName, {
5119
5209
  cid,
5120
5210
  sequence: newSeq,
5121
5211
  gateway: "local"
5122
5212
  });
5123
5213
  this.cache.setContent(cid, updatedData);
5124
- this.cache.markIpnsFresh(this.ipnsName);
5125
- await this.statePersistence.save(this.ipnsName, {
5214
+ this.cache.markIpnsFresh(ipnsName);
5215
+ await this.statePersistence.save(ipnsName, {
5126
5216
  sequenceNumber: newSeq.toString(),
5127
5217
  lastCid: cid,
5128
5218
  version: this.dataVersion
@@ -5174,7 +5264,8 @@ var IpfsStorageProvider = class {
5174
5264
  const baseData = active.txfData ?? {
5175
5265
  _meta: { version: 0, address: this.identity?.directAddress ?? "", formatVersion: "2.0", updatedAt: 0 }
5176
5266
  };
5177
- const result = await this._doSave(baseData);
5267
+ const overrideIpns = active.capturedIpnsKeyPair && active.capturedIpnsName ? { keyPair: active.capturedIpnsKeyPair, name: active.capturedIpnsName } : void 0;
5268
+ const result = await this._doSave(baseData, overrideIpns);
5178
5269
  if (!result.success) {
5179
5270
  throw new SphereError(result.error ?? "Save failed", "STORAGE_ERROR");
5180
5271
  }
@@ -5477,6 +5568,13 @@ var IpfsStorageProvider = class {
5477
5568
  log(message) {
5478
5569
  logger.debug("IPFS-Storage", message);
5479
5570
  }
5571
+ /**
5572
+ * Create an independent instance for a different address.
5573
+ * Shares the same gateway/timeout config but has fresh IPNS state.
5574
+ */
5575
+ createForAddress() {
5576
+ return new _IpfsStorageProvider(this._config, this._statePersistenceCtor);
5577
+ }
5480
5578
  };
5481
5579
 
5482
5580
  // impl/browser/ipfs/browser-ipfs-state-persistence.ts