@unicitylabs/sphere-sdk 0.6.10-dev.4 → 0.6.10-dev.6

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.
package/dist/index.d.cts CHANGED
@@ -3740,7 +3740,7 @@ declare class GroupChatModule {
3740
3740
  * or 0 if no messages exist. Used to set `since` on subscriptions so the relay
3741
3741
  * only sends events we don't already have.
3742
3742
  */
3743
- private getLatestMessageTimestamp;
3743
+ private getLatestKnownTimestamp;
3744
3744
  private fetchRelayAdmins;
3745
3745
  private doFetchRelayAdmins;
3746
3746
  private fetchGroupMetadataInternal;
package/dist/index.d.ts CHANGED
@@ -3740,7 +3740,7 @@ declare class GroupChatModule {
3740
3740
  * or 0 if no messages exist. Used to set `since` on subscriptions so the relay
3741
3741
  * only sends events we don't already have.
3742
3742
  */
3743
- private getLatestMessageTimestamp;
3743
+ private getLatestKnownTimestamp;
3744
3744
  private fetchRelayAdmins;
3745
3745
  private doFetchRelayAdmins;
3746
3746
  private fetchGroupMetadataInternal;
package/dist/index.js CHANGED
@@ -542,6 +542,33 @@ function waitForConnection() {
542
542
  connectionCallbacks.push(callback);
543
543
  });
544
544
  }
545
+ function startPingTimer() {
546
+ stopPingTimer();
547
+ pingTimer = setInterval(() => {
548
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
549
+ try {
550
+ const id = ++requestId;
551
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id, method: "server.ping", params: [] }));
552
+ pending[id] = {
553
+ resolve: () => {
554
+ },
555
+ reject: () => {
556
+ }
557
+ };
558
+ const timeoutId = setTimeout(() => {
559
+ delete pending[id];
560
+ }, 1e4);
561
+ pending[id].timeoutId = timeoutId;
562
+ } catch {
563
+ }
564
+ }, PING_INTERVAL);
565
+ }
566
+ function stopPingTimer() {
567
+ if (pingTimer) {
568
+ clearInterval(pingTimer);
569
+ pingTimer = null;
570
+ }
571
+ }
545
572
  function connect(endpoint = DEFAULT_ENDPOINT) {
546
573
  if (isConnected) {
547
574
  return Promise.resolve();
@@ -564,6 +591,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
564
591
  isConnected = true;
565
592
  isConnecting = false;
566
593
  reconnectAttempts = 0;
594
+ startPingTimer();
567
595
  hasResolved = true;
568
596
  resolve();
569
597
  connectionCallbacks.forEach((cb) => {
@@ -575,6 +603,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
575
603
  ws.onclose = () => {
576
604
  isConnected = false;
577
605
  isBlockSubscribed = false;
606
+ stopPingTimer();
578
607
  Object.values(pending).forEach((req) => {
579
608
  if (req.timeoutId) clearTimeout(req.timeoutId);
580
609
  req.reject(new Error("WebSocket connection closed"));
@@ -757,6 +786,7 @@ async function getCurrentBlockHeight() {
757
786
  }
758
787
  }
759
788
  function disconnect() {
789
+ stopPingTimer();
760
790
  if (ws) {
761
791
  intentionalClose = true;
762
792
  ws.close();
@@ -777,7 +807,7 @@ function disconnect() {
777
807
  blockSubscribers.length = 0;
778
808
  lastBlockHeader = null;
779
809
  }
780
- var DEFAULT_ENDPOINT, ws, isConnected, isConnecting, requestId, intentionalClose, reconnectAttempts, isBlockSubscribed, lastBlockHeader, pending, blockSubscribers, connectionCallbacks, MAX_RECONNECT_ATTEMPTS, BASE_DELAY, MAX_DELAY, RPC_TIMEOUT, CONNECTION_TIMEOUT;
810
+ var DEFAULT_ENDPOINT, ws, isConnected, isConnecting, requestId, intentionalClose, reconnectAttempts, isBlockSubscribed, lastBlockHeader, pingTimer, pending, blockSubscribers, connectionCallbacks, MAX_RECONNECT_ATTEMPTS, BASE_DELAY, MAX_DELAY, RPC_TIMEOUT, CONNECTION_TIMEOUT, PING_INTERVAL;
781
811
  var init_network = __esm({
782
812
  "l1/network.ts"() {
783
813
  "use strict";
@@ -793,6 +823,7 @@ var init_network = __esm({
793
823
  reconnectAttempts = 0;
794
824
  isBlockSubscribed = false;
795
825
  lastBlockHeader = null;
826
+ pingTimer = null;
796
827
  pending = {};
797
828
  blockSubscribers = [];
798
829
  connectionCallbacks = [];
@@ -801,6 +832,7 @@ var init_network = __esm({
801
832
  MAX_DELAY = 6e4;
802
833
  RPC_TIMEOUT = 3e4;
803
834
  CONNECTION_TIMEOUT = 3e4;
835
+ PING_INTERVAL = 3e4;
804
836
  }
805
837
  });
806
838
 
@@ -2461,9 +2493,9 @@ var NostrTransportProvider = class _NostrTransportProvider {
2461
2493
  if (subId) {
2462
2494
  this.nostrClient?.unsubscribe(subId);
2463
2495
  }
2464
- logger.warn("Nostr", `queryEvents timed out after 5s, returning ${events.length} event(s)`, { kinds: filterObj.kinds, limit: filterObj.limit });
2496
+ logger.warn("Nostr", `queryEvents timed out after 15s, returning ${events.length} event(s)`, { kinds: filterObj.kinds, limit: filterObj.limit });
2465
2497
  resolve(events);
2466
- }, 5e3);
2498
+ }, 15e3);
2467
2499
  const subId = this.nostrClient.subscribe(filter, {
2468
2500
  onEvent: (event) => {
2469
2501
  events.push({
@@ -12383,6 +12415,15 @@ var GroupChatModule = class {
12383
12415
  }
12384
12416
  destroy() {
12385
12417
  this.destroyConnection();
12418
+ if (this.persistTimer) {
12419
+ clearTimeout(this.persistTimer);
12420
+ this.persistTimer = null;
12421
+ if (this.deps) {
12422
+ this.doPersistAll().catch(
12423
+ (err) => logger.debug("GroupChat", "Persist on destroy failed", err)
12424
+ );
12425
+ }
12426
+ }
12386
12427
  this.groups.clear();
12387
12428
  this.messages.clear();
12388
12429
  this.members.clear();
@@ -12391,10 +12432,7 @@ var GroupChatModule = class {
12391
12432
  this.messageHandlers.clear();
12392
12433
  this.relayAdminPubkeys = null;
12393
12434
  this.relayAdminFetchPromise = null;
12394
- if (this.persistTimer) {
12395
- clearTimeout(this.persistTimer);
12396
- this.persistTimer = null;
12397
- }
12435
+ this.persistPromise = null;
12398
12436
  this.deps = null;
12399
12437
  }
12400
12438
  destroyConnection() {
@@ -12519,12 +12557,12 @@ var GroupChatModule = class {
12519
12557
  if (!this.client) return;
12520
12558
  const groupIds = Array.from(this.groups.keys());
12521
12559
  if (groupIds.length === 0) return;
12522
- const latestTimestamp = this.getLatestMessageTimestamp(groupIds);
12560
+ const sinceTimestamp = this.getLatestKnownTimestamp(groupIds);
12523
12561
  this.trackSubscription(
12524
12562
  createNip29Filter({
12525
12563
  kinds: [NIP29_KINDS.CHAT_MESSAGE, NIP29_KINDS.THREAD_ROOT, NIP29_KINDS.THREAD_REPLY],
12526
12564
  "#h": groupIds,
12527
- ...latestTimestamp ? { since: latestTimestamp } : {}
12565
+ ...sinceTimestamp ? { since: sinceTimestamp } : {}
12528
12566
  }),
12529
12567
  { onEvent: (event) => this.handleGroupEvent(event) }
12530
12568
  );
@@ -12545,12 +12583,12 @@ var GroupChatModule = class {
12545
12583
  }
12546
12584
  subscribeToGroup(groupId) {
12547
12585
  if (!this.client) return;
12548
- const latestTimestamp = this.getLatestMessageTimestamp([groupId]);
12586
+ const sinceTimestamp = this.getLatestKnownTimestamp([groupId]);
12549
12587
  this.trackSubscription(
12550
12588
  createNip29Filter({
12551
12589
  kinds: [NIP29_KINDS.CHAT_MESSAGE, NIP29_KINDS.THREAD_ROOT, NIP29_KINDS.THREAD_REPLY],
12552
12590
  "#h": [groupId],
12553
- ...latestTimestamp ? { since: latestTimestamp } : {}
12591
+ ...sinceTimestamp ? { since: sinceTimestamp } : {}
12554
12592
  }),
12555
12593
  { onEvent: (event) => this.handleGroupEvent(event) }
12556
12594
  );
@@ -12624,7 +12662,7 @@ var GroupChatModule = class {
12624
12662
  }
12625
12663
  group.updatedAt = event.created_at * 1e3;
12626
12664
  this.groups.set(groupId, group);
12627
- this.persistGroups();
12665
+ this.schedulePersist();
12628
12666
  } else if (event.kind === NIP29_KINDS.GROUP_MEMBERS) {
12629
12667
  this.updateMembersFromEvent(groupId, event);
12630
12668
  } else if (event.kind === NIP29_KINDS.GROUP_ADMINS) {
@@ -12645,7 +12683,7 @@ var GroupChatModule = class {
12645
12683
  }
12646
12684
  }
12647
12685
  this.deps.emitEvent("groupchat:updated", {});
12648
- this.persistMessages();
12686
+ this.schedulePersist();
12649
12687
  } else if (event.kind === NIP29_KINDS.REMOVE_USER) {
12650
12688
  if (this.processedEventIds.has(event.id)) return;
12651
12689
  const eventTimestampMs = event.created_at * 1e3;
@@ -12706,7 +12744,7 @@ var GroupChatModule = class {
12706
12744
  };
12707
12745
  this.saveMemberToMemory(member);
12708
12746
  }
12709
- this.persistMembers();
12747
+ this.schedulePersist();
12710
12748
  }
12711
12749
  updateAdminsFromEvent(groupId, event) {
12712
12750
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -12726,7 +12764,7 @@ var GroupChatModule = class {
12726
12764
  });
12727
12765
  }
12728
12766
  }
12729
- this.persistMembers();
12767
+ this.schedulePersist();
12730
12768
  }
12731
12769
  // ===========================================================================
12732
12770
  // Group Membership Restoration
@@ -12737,13 +12775,11 @@ var GroupChatModule = class {
12737
12775
  if (!myPubkey) return [];
12738
12776
  const groupIdsWithMembership = /* @__PURE__ */ new Set();
12739
12777
  await this.oneshotSubscription(
12740
- new Filter3({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
12778
+ createNip29Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#p": [myPubkey] }),
12741
12779
  {
12742
12780
  onEvent: (event) => {
12743
12781
  const groupId = this.getGroupIdFromMetadataEvent(event);
12744
- if (!groupId) return;
12745
- const pTags = event.tags.filter((t) => t[0] === "p");
12746
- if (pTags.some((tag) => tag[1] === myPubkey)) {
12782
+ if (groupId) {
12747
12783
  groupIdsWithMembership.add(groupId);
12748
12784
  }
12749
12785
  },
@@ -12755,22 +12791,24 @@ var GroupChatModule = class {
12755
12791
  );
12756
12792
  if (groupIdsWithMembership.size === 0) return [];
12757
12793
  const restoredGroups = [];
12758
- for (const groupId of groupIdsWithMembership) {
12759
- if (this.groups.has(groupId)) continue;
12760
- try {
12761
- const group = await this.fetchGroupMetadataInternal(groupId);
12762
- if (group) {
12763
- this.groups.set(groupId, group);
12764
- restoredGroups.push(group);
12765
- await Promise.all([
12766
- this.fetchAndSaveMembers(groupId),
12767
- this.fetchMessages(groupId)
12768
- ]);
12794
+ await Promise.all(
12795
+ Array.from(groupIdsWithMembership).map(async (groupId) => {
12796
+ if (this.groups.has(groupId)) return;
12797
+ try {
12798
+ const group = await this.fetchGroupMetadataInternal(groupId);
12799
+ if (group) {
12800
+ this.groups.set(groupId, group);
12801
+ restoredGroups.push(group);
12802
+ await Promise.all([
12803
+ this.fetchAndSaveMembers(groupId),
12804
+ this.fetchMessages(groupId)
12805
+ ]);
12806
+ }
12807
+ } catch (error) {
12808
+ logger.warn("GroupChat", "Failed to restore group", groupId, error);
12769
12809
  }
12770
- } catch (error) {
12771
- logger.warn("GroupChat", "Failed to restore group", groupId, error);
12772
- }
12773
- }
12810
+ })
12811
+ );
12774
12812
  if (restoredGroups.length > 0) {
12775
12813
  await this.subscribeToJoinedGroups();
12776
12814
  this.deps.emitEvent("groupchat:updated", {});
@@ -12785,47 +12823,24 @@ var GroupChatModule = class {
12785
12823
  await this.ensureConnected();
12786
12824
  if (!this.client) return [];
12787
12825
  const groupsMap = /* @__PURE__ */ new Map();
12788
- const memberCountsMap = /* @__PURE__ */ new Map();
12789
- await Promise.all([
12790
- this.oneshotSubscription(
12791
- new Filter3({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
12792
- {
12793
- onEvent: (event) => {
12794
- const group = this.parseGroupMetadata(event);
12795
- if (group && group.visibility === GroupVisibility.PUBLIC) {
12796
- const existing = groupsMap.get(group.id);
12797
- if (!existing || group.createdAt > existing.createdAt) {
12798
- groupsMap.set(group.id, group);
12799
- }
12800
- }
12801
- },
12802
- onComplete: () => {
12803
- },
12804
- timeoutMs: 1e4,
12805
- timeoutLabel: "fetchAvailableGroups(metadata)"
12806
- }
12807
- ),
12808
- this.oneshotSubscription(
12809
- new Filter3({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
12810
- {
12811
- onEvent: (event) => {
12812
- const groupId = this.getGroupIdFromMetadataEvent(event);
12813
- if (groupId) {
12814
- const pTags = event.tags.filter((t) => t[0] === "p");
12815
- memberCountsMap.set(groupId, pTags.length);
12826
+ await this.oneshotSubscription(
12827
+ new Filter3({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
12828
+ {
12829
+ onEvent: (event) => {
12830
+ const group = this.parseGroupMetadata(event);
12831
+ if (group && group.visibility === GroupVisibility.PUBLIC) {
12832
+ const existing = groupsMap.get(group.id);
12833
+ if (!existing || group.createdAt > existing.createdAt) {
12834
+ groupsMap.set(group.id, group);
12816
12835
  }
12817
- },
12818
- onComplete: () => {
12819
- },
12820
- timeoutMs: 1e4,
12821
- timeoutLabel: "fetchAvailableGroups(members)"
12822
- }
12823
- )
12824
- ]);
12825
- for (const [groupId, count] of memberCountsMap) {
12826
- const group = groupsMap.get(groupId);
12827
- if (group) group.memberCount = count;
12828
- }
12836
+ }
12837
+ },
12838
+ onComplete: () => {
12839
+ },
12840
+ timeoutMs: 1e4,
12841
+ timeoutLabel: "fetchAvailableGroups(metadata)"
12842
+ }
12843
+ );
12829
12844
  return Array.from(groupsMap.values());
12830
12845
  }
12831
12846
  async joinGroup(groupId, inviteCode) {
@@ -13156,7 +13171,7 @@ var GroupChatModule = class {
13156
13171
  if (group && (group.unreadCount || 0) > 0) {
13157
13172
  group.unreadCount = 0;
13158
13173
  this.groups.set(groupId, group);
13159
- this.persistGroups();
13174
+ this.schedulePersist();
13160
13175
  }
13161
13176
  }
13162
13177
  // ===========================================================================
@@ -13178,7 +13193,7 @@ var GroupChatModule = class {
13178
13193
  if (eventId) {
13179
13194
  this.removeMemberFromMemory(groupId, userPubkey);
13180
13195
  this.deps.emitEvent("groupchat:updated", {});
13181
- this.persistMembers();
13196
+ this.schedulePersist();
13182
13197
  return true;
13183
13198
  }
13184
13199
  return false;
@@ -13201,7 +13216,7 @@ var GroupChatModule = class {
13201
13216
  if (eventId) {
13202
13217
  this.deleteMessageFromMemory(groupId, messageId);
13203
13218
  this.deps.emitEvent("groupchat:updated", {});
13204
- this.persistMessages();
13219
+ this.schedulePersist();
13205
13220
  return true;
13206
13221
  }
13207
13222
  return false;
@@ -13281,7 +13296,7 @@ var GroupChatModule = class {
13281
13296
  * or 0 if no messages exist. Used to set `since` on subscriptions so the relay
13282
13297
  * only sends events we don't already have.
13283
13298
  */
13284
- getLatestMessageTimestamp(groupIds) {
13299
+ getLatestKnownTimestamp(groupIds) {
13285
13300
  let latest = 0;
13286
13301
  for (const gid of groupIds) {
13287
13302
  const msgs = this.messages.get(gid);
@@ -13362,7 +13377,7 @@ var GroupChatModule = class {
13362
13377
  });
13363
13378
  }
13364
13379
  }
13365
- this.persistMembers();
13380
+ this.schedulePersist();
13366
13381
  }
13367
13382
  async fetchGroupMembersInternal(groupId) {
13368
13383
  if (!this.client) return [];
@@ -13489,8 +13504,11 @@ var GroupChatModule = class {
13489
13504
  addProcessedEventId(eventId) {
13490
13505
  this.processedEventIds.add(eventId);
13491
13506
  if (this.processedEventIds.size > 1e4) {
13492
- const arr = Array.from(this.processedEventIds);
13493
- this.processedEventIds = new Set(arr.slice(arr.length - 1e4));
13507
+ let toDelete = 5e3;
13508
+ for (const id of this.processedEventIds) {
13509
+ if (toDelete-- <= 0) break;
13510
+ this.processedEventIds.delete(id);
13511
+ }
13494
13512
  }
13495
13513
  }
13496
13514
  // ===========================================================================
@@ -13627,6 +13645,7 @@ var GroupChatModule = class {
13627
13645
  let name = "Unnamed Group";
13628
13646
  let description;
13629
13647
  let picture;
13648
+ let memberCount;
13630
13649
  let isPrivate = false;
13631
13650
  let writeRestricted = false;
13632
13651
  if (event.content && event.content.trim()) {
@@ -13647,6 +13666,7 @@ var GroupChatModule = class {
13647
13666
  if (tag[0] === "private") isPrivate = true;
13648
13667
  if (tag[0] === "public" && tag[1] === "false") isPrivate = true;
13649
13668
  if (tag[0] === "write-restricted") writeRestricted = true;
13669
+ if (tag[0] === "member_count" && tag[1]) memberCount = parseInt(tag[1], 10) || void 0;
13650
13670
  }
13651
13671
  return {
13652
13672
  id: groupId,
@@ -13654,6 +13674,7 @@ var GroupChatModule = class {
13654
13674
  name,
13655
13675
  description,
13656
13676
  picture,
13677
+ memberCount,
13657
13678
  visibility: isPrivate ? GroupVisibility.PRIVATE : GroupVisibility.PUBLIC,
13658
13679
  writeRestricted: writeRestricted || void 0,
13659
13680
  createdAt: event.created_at * 1e3