@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.
@@ -3330,7 +3330,7 @@ declare class GroupChatModule {
3330
3330
  * or 0 if no messages exist. Used to set `since` on subscriptions so the relay
3331
3331
  * only sends events we don't already have.
3332
3332
  */
3333
- private getLatestMessageTimestamp;
3333
+ private getLatestKnownTimestamp;
3334
3334
  private fetchRelayAdmins;
3335
3335
  private doFetchRelayAdmins;
3336
3336
  private fetchGroupMetadataInternal;
@@ -3330,7 +3330,7 @@ declare class GroupChatModule {
3330
3330
  * or 0 if no messages exist. Used to set `since` on subscriptions so the relay
3331
3331
  * only sends events we don't already have.
3332
3332
  */
3333
- private getLatestMessageTimestamp;
3333
+ private getLatestKnownTimestamp;
3334
3334
  private fetchRelayAdmins;
3335
3335
  private doFetchRelayAdmins;
3336
3336
  private fetchGroupMetadataInternal;
@@ -515,6 +515,33 @@ function waitForConnection() {
515
515
  connectionCallbacks.push(callback);
516
516
  });
517
517
  }
518
+ function startPingTimer() {
519
+ stopPingTimer();
520
+ pingTimer = setInterval(() => {
521
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
522
+ try {
523
+ const id = ++requestId;
524
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id, method: "server.ping", params: [] }));
525
+ pending[id] = {
526
+ resolve: () => {
527
+ },
528
+ reject: () => {
529
+ }
530
+ };
531
+ const timeoutId = setTimeout(() => {
532
+ delete pending[id];
533
+ }, 1e4);
534
+ pending[id].timeoutId = timeoutId;
535
+ } catch {
536
+ }
537
+ }, PING_INTERVAL);
538
+ }
539
+ function stopPingTimer() {
540
+ if (pingTimer) {
541
+ clearInterval(pingTimer);
542
+ pingTimer = null;
543
+ }
544
+ }
518
545
  function connect(endpoint = DEFAULT_ENDPOINT) {
519
546
  if (isConnected) {
520
547
  return Promise.resolve();
@@ -537,6 +564,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
537
564
  isConnected = true;
538
565
  isConnecting = false;
539
566
  reconnectAttempts = 0;
567
+ startPingTimer();
540
568
  hasResolved = true;
541
569
  resolve();
542
570
  connectionCallbacks.forEach((cb) => {
@@ -548,6 +576,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
548
576
  ws.onclose = () => {
549
577
  isConnected = false;
550
578
  isBlockSubscribed = false;
579
+ stopPingTimer();
551
580
  Object.values(pending).forEach((req) => {
552
581
  if (req.timeoutId) clearTimeout(req.timeoutId);
553
582
  req.reject(new Error("WebSocket connection closed"));
@@ -730,6 +759,7 @@ async function getCurrentBlockHeight() {
730
759
  }
731
760
  }
732
761
  function disconnect() {
762
+ stopPingTimer();
733
763
  if (ws) {
734
764
  intentionalClose = true;
735
765
  ws.close();
@@ -750,7 +780,7 @@ function disconnect() {
750
780
  blockSubscribers.length = 0;
751
781
  lastBlockHeader = null;
752
782
  }
753
- 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;
783
+ 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;
754
784
  var init_network = __esm({
755
785
  "l1/network.ts"() {
756
786
  "use strict";
@@ -766,6 +796,7 @@ var init_network = __esm({
766
796
  reconnectAttempts = 0;
767
797
  isBlockSubscribed = false;
768
798
  lastBlockHeader = null;
799
+ pingTimer = null;
769
800
  pending = {};
770
801
  blockSubscribers = [];
771
802
  connectionCallbacks = [];
@@ -774,6 +805,7 @@ var init_network = __esm({
774
805
  MAX_DELAY = 6e4;
775
806
  RPC_TIMEOUT = 3e4;
776
807
  CONNECTION_TIMEOUT = 3e4;
808
+ PING_INTERVAL = 3e4;
777
809
  }
778
810
  });
779
811
 
@@ -2434,9 +2466,9 @@ var NostrTransportProvider = class _NostrTransportProvider {
2434
2466
  if (subId) {
2435
2467
  this.nostrClient?.unsubscribe(subId);
2436
2468
  }
2437
- logger.warn("Nostr", `queryEvents timed out after 5s, returning ${events.length} event(s)`, { kinds: filterObj.kinds, limit: filterObj.limit });
2469
+ logger.warn("Nostr", `queryEvents timed out after 15s, returning ${events.length} event(s)`, { kinds: filterObj.kinds, limit: filterObj.limit });
2438
2470
  resolve(events);
2439
- }, 5e3);
2471
+ }, 15e3);
2440
2472
  const subId = this.nostrClient.subscribe(filter, {
2441
2473
  onEvent: (event) => {
2442
2474
  events.push({
@@ -12114,6 +12146,15 @@ var GroupChatModule = class {
12114
12146
  }
12115
12147
  destroy() {
12116
12148
  this.destroyConnection();
12149
+ if (this.persistTimer) {
12150
+ clearTimeout(this.persistTimer);
12151
+ this.persistTimer = null;
12152
+ if (this.deps) {
12153
+ this.doPersistAll().catch(
12154
+ (err) => logger.debug("GroupChat", "Persist on destroy failed", err)
12155
+ );
12156
+ }
12157
+ }
12117
12158
  this.groups.clear();
12118
12159
  this.messages.clear();
12119
12160
  this.members.clear();
@@ -12122,10 +12163,7 @@ var GroupChatModule = class {
12122
12163
  this.messageHandlers.clear();
12123
12164
  this.relayAdminPubkeys = null;
12124
12165
  this.relayAdminFetchPromise = null;
12125
- if (this.persistTimer) {
12126
- clearTimeout(this.persistTimer);
12127
- this.persistTimer = null;
12128
- }
12166
+ this.persistPromise = null;
12129
12167
  this.deps = null;
12130
12168
  }
12131
12169
  destroyConnection() {
@@ -12250,12 +12288,12 @@ var GroupChatModule = class {
12250
12288
  if (!this.client) return;
12251
12289
  const groupIds = Array.from(this.groups.keys());
12252
12290
  if (groupIds.length === 0) return;
12253
- const latestTimestamp = this.getLatestMessageTimestamp(groupIds);
12291
+ const sinceTimestamp = this.getLatestKnownTimestamp(groupIds);
12254
12292
  this.trackSubscription(
12255
12293
  createNip29Filter({
12256
12294
  kinds: [NIP29_KINDS.CHAT_MESSAGE, NIP29_KINDS.THREAD_ROOT, NIP29_KINDS.THREAD_REPLY],
12257
12295
  "#h": groupIds,
12258
- ...latestTimestamp ? { since: latestTimestamp } : {}
12296
+ ...sinceTimestamp ? { since: sinceTimestamp } : {}
12259
12297
  }),
12260
12298
  { onEvent: (event) => this.handleGroupEvent(event) }
12261
12299
  );
@@ -12276,12 +12314,12 @@ var GroupChatModule = class {
12276
12314
  }
12277
12315
  subscribeToGroup(groupId) {
12278
12316
  if (!this.client) return;
12279
- const latestTimestamp = this.getLatestMessageTimestamp([groupId]);
12317
+ const sinceTimestamp = this.getLatestKnownTimestamp([groupId]);
12280
12318
  this.trackSubscription(
12281
12319
  createNip29Filter({
12282
12320
  kinds: [NIP29_KINDS.CHAT_MESSAGE, NIP29_KINDS.THREAD_ROOT, NIP29_KINDS.THREAD_REPLY],
12283
12321
  "#h": [groupId],
12284
- ...latestTimestamp ? { since: latestTimestamp } : {}
12322
+ ...sinceTimestamp ? { since: sinceTimestamp } : {}
12285
12323
  }),
12286
12324
  { onEvent: (event) => this.handleGroupEvent(event) }
12287
12325
  );
@@ -12355,7 +12393,7 @@ var GroupChatModule = class {
12355
12393
  }
12356
12394
  group.updatedAt = event.created_at * 1e3;
12357
12395
  this.groups.set(groupId, group);
12358
- this.persistGroups();
12396
+ this.schedulePersist();
12359
12397
  } else if (event.kind === NIP29_KINDS.GROUP_MEMBERS) {
12360
12398
  this.updateMembersFromEvent(groupId, event);
12361
12399
  } else if (event.kind === NIP29_KINDS.GROUP_ADMINS) {
@@ -12376,7 +12414,7 @@ var GroupChatModule = class {
12376
12414
  }
12377
12415
  }
12378
12416
  this.deps.emitEvent("groupchat:updated", {});
12379
- this.persistMessages();
12417
+ this.schedulePersist();
12380
12418
  } else if (event.kind === NIP29_KINDS.REMOVE_USER) {
12381
12419
  if (this.processedEventIds.has(event.id)) return;
12382
12420
  const eventTimestampMs = event.created_at * 1e3;
@@ -12437,7 +12475,7 @@ var GroupChatModule = class {
12437
12475
  };
12438
12476
  this.saveMemberToMemory(member);
12439
12477
  }
12440
- this.persistMembers();
12478
+ this.schedulePersist();
12441
12479
  }
12442
12480
  updateAdminsFromEvent(groupId, event) {
12443
12481
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -12457,7 +12495,7 @@ var GroupChatModule = class {
12457
12495
  });
12458
12496
  }
12459
12497
  }
12460
- this.persistMembers();
12498
+ this.schedulePersist();
12461
12499
  }
12462
12500
  // ===========================================================================
12463
12501
  // Group Membership Restoration
@@ -12468,13 +12506,11 @@ var GroupChatModule = class {
12468
12506
  if (!myPubkey) return [];
12469
12507
  const groupIdsWithMembership = /* @__PURE__ */ new Set();
12470
12508
  await this.oneshotSubscription(
12471
- new Filter3({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
12509
+ createNip29Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#p": [myPubkey] }),
12472
12510
  {
12473
12511
  onEvent: (event) => {
12474
12512
  const groupId = this.getGroupIdFromMetadataEvent(event);
12475
- if (!groupId) return;
12476
- const pTags = event.tags.filter((t) => t[0] === "p");
12477
- if (pTags.some((tag) => tag[1] === myPubkey)) {
12513
+ if (groupId) {
12478
12514
  groupIdsWithMembership.add(groupId);
12479
12515
  }
12480
12516
  },
@@ -12486,22 +12522,24 @@ var GroupChatModule = class {
12486
12522
  );
12487
12523
  if (groupIdsWithMembership.size === 0) return [];
12488
12524
  const restoredGroups = [];
12489
- for (const groupId of groupIdsWithMembership) {
12490
- if (this.groups.has(groupId)) continue;
12491
- try {
12492
- const group = await this.fetchGroupMetadataInternal(groupId);
12493
- if (group) {
12494
- this.groups.set(groupId, group);
12495
- restoredGroups.push(group);
12496
- await Promise.all([
12497
- this.fetchAndSaveMembers(groupId),
12498
- this.fetchMessages(groupId)
12499
- ]);
12525
+ await Promise.all(
12526
+ Array.from(groupIdsWithMembership).map(async (groupId) => {
12527
+ if (this.groups.has(groupId)) return;
12528
+ try {
12529
+ const group = await this.fetchGroupMetadataInternal(groupId);
12530
+ if (group) {
12531
+ this.groups.set(groupId, group);
12532
+ restoredGroups.push(group);
12533
+ await Promise.all([
12534
+ this.fetchAndSaveMembers(groupId),
12535
+ this.fetchMessages(groupId)
12536
+ ]);
12537
+ }
12538
+ } catch (error) {
12539
+ logger.warn("GroupChat", "Failed to restore group", groupId, error);
12500
12540
  }
12501
- } catch (error) {
12502
- logger.warn("GroupChat", "Failed to restore group", groupId, error);
12503
- }
12504
- }
12541
+ })
12542
+ );
12505
12543
  if (restoredGroups.length > 0) {
12506
12544
  await this.subscribeToJoinedGroups();
12507
12545
  this.deps.emitEvent("groupchat:updated", {});
@@ -12516,47 +12554,24 @@ var GroupChatModule = class {
12516
12554
  await this.ensureConnected();
12517
12555
  if (!this.client) return [];
12518
12556
  const groupsMap = /* @__PURE__ */ new Map();
12519
- const memberCountsMap = /* @__PURE__ */ new Map();
12520
- await Promise.all([
12521
- this.oneshotSubscription(
12522
- new Filter3({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
12523
- {
12524
- onEvent: (event) => {
12525
- const group = this.parseGroupMetadata(event);
12526
- if (group && group.visibility === GroupVisibility.PUBLIC) {
12527
- const existing = groupsMap.get(group.id);
12528
- if (!existing || group.createdAt > existing.createdAt) {
12529
- groupsMap.set(group.id, group);
12530
- }
12531
- }
12532
- },
12533
- onComplete: () => {
12534
- },
12535
- timeoutMs: 1e4,
12536
- timeoutLabel: "fetchAvailableGroups(metadata)"
12537
- }
12538
- ),
12539
- this.oneshotSubscription(
12540
- new Filter3({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
12541
- {
12542
- onEvent: (event) => {
12543
- const groupId = this.getGroupIdFromMetadataEvent(event);
12544
- if (groupId) {
12545
- const pTags = event.tags.filter((t) => t[0] === "p");
12546
- memberCountsMap.set(groupId, pTags.length);
12557
+ await this.oneshotSubscription(
12558
+ new Filter3({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
12559
+ {
12560
+ onEvent: (event) => {
12561
+ const group = this.parseGroupMetadata(event);
12562
+ if (group && group.visibility === GroupVisibility.PUBLIC) {
12563
+ const existing = groupsMap.get(group.id);
12564
+ if (!existing || group.createdAt > existing.createdAt) {
12565
+ groupsMap.set(group.id, group);
12547
12566
  }
12548
- },
12549
- onComplete: () => {
12550
- },
12551
- timeoutMs: 1e4,
12552
- timeoutLabel: "fetchAvailableGroups(members)"
12553
- }
12554
- )
12555
- ]);
12556
- for (const [groupId, count] of memberCountsMap) {
12557
- const group = groupsMap.get(groupId);
12558
- if (group) group.memberCount = count;
12559
- }
12567
+ }
12568
+ },
12569
+ onComplete: () => {
12570
+ },
12571
+ timeoutMs: 1e4,
12572
+ timeoutLabel: "fetchAvailableGroups(metadata)"
12573
+ }
12574
+ );
12560
12575
  return Array.from(groupsMap.values());
12561
12576
  }
12562
12577
  async joinGroup(groupId, inviteCode) {
@@ -12887,7 +12902,7 @@ var GroupChatModule = class {
12887
12902
  if (group && (group.unreadCount || 0) > 0) {
12888
12903
  group.unreadCount = 0;
12889
12904
  this.groups.set(groupId, group);
12890
- this.persistGroups();
12905
+ this.schedulePersist();
12891
12906
  }
12892
12907
  }
12893
12908
  // ===========================================================================
@@ -12909,7 +12924,7 @@ var GroupChatModule = class {
12909
12924
  if (eventId) {
12910
12925
  this.removeMemberFromMemory(groupId, userPubkey);
12911
12926
  this.deps.emitEvent("groupchat:updated", {});
12912
- this.persistMembers();
12927
+ this.schedulePersist();
12913
12928
  return true;
12914
12929
  }
12915
12930
  return false;
@@ -12932,7 +12947,7 @@ var GroupChatModule = class {
12932
12947
  if (eventId) {
12933
12948
  this.deleteMessageFromMemory(groupId, messageId);
12934
12949
  this.deps.emitEvent("groupchat:updated", {});
12935
- this.persistMessages();
12950
+ this.schedulePersist();
12936
12951
  return true;
12937
12952
  }
12938
12953
  return false;
@@ -13012,7 +13027,7 @@ var GroupChatModule = class {
13012
13027
  * or 0 if no messages exist. Used to set `since` on subscriptions so the relay
13013
13028
  * only sends events we don't already have.
13014
13029
  */
13015
- getLatestMessageTimestamp(groupIds) {
13030
+ getLatestKnownTimestamp(groupIds) {
13016
13031
  let latest = 0;
13017
13032
  for (const gid of groupIds) {
13018
13033
  const msgs = this.messages.get(gid);
@@ -13093,7 +13108,7 @@ var GroupChatModule = class {
13093
13108
  });
13094
13109
  }
13095
13110
  }
13096
- this.persistMembers();
13111
+ this.schedulePersist();
13097
13112
  }
13098
13113
  async fetchGroupMembersInternal(groupId) {
13099
13114
  if (!this.client) return [];
@@ -13220,8 +13235,11 @@ var GroupChatModule = class {
13220
13235
  addProcessedEventId(eventId) {
13221
13236
  this.processedEventIds.add(eventId);
13222
13237
  if (this.processedEventIds.size > 1e4) {
13223
- const arr = Array.from(this.processedEventIds);
13224
- this.processedEventIds = new Set(arr.slice(arr.length - 1e4));
13238
+ let toDelete = 5e3;
13239
+ for (const id of this.processedEventIds) {
13240
+ if (toDelete-- <= 0) break;
13241
+ this.processedEventIds.delete(id);
13242
+ }
13225
13243
  }
13226
13244
  }
13227
13245
  // ===========================================================================
@@ -13358,6 +13376,7 @@ var GroupChatModule = class {
13358
13376
  let name = "Unnamed Group";
13359
13377
  let description;
13360
13378
  let picture;
13379
+ let memberCount;
13361
13380
  let isPrivate = false;
13362
13381
  let writeRestricted = false;
13363
13382
  if (event.content && event.content.trim()) {
@@ -13378,6 +13397,7 @@ var GroupChatModule = class {
13378
13397
  if (tag[0] === "private") isPrivate = true;
13379
13398
  if (tag[0] === "public" && tag[1] === "false") isPrivate = true;
13380
13399
  if (tag[0] === "write-restricted") writeRestricted = true;
13400
+ if (tag[0] === "member_count" && tag[1]) memberCount = parseInt(tag[1], 10) || void 0;
13381
13401
  }
13382
13402
  return {
13383
13403
  id: groupId,
@@ -13385,6 +13405,7 @@ var GroupChatModule = class {
13385
13405
  name,
13386
13406
  description,
13387
13407
  picture,
13408
+ memberCount,
13388
13409
  visibility: isPrivate ? GroupVisibility.PRIVATE : GroupVisibility.PUBLIC,
13389
13410
  writeRestricted: writeRestricted || void 0,
13390
13411
  createdAt: event.created_at * 1e3