@peerbit/pubsub 5.1.0 → 5.1.2

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/src/index.ts CHANGED
@@ -28,7 +28,6 @@ import {
28
28
  type DirectStreamComponents,
29
29
  type DirectStreamOptions,
30
30
  type PeerStreams,
31
- dontThrowIfDeliveryError,
32
31
  } from "@peerbit/stream";
33
32
  import {
34
33
  AcknowledgeAnyWhere,
@@ -687,9 +686,7 @@ export class TopicControlPlane
687
686
  priority: 1,
688
687
  skipRecipientValidation: true,
689
688
  } as any);
690
- await this.publishMessage(this.publicKey, embedded, streams).catch(
691
- dontThrowIfDeliveryError,
692
- );
689
+ await this.publishMessageMaybe(this.publicKey, embedded, streams);
693
690
  }
694
691
 
695
692
  private mergeAutoTopicRootCandidatesFromPeer(candidates: string[]): boolean {
@@ -1559,7 +1556,7 @@ export class TopicControlPlane
1559
1556
  await this.ensureFanoutChannel(shardTopic, { ephemeral: true });
1560
1557
  const st = this.fanoutChannels.get(shardTopic);
1561
1558
  if (st) {
1562
- void st.channel.publish(toUint8Array(embedded.bytes())).catch(() => {});
1559
+ void st.channel.publishMaybe(toUint8Array(embedded.bytes()));
1563
1560
  this.touchFanoutChannel(shardTopic);
1564
1561
  }
1565
1562
  } catch {
@@ -1589,7 +1586,7 @@ export class TopicControlPlane
1589
1586
  await this.ensureFanoutChannel(shardTopic, { ephemeral: true });
1590
1587
  const st = this.fanoutChannels.get(shardTopic);
1591
1588
  if (st) {
1592
- void st.channel.publish(toUint8Array(embedded.bytes())).catch(() => {});
1589
+ void st.channel.publishMaybe(toUint8Array(embedded.bytes()));
1593
1590
  this.touchFanoutChannel(shardTopic);
1594
1591
  }
1595
1592
  } catch {
@@ -1672,10 +1669,16 @@ export class TopicControlPlane
1672
1669
 
1673
1670
  await Promise.all(
1674
1671
  [...byShard.entries()].map(async ([shardTopic, userTopics]) => {
1672
+ const msg = new GetSubscribers({ topics: userTopics });
1673
+ const directPeer = to ? this.peers.get(to.hashcode()) : undefined;
1674
+ if (directPeer) {
1675
+ await this.sendDirectControlMessage(directPeer, msg);
1676
+ return;
1677
+ }
1678
+
1675
1679
  const persistent = (this.shardRefCounts.get(shardTopic) ?? 0) > 0;
1676
1680
  await this.ensureFanoutChannel(shardTopic, { ephemeral: !persistent });
1677
1681
 
1678
- const msg = new GetSubscribers({ topics: userTopics });
1679
1682
  const embedded = await this.createMessage(toUint8Array(msg.bytes()), {
1680
1683
  mode: new AnyWhere(),
1681
1684
  priority: 1,
@@ -1693,7 +1696,7 @@ export class TopicControlPlane
1693
1696
  timeoutMs: 5_000,
1694
1697
  });
1695
1698
  } catch {
1696
- await st.channel.publish(payload);
1699
+ await st.channel.publishMaybe(payload);
1697
1700
  }
1698
1701
  } else {
1699
1702
  await st.channel.publish(payload);
@@ -2048,19 +2051,16 @@ export class TopicControlPlane
2048
2051
  } catch {
2049
2052
  // ignore and fall back
2050
2053
  }
2051
- try {
2052
- await st.channel.publish(payload);
2053
- } catch {
2054
- // ignore
2055
- }
2054
+ await st.channel.publishMaybe(payload);
2056
2055
  }
2057
2056
 
2058
2057
  private async processDirectPubSubMessage(input: {
2059
2058
  pubsubMessage: PubSubMessage;
2060
2059
  message: DataMessage;
2060
+ from: PublicSignKey;
2061
2061
  stream: PeerStreams;
2062
2062
  }): Promise<void> {
2063
- const { pubsubMessage, message, stream } = input;
2063
+ const { pubsubMessage, message, from, stream } = input;
2064
2064
 
2065
2065
  if (pubsubMessage instanceof TopicRootCandidates) {
2066
2066
  // Used only to converge deterministic shard-root candidates in auto mode.
@@ -2088,6 +2088,82 @@ export class TopicControlPlane
2088
2088
  return;
2089
2089
  }
2090
2090
 
2091
+ if (pubsubMessage instanceof Subscribe) {
2092
+ const sender = from;
2093
+ const senderKey = sender.hashcode();
2094
+ const relevantTopics = pubsubMessage.topics.filter((t) =>
2095
+ this.isTrackedTopic(t),
2096
+ );
2097
+
2098
+ if (
2099
+ relevantTopics.length > 0 &&
2100
+ this.subscriptionMessageIsLatest(message, pubsubMessage, relevantTopics)
2101
+ ) {
2102
+ const changed: string[] = [];
2103
+ for (const topic of relevantTopics) {
2104
+ const peers = this.topics.get(topic);
2105
+ if (!peers) continue;
2106
+ this.initializePeer(sender);
2107
+
2108
+ const existing = peers.get(senderKey);
2109
+ if (!existing || existing.session < message.header.session) {
2110
+ peers.delete(senderKey);
2111
+ peers.set(
2112
+ senderKey,
2113
+ new SubscriptionData({
2114
+ session: message.header.session,
2115
+ timestamp: message.header.timestamp,
2116
+ publicKey: sender,
2117
+ }),
2118
+ );
2119
+ changed.push(topic);
2120
+ } else {
2121
+ peers.delete(senderKey);
2122
+ peers.set(senderKey, existing);
2123
+ }
2124
+
2125
+ if (!existing) {
2126
+ this.peerToTopic.get(senderKey)!.add(topic);
2127
+ }
2128
+ this.pruneTopicSubscribers(topic);
2129
+ }
2130
+
2131
+ if (changed.length > 0) {
2132
+ this.dispatchEvent(
2133
+ new CustomEvent<SubscriptionEvent>("subscribe", {
2134
+ detail: new SubscriptionEvent(sender, changed),
2135
+ }),
2136
+ );
2137
+ }
2138
+ }
2139
+
2140
+ if (pubsubMessage.requestSubscribers) {
2141
+ const overlap = this.getSubscriptionOverlap(pubsubMessage.topics);
2142
+ if (overlap.length > 0) {
2143
+ await this.sendDirectControlMessage(
2144
+ stream,
2145
+ new Subscribe({
2146
+ topics: overlap,
2147
+ requestSubscribers: false,
2148
+ }),
2149
+ );
2150
+ }
2151
+ }
2152
+ return;
2153
+ }
2154
+
2155
+ if (pubsubMessage instanceof GetSubscribers) {
2156
+ const overlap = this.getSubscriptionOverlap(pubsubMessage.topics);
2157
+ if (overlap.length === 0) return;
2158
+ await this.sendDirectControlMessage(
2159
+ stream,
2160
+ new Subscribe({
2161
+ topics: overlap,
2162
+ requestSubscribers: false,
2163
+ }),
2164
+ );
2165
+ return;
2166
+ }
2091
2167
  if (pubsubMessage instanceof PubSubData) {
2092
2168
  this.dispatchEvent(
2093
2169
  new CustomEvent("data", {
@@ -2340,12 +2416,14 @@ export class TopicControlPlane
2340
2416
  return super.onDataMessage(from, stream, message, seenBefore);
2341
2417
  }
2342
2418
 
2343
- // DirectStream only supports targeted pubsub data and a small set of utility
2344
- // messages. All membership/control traffic is shard-only.
2419
+ // DirectStream supports targeted pubsub data plus a small set of control
2420
+ // messages used for topic-root discovery and direct subscriber snapshots.
2345
2421
  if (
2346
2422
  !(pubsubMessage instanceof PubSubData) &&
2347
2423
  !(pubsubMessage instanceof TopicRootCandidates) &&
2348
2424
  !(pubsubMessage instanceof TopicRootQuery) &&
2425
+ !(pubsubMessage instanceof GetSubscribers) &&
2426
+ !(pubsubMessage instanceof Subscribe) &&
2349
2427
  !(pubsubMessage instanceof TopicRootQueryResponse)
2350
2428
  ) {
2351
2429
  return true;
@@ -2376,6 +2454,7 @@ export class TopicControlPlane
2376
2454
  await this.processDirectPubSubMessage({
2377
2455
  pubsubMessage,
2378
2456
  message,
2457
+ from,
2379
2458
  stream,
2380
2459
  });
2381
2460
  }