@unicitylabs/sphere-sdk 0.6.1 → 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
  // ===========================================================================
@@ -1781,6 +1835,7 @@ var NostrTransportProvider = class {
1781
1835
  this.mainSubscriptionId = null;
1782
1836
  this.walletSubscriptionId = null;
1783
1837
  this.chatSubscriptionId = null;
1838
+ this.chatEoseFired = false;
1784
1839
  this.status = "disconnected";
1785
1840
  this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
1786
1841
  logger.debug("Nostr", "Disconnected from all relays");
@@ -1891,6 +1946,8 @@ var NostrTransportProvider = class {
1891
1946
  // ===========================================================================
1892
1947
  async setIdentity(identity) {
1893
1948
  this.identity = identity;
1949
+ this.processedEventIds.clear();
1950
+ this.lastEventTs = 0;
1894
1951
  const secretKey = import_buffer.Buffer.from(identity.privateKey, "hex");
1895
1952
  this.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(secretKey);
1896
1953
  const nostrPubkey = this.keyManager.getPublicKeyHex();
@@ -1933,6 +1990,9 @@ var NostrTransportProvider = class {
1933
1990
  await this.subscribeToEvents();
1934
1991
  }
1935
1992
  }
1993
+ setFallbackSince(sinceSeconds) {
1994
+ this.fallbackSince = sinceSeconds;
1995
+ }
1936
1996
  /**
1937
1997
  * Get the Nostr-format public key (32 bytes / 64 hex chars)
1938
1998
  * This is the x-coordinate only, without the 02/03 prefix.
@@ -2104,6 +2164,20 @@ var NostrTransportProvider = class {
2104
2164
  this.typingIndicatorHandlers.add(handler);
2105
2165
  return () => this.typingIndicatorHandlers.delete(handler);
2106
2166
  }
2167
+ onChatReady(handler) {
2168
+ if (this.chatEoseFired) {
2169
+ try {
2170
+ handler();
2171
+ } catch {
2172
+ }
2173
+ return () => {
2174
+ };
2175
+ }
2176
+ this.chatEoseHandlers.push(handler);
2177
+ return () => {
2178
+ this.chatEoseHandlers = this.chatEoseHandlers.filter((h) => h !== handler);
2179
+ };
2180
+ }
2107
2181
  // ===========================================================================
2108
2182
  // Composing Indicators (NIP-59 kind 25050)
2109
2183
  // ===========================================================================
@@ -2821,8 +2895,15 @@ var NostrTransportProvider = class {
2821
2895
  // Track subscription IDs for cleanup
2822
2896
  walletSubscriptionId = null;
2823
2897
  chatSubscriptionId = null;
2898
+ // Chat EOSE handlers — fired once when relay finishes delivering stored DMs
2899
+ chatEoseHandlers = [];
2900
+ chatEoseFired = false;
2824
2901
  async subscribeToEvents() {
2825
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
+ }
2826
2907
  if (!this.identity || !this.keyManager || !this.nostrClient) {
2827
2908
  logger.debug("Nostr", "subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
2828
2909
  return;
@@ -2849,7 +2930,13 @@ var NostrTransportProvider = class {
2849
2930
  if (stored) {
2850
2931
  since = parseInt(stored, 10);
2851
2932
  this.lastEventTs = since;
2933
+ this.fallbackSince = null;
2852
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);
2853
2940
  } else {
2854
2941
  since = Math.floor(Date.now() / 1e3);
2855
2942
  logger.debug("Nostr", "No stored timestamp, starting from now:", since);
@@ -2857,6 +2944,7 @@ var NostrTransportProvider = class {
2857
2944
  } catch (err) {
2858
2945
  logger.debug("Nostr", "Failed to read last event timestamp, falling back to now:", err);
2859
2946
  since = Math.floor(Date.now() / 1e3);
2947
+ this.fallbackSince = null;
2860
2948
  }
2861
2949
  } else {
2862
2950
  since = Math.floor(Date.now() / 1e3) - 86400;
@@ -2910,6 +2998,16 @@ var NostrTransportProvider = class {
2910
2998
  },
2911
2999
  onEndOfStoredEvents: () => {
2912
3000
  logger.debug("Nostr", "Chat subscription ready (EOSE)");
3001
+ if (!this.chatEoseFired) {
3002
+ this.chatEoseFired = true;
3003
+ for (const handler of this.chatEoseHandlers) {
3004
+ try {
3005
+ handler();
3006
+ } catch {
3007
+ }
3008
+ }
3009
+ this.chatEoseHandlers = [];
3010
+ }
2913
3011
  },
2914
3012
  onError: (_subId, error) => {
2915
3013
  logger.debug("Nostr", "Chat subscription error:", error);
@@ -4867,11 +4965,17 @@ var AsyncSerialQueue = class {
4867
4965
  var WriteBuffer = class {
4868
4966
  /** Full TXF data from save() calls — latest wins */
4869
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;
4870
4972
  get isEmpty() {
4871
4973
  return this.txfData === null;
4872
4974
  }
4873
4975
  clear() {
4874
4976
  this.txfData = null;
4977
+ this.capturedIpnsKeyPair = null;
4978
+ this.capturedIpnsName = null;
4875
4979
  }
4876
4980
  /**
4877
4981
  * Merge another buffer's contents into this one (for rollback).
@@ -4880,12 +4984,14 @@ var WriteBuffer = class {
4880
4984
  mergeFrom(other) {
4881
4985
  if (other.txfData && !this.txfData) {
4882
4986
  this.txfData = other.txfData;
4987
+ this.capturedIpnsKeyPair = other.capturedIpnsKeyPair;
4988
+ this.capturedIpnsName = other.capturedIpnsName;
4883
4989
  }
4884
4990
  }
4885
4991
  };
4886
4992
 
4887
4993
  // impl/shared/ipfs/ipfs-storage-provider.ts
4888
- var IpfsStorageProvider = class {
4994
+ var IpfsStorageProvider = class _IpfsStorageProvider {
4889
4995
  id = "ipfs";
4890
4996
  name = "IPFS Storage";
4891
4997
  type = "p2p";
@@ -4930,7 +5036,12 @@ var IpfsStorageProvider = class {
4930
5036
  flushDebounceMs;
4931
5037
  /** Set to true during shutdown to prevent new flushes */
4932
5038
  isShuttingDown = false;
5039
+ /** Stored config for createForAddress() cloning */
5040
+ _config;
5041
+ _statePersistenceCtor;
4933
5042
  constructor(config, statePersistence) {
5043
+ this._config = config;
5044
+ this._statePersistenceCtor = statePersistence;
4934
5045
  const gateways = config?.gateways ?? getIpfsGatewayUrls();
4935
5046
  this.debug = config?.debug ?? false;
4936
5047
  this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? 99 * 365 * 24 * 60 * 60 * 1e3;
@@ -5050,6 +5161,7 @@ var IpfsStorageProvider = class {
5050
5161
  }
5051
5162
  async shutdown() {
5052
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"}`);
5053
5165
  if (this.flushTimer) {
5054
5166
  clearTimeout(this.flushTimer);
5055
5167
  this.flushTimer = null;
@@ -5082,6 +5194,8 @@ var IpfsStorageProvider = class {
5082
5194
  return { success: false, error: "Not initialized", timestamp: Date.now() };
5083
5195
  }
5084
5196
  this.pendingBuffer.txfData = data;
5197
+ this.pendingBuffer.capturedIpnsKeyPair = this.ipnsKeyPair;
5198
+ this.pendingBuffer.capturedIpnsName = this.ipnsName;
5085
5199
  this.scheduleFlush();
5086
5200
  return { success: true, timestamp: Date.now() };
5087
5201
  }
@@ -5092,8 +5206,12 @@ var IpfsStorageProvider = class {
5092
5206
  * Perform the actual upload + IPNS publish synchronously.
5093
5207
  * Called by executeFlush() and sync() — never by public save().
5094
5208
  */
5095
- async _doSave(data) {
5096
- 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) {
5097
5215
  return { success: false, error: "Not initialized", timestamp: Date.now() };
5098
5216
  }
5099
5217
  this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
@@ -5102,7 +5220,7 @@ var IpfsStorageProvider = class {
5102
5220
  const metaUpdate = {
5103
5221
  ...data._meta,
5104
5222
  version: this.dataVersion,
5105
- ipnsName: this.ipnsName,
5223
+ ipnsName,
5106
5224
  updatedAt: Date.now()
5107
5225
  };
5108
5226
  if (this.remoteCid) {
@@ -5114,13 +5232,13 @@ var IpfsStorageProvider = class {
5114
5232
  const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
5115
5233
  const newSeq = baseSeq + 1n;
5116
5234
  const marshalledRecord = await createSignedRecord(
5117
- this.ipnsKeyPair,
5235
+ ipnsKeyPair,
5118
5236
  cid,
5119
5237
  newSeq,
5120
5238
  this.ipnsLifetimeMs
5121
5239
  );
5122
5240
  const publishResult = await this.httpClient.publishIpns(
5123
- this.ipnsName,
5241
+ ipnsName,
5124
5242
  marshalledRecord
5125
5243
  );
5126
5244
  if (!publishResult.success) {
@@ -5135,14 +5253,14 @@ var IpfsStorageProvider = class {
5135
5253
  this.ipnsSequenceNumber = newSeq;
5136
5254
  this.lastCid = cid;
5137
5255
  this.remoteCid = cid;
5138
- this.cache.setIpnsRecord(this.ipnsName, {
5256
+ this.cache.setIpnsRecord(ipnsName, {
5139
5257
  cid,
5140
5258
  sequence: newSeq,
5141
5259
  gateway: "local"
5142
5260
  });
5143
5261
  this.cache.setContent(cid, updatedData);
5144
- this.cache.markIpnsFresh(this.ipnsName);
5145
- await this.statePersistence.save(this.ipnsName, {
5262
+ this.cache.markIpnsFresh(ipnsName);
5263
+ await this.statePersistence.save(ipnsName, {
5146
5264
  sequenceNumber: newSeq.toString(),
5147
5265
  lastCid: cid,
5148
5266
  version: this.dataVersion
@@ -5194,7 +5312,8 @@ var IpfsStorageProvider = class {
5194
5312
  const baseData = active.txfData ?? {
5195
5313
  _meta: { version: 0, address: this.identity?.directAddress ?? "", formatVersion: "2.0", updatedAt: 0 }
5196
5314
  };
5197
- 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);
5198
5317
  if (!result.success) {
5199
5318
  throw new SphereError(result.error ?? "Save failed", "STORAGE_ERROR");
5200
5319
  }
@@ -5497,6 +5616,13 @@ var IpfsStorageProvider = class {
5497
5616
  log(message) {
5498
5617
  logger.debug("IPFS-Storage", message);
5499
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
+ }
5500
5626
  };
5501
5627
 
5502
5628
  // impl/browser/ipfs/browser-ipfs-state-persistence.ts