@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.
@@ -818,7 +818,7 @@ var STORE_TOKENS = "tokens";
818
818
  var STORE_META = "meta";
819
819
  var STORE_HISTORY = "history";
820
820
  var connectionSeq2 = 0;
821
- var IndexedDBTokenStorageProvider = class {
821
+ var IndexedDBTokenStorageProvider = class _IndexedDBTokenStorageProvider {
822
822
  id = "indexeddb-token-storage";
823
823
  name = "IndexedDB Token Storage";
824
824
  type = "local";
@@ -1266,6 +1266,16 @@ var IndexedDBTokenStorageProvider = class {
1266
1266
  db.close();
1267
1267
  }
1268
1268
  }
1269
+ /**
1270
+ * Create an independent instance for a different address.
1271
+ * The new instance shares the same config but has its own IDB connection.
1272
+ */
1273
+ createForAddress() {
1274
+ return new _IndexedDBTokenStorageProvider({
1275
+ dbNamePrefix: this.dbNamePrefix,
1276
+ debug: this.debug
1277
+ });
1278
+ }
1269
1279
  };
1270
1280
  function createIndexedDBTokenStorageProvider(config) {
1271
1281
  return new IndexedDBTokenStorageProvider(config);
@@ -1682,6 +1692,8 @@ var NostrTransportProvider = class {
1682
1692
  storage = null;
1683
1693
  /** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
1684
1694
  lastEventTs = 0;
1695
+ /** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
1696
+ fallbackSince = null;
1685
1697
  identity = null;
1686
1698
  keyManager = null;
1687
1699
  status = "disconnected";
@@ -1714,6 +1726,48 @@ var NostrTransportProvider = class {
1714
1726
  };
1715
1727
  this.storage = config.storage ?? null;
1716
1728
  }
1729
+ /**
1730
+ * Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
1731
+ */
1732
+ getWebSocketFactory() {
1733
+ return this.config.createWebSocket;
1734
+ }
1735
+ /**
1736
+ * Get the configured relay URLs.
1737
+ */
1738
+ getConfiguredRelays() {
1739
+ return [...this.config.relays];
1740
+ }
1741
+ /**
1742
+ * Get the storage adapter.
1743
+ */
1744
+ getStorageAdapter() {
1745
+ return this.storage;
1746
+ }
1747
+ /**
1748
+ * Suppress event subscriptions — unsubscribe wallet/chat filters
1749
+ * but keep the connection alive for resolve/identity-binding operations.
1750
+ * Used when MultiAddressTransportMux takes over event handling.
1751
+ */
1752
+ suppressSubscriptions() {
1753
+ if (!this.nostrClient) return;
1754
+ if (this.walletSubscriptionId) {
1755
+ this.nostrClient.unsubscribe(this.walletSubscriptionId);
1756
+ this.walletSubscriptionId = null;
1757
+ }
1758
+ if (this.chatSubscriptionId) {
1759
+ this.nostrClient.unsubscribe(this.chatSubscriptionId);
1760
+ this.chatSubscriptionId = null;
1761
+ }
1762
+ if (this.mainSubscriptionId) {
1763
+ this.nostrClient.unsubscribe(this.mainSubscriptionId);
1764
+ this.mainSubscriptionId = null;
1765
+ }
1766
+ this._subscriptionsSuppressed = true;
1767
+ logger.debug("Nostr", "Subscriptions suppressed \u2014 mux handles event routing");
1768
+ }
1769
+ // Flag to prevent re-subscription after suppressSubscriptions()
1770
+ _subscriptionsSuppressed = false;
1717
1771
  // ===========================================================================
1718
1772
  // BaseProvider Implementation
1719
1773
  // ===========================================================================
@@ -1892,6 +1946,8 @@ var NostrTransportProvider = class {
1892
1946
  // ===========================================================================
1893
1947
  async setIdentity(identity) {
1894
1948
  this.identity = identity;
1949
+ this.processedEventIds.clear();
1950
+ this.lastEventTs = 0;
1895
1951
  const secretKey = import_buffer.Buffer.from(identity.privateKey, "hex");
1896
1952
  this.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(secretKey);
1897
1953
  const nostrPubkey = this.keyManager.getPublicKeyHex();
@@ -1934,6 +1990,9 @@ var NostrTransportProvider = class {
1934
1990
  await this.subscribeToEvents();
1935
1991
  }
1936
1992
  }
1993
+ setFallbackSince(sinceSeconds) {
1994
+ this.fallbackSince = sinceSeconds;
1995
+ }
1937
1996
  /**
1938
1997
  * Get the Nostr-format public key (32 bytes / 64 hex chars)
1939
1998
  * This is the x-coordinate only, without the 02/03 prefix.
@@ -2841,6 +2900,10 @@ var NostrTransportProvider = class {
2841
2900
  chatEoseFired = false;
2842
2901
  async subscribeToEvents() {
2843
2902
  logger.debug("Nostr", "subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
2903
+ if (this._subscriptionsSuppressed) {
2904
+ logger.debug("Nostr", "subscribeToEvents: suppressed \u2014 mux handles event routing");
2905
+ return;
2906
+ }
2844
2907
  if (!this.identity || !this.keyManager || !this.nostrClient) {
2845
2908
  logger.debug("Nostr", "subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
2846
2909
  return;
@@ -2867,7 +2930,13 @@ var NostrTransportProvider = class {
2867
2930
  if (stored) {
2868
2931
  since = parseInt(stored, 10);
2869
2932
  this.lastEventTs = since;
2933
+ this.fallbackSince = null;
2870
2934
  logger.debug("Nostr", "Resuming from stored event timestamp:", since);
2935
+ } else if (this.fallbackSince !== null) {
2936
+ since = this.fallbackSince;
2937
+ this.lastEventTs = since;
2938
+ this.fallbackSince = null;
2939
+ logger.debug("Nostr", "Using fallback since timestamp:", since);
2871
2940
  } else {
2872
2941
  since = Math.floor(Date.now() / 1e3);
2873
2942
  logger.debug("Nostr", "No stored timestamp, starting from now:", since);
@@ -2875,6 +2944,7 @@ var NostrTransportProvider = class {
2875
2944
  } catch (err) {
2876
2945
  logger.debug("Nostr", "Failed to read last event timestamp, falling back to now:", err);
2877
2946
  since = Math.floor(Date.now() / 1e3);
2947
+ this.fallbackSince = null;
2878
2948
  }
2879
2949
  } else {
2880
2950
  since = Math.floor(Date.now() / 1e3) - 86400;
@@ -4895,11 +4965,17 @@ var AsyncSerialQueue = class {
4895
4965
  var WriteBuffer = class {
4896
4966
  /** Full TXF data from save() calls — latest wins */
4897
4967
  txfData = null;
4968
+ /** IPNS context captured at save() time — ensures flush writes to the correct
4969
+ * IPNS record even if identity changes between save() and flush(). */
4970
+ capturedIpnsKeyPair = null;
4971
+ capturedIpnsName = null;
4898
4972
  get isEmpty() {
4899
4973
  return this.txfData === null;
4900
4974
  }
4901
4975
  clear() {
4902
4976
  this.txfData = null;
4977
+ this.capturedIpnsKeyPair = null;
4978
+ this.capturedIpnsName = null;
4903
4979
  }
4904
4980
  /**
4905
4981
  * Merge another buffer's contents into this one (for rollback).
@@ -4908,12 +4984,14 @@ var WriteBuffer = class {
4908
4984
  mergeFrom(other) {
4909
4985
  if (other.txfData && !this.txfData) {
4910
4986
  this.txfData = other.txfData;
4987
+ this.capturedIpnsKeyPair = other.capturedIpnsKeyPair;
4988
+ this.capturedIpnsName = other.capturedIpnsName;
4911
4989
  }
4912
4990
  }
4913
4991
  };
4914
4992
 
4915
4993
  // impl/shared/ipfs/ipfs-storage-provider.ts
4916
- var IpfsStorageProvider = class {
4994
+ var IpfsStorageProvider = class _IpfsStorageProvider {
4917
4995
  id = "ipfs";
4918
4996
  name = "IPFS Storage";
4919
4997
  type = "p2p";
@@ -4958,7 +5036,12 @@ var IpfsStorageProvider = class {
4958
5036
  flushDebounceMs;
4959
5037
  /** Set to true during shutdown to prevent new flushes */
4960
5038
  isShuttingDown = false;
5039
+ /** Stored config for createForAddress() cloning */
5040
+ _config;
5041
+ _statePersistenceCtor;
4961
5042
  constructor(config, statePersistence) {
5043
+ this._config = config;
5044
+ this._statePersistenceCtor = statePersistence;
4962
5045
  const gateways = config?.gateways ?? getIpfsGatewayUrls();
4963
5046
  this.debug = config?.debug ?? false;
4964
5047
  this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? 99 * 365 * 24 * 60 * 60 * 1e3;
@@ -5078,6 +5161,7 @@ var IpfsStorageProvider = class {
5078
5161
  }
5079
5162
  async shutdown() {
5080
5163
  this.isShuttingDown = true;
5164
+ logger.debug("IPFS-Storage", `shutdown: ipnsName=${this.ipnsName?.slice(0, 20)}..., pendingEmpty=${this.pendingBuffer.isEmpty}, capturedIpns=${this.pendingBuffer.capturedIpnsName?.slice(0, 20) ?? "none"}`);
5081
5165
  if (this.flushTimer) {
5082
5166
  clearTimeout(this.flushTimer);
5083
5167
  this.flushTimer = null;
@@ -5110,6 +5194,8 @@ var IpfsStorageProvider = class {
5110
5194
  return { success: false, error: "Not initialized", timestamp: Date.now() };
5111
5195
  }
5112
5196
  this.pendingBuffer.txfData = data;
5197
+ this.pendingBuffer.capturedIpnsKeyPair = this.ipnsKeyPair;
5198
+ this.pendingBuffer.capturedIpnsName = this.ipnsName;
5113
5199
  this.scheduleFlush();
5114
5200
  return { success: true, timestamp: Date.now() };
5115
5201
  }
@@ -5120,8 +5206,12 @@ var IpfsStorageProvider = class {
5120
5206
  * Perform the actual upload + IPNS publish synchronously.
5121
5207
  * Called by executeFlush() and sync() — never by public save().
5122
5208
  */
5123
- async _doSave(data) {
5124
- if (!this.ipnsKeyPair || !this.ipnsName) {
5209
+ async _doSave(data, overrideIpns) {
5210
+ const ipnsKeyPair = overrideIpns?.keyPair ?? this.ipnsKeyPair;
5211
+ const ipnsName = overrideIpns?.name ?? this.ipnsName;
5212
+ const metaAddr = data?._meta?.address;
5213
+ logger.debug("IPFS-Storage", `_doSave: ipnsName=${ipnsName?.slice(0, 20)}..., override=${!!overrideIpns}, meta.address=${metaAddr?.slice(0, 20) ?? "none"}`);
5214
+ if (!ipnsKeyPair || !ipnsName) {
5125
5215
  return { success: false, error: "Not initialized", timestamp: Date.now() };
5126
5216
  }
5127
5217
  this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
@@ -5130,7 +5220,7 @@ var IpfsStorageProvider = class {
5130
5220
  const metaUpdate = {
5131
5221
  ...data._meta,
5132
5222
  version: this.dataVersion,
5133
- ipnsName: this.ipnsName,
5223
+ ipnsName,
5134
5224
  updatedAt: Date.now()
5135
5225
  };
5136
5226
  if (this.remoteCid) {
@@ -5142,13 +5232,13 @@ var IpfsStorageProvider = class {
5142
5232
  const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
5143
5233
  const newSeq = baseSeq + 1n;
5144
5234
  const marshalledRecord = await createSignedRecord(
5145
- this.ipnsKeyPair,
5235
+ ipnsKeyPair,
5146
5236
  cid,
5147
5237
  newSeq,
5148
5238
  this.ipnsLifetimeMs
5149
5239
  );
5150
5240
  const publishResult = await this.httpClient.publishIpns(
5151
- this.ipnsName,
5241
+ ipnsName,
5152
5242
  marshalledRecord
5153
5243
  );
5154
5244
  if (!publishResult.success) {
@@ -5163,14 +5253,14 @@ var IpfsStorageProvider = class {
5163
5253
  this.ipnsSequenceNumber = newSeq;
5164
5254
  this.lastCid = cid;
5165
5255
  this.remoteCid = cid;
5166
- this.cache.setIpnsRecord(this.ipnsName, {
5256
+ this.cache.setIpnsRecord(ipnsName, {
5167
5257
  cid,
5168
5258
  sequence: newSeq,
5169
5259
  gateway: "local"
5170
5260
  });
5171
5261
  this.cache.setContent(cid, updatedData);
5172
- this.cache.markIpnsFresh(this.ipnsName);
5173
- await this.statePersistence.save(this.ipnsName, {
5262
+ this.cache.markIpnsFresh(ipnsName);
5263
+ await this.statePersistence.save(ipnsName, {
5174
5264
  sequenceNumber: newSeq.toString(),
5175
5265
  lastCid: cid,
5176
5266
  version: this.dataVersion
@@ -5222,7 +5312,8 @@ var IpfsStorageProvider = class {
5222
5312
  const baseData = active.txfData ?? {
5223
5313
  _meta: { version: 0, address: this.identity?.directAddress ?? "", formatVersion: "2.0", updatedAt: 0 }
5224
5314
  };
5225
- const result = await this._doSave(baseData);
5315
+ const overrideIpns = active.capturedIpnsKeyPair && active.capturedIpnsName ? { keyPair: active.capturedIpnsKeyPair, name: active.capturedIpnsName } : void 0;
5316
+ const result = await this._doSave(baseData, overrideIpns);
5226
5317
  if (!result.success) {
5227
5318
  throw new SphereError(result.error ?? "Save failed", "STORAGE_ERROR");
5228
5319
  }
@@ -5525,6 +5616,13 @@ var IpfsStorageProvider = class {
5525
5616
  log(message) {
5526
5617
  logger.debug("IPFS-Storage", message);
5527
5618
  }
5619
+ /**
5620
+ * Create an independent instance for a different address.
5621
+ * Shares the same gateway/timeout config but has fresh IPNS state.
5622
+ */
5623
+ createForAddress() {
5624
+ return new _IpfsStorageProvider(this._config, this._statePersistenceCtor);
5625
+ }
5528
5626
  };
5529
5627
 
5530
5628
  // impl/browser/ipfs/browser-ipfs-state-persistence.ts