@peerbit/pubsub 5.1.0 → 5.1.1

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,83 @@ 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
+ }
2167
+
2091
2168
  if (pubsubMessage instanceof PubSubData) {
2092
2169
  this.dispatchEvent(
2093
2170
  new CustomEvent("data", {
@@ -2340,12 +2417,14 @@ export class TopicControlPlane
2340
2417
  return super.onDataMessage(from, stream, message, seenBefore);
2341
2418
  }
2342
2419
 
2343
- // DirectStream only supports targeted pubsub data and a small set of utility
2344
- // messages. All membership/control traffic is shard-only.
2420
+ // DirectStream supports targeted pubsub data plus targeted subscriber snapshot
2421
+ // traffic used by Program.waitFor() when a peer is already connected directly.
2345
2422
  if (
2346
2423
  !(pubsubMessage instanceof PubSubData) &&
2347
2424
  !(pubsubMessage instanceof TopicRootCandidates) &&
2348
2425
  !(pubsubMessage instanceof TopicRootQuery) &&
2426
+ !(pubsubMessage instanceof GetSubscribers) &&
2427
+ !(pubsubMessage instanceof Subscribe) &&
2349
2428
  !(pubsubMessage instanceof TopicRootQueryResponse)
2350
2429
  ) {
2351
2430
  return true;
@@ -2376,6 +2455,7 @@ export class TopicControlPlane
2376
2455
  await this.processDirectPubSubMessage({
2377
2456
  pubsubMessage,
2378
2457
  message,
2458
+ from,
2379
2459
  stream,
2380
2460
  });
2381
2461
  }