@rljson/network 0.0.2 → 0.0.4

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/network.js CHANGED
@@ -1156,6 +1156,8 @@ class ProbeScheduler {
1156
1156
  _wasReachable = /* @__PURE__ */ new Map();
1157
1157
  /** Consecutive failure count per peer, for flap dampening */
1158
1158
  _failCount = /* @__PURE__ */ new Map();
1159
+ /** Peers that have been successfully probed at least once */
1160
+ _everReachable = /* @__PURE__ */ new Set();
1159
1161
  /** Event listeners */
1160
1162
  _listeners = /* @__PURE__ */ new Map();
1161
1163
  /** The probe function — real TCP by default, injectable for tests */
@@ -1208,6 +1210,7 @@ class ProbeScheduler {
1208
1210
  this._probes.clear();
1209
1211
  this._wasReachable.clear();
1210
1212
  this._failCount.clear();
1213
+ this._everReachable.clear();
1211
1214
  this._peers = [];
1212
1215
  }
1213
1216
  /** Whether the scheduler is currently running */
@@ -1250,6 +1253,14 @@ class ProbeScheduler {
1250
1253
  async runOnce() {
1251
1254
  return this._runCycle();
1252
1255
  }
1256
+ /**
1257
+ * Whether a peer has ever been successfully probed.
1258
+ * Used to distinguish "new peer (startup race)" from "crashed peer".
1259
+ * @param nodeId - The peer's nodeId
1260
+ */
1261
+ hasEverBeenReachable(nodeId) {
1262
+ return this._everReachable.has(nodeId);
1263
+ }
1253
1264
  // .........................................................................
1254
1265
  // Events
1255
1266
  // .........................................................................
@@ -1304,6 +1315,7 @@ class ProbeScheduler {
1304
1315
  if (probe.reachable) {
1305
1316
  this._failCount.set(probe.toNodeId, 0);
1306
1317
  this._wasReachable.set(probe.toNodeId, true);
1318
+ this._everReachable.add(probe.toNodeId);
1307
1319
  if (previous !== void 0 && !previous) {
1308
1320
  this._emit("peer-reachable", probe.toNodeId, probe);
1309
1321
  }
@@ -1404,6 +1416,10 @@ class NetworkManager {
1404
1416
  this._peerTable.on("peer-joined", (peer) => {
1405
1417
  this._emit("peer-joined", peer);
1406
1418
  this._probeScheduler.setPeers(this._peerTable.getPeers());
1419
+ const broadcastPeers = this._broadcastLayer.getPeers();
1420
+ if (broadcastPeers.some((bp) => bp.nodeId === peer.nodeId)) {
1421
+ void this._probeScheduler.runOnce();
1422
+ }
1407
1423
  this._recomputeTopology();
1408
1424
  });
1409
1425
  this._peerTable.on("peer-left", (nodeId) => {
@@ -1583,6 +1599,17 @@ class NetworkManager {
1583
1599
  );
1584
1600
  /* v8 ignore else -- @preserve */
1585
1601
  if (result.hubId) {
1602
+ if (result.hubId === this._identity.nodeId && result.reason !== "incumbent") {
1603
+ const selfInfo = this._identity.toNodeInfo();
1604
+ const broadcastPeers = this._broadcastLayer.getPeers();
1605
+ const hasUntestedEarlierPeer = broadcastPeers.some(
1606
+ (p) => !this._probeScheduler.hasEverBeenReachable(p.nodeId) && (p.startedAt < selfInfo.startedAt || /* v8 ignore next -- @preserve */
1607
+ p.startedAt === selfInfo.startedAt && p.nodeId < selfInfo.nodeId)
1608
+ );
1609
+ if (hasUntestedEarlierPeer) {
1610
+ return { hubId: null, formedBy: "election" };
1611
+ }
1612
+ }
1586
1613
  const formedBy = this._broadcastLayer.isActive() && this._broadcastLayer.getPeers().length > 0 ? "broadcast" : "election";
1587
1614
  return { hubId: result.hubId, formedBy };
1588
1615
  }
@@ -50,6 +50,8 @@ export declare class ProbeScheduler {
50
50
  private _wasReachable;
51
51
  /** Consecutive failure count per peer, for flap dampening */
52
52
  private _failCount;
53
+ /** Peers that have been successfully probed at least once */
54
+ private _everReachable;
53
55
  /** Event listeners */
54
56
  private _listeners;
55
57
  /** The probe function — real TCP by default, injectable for tests */
@@ -94,6 +96,12 @@ export declare class ProbeScheduler {
94
96
  * Useful for tests that need immediate results without waiting.
95
97
  */
96
98
  runOnce(): Promise<PeerProbe[]>;
99
+ /**
100
+ * Whether a peer has ever been successfully probed.
101
+ * Used to distinguish "new peer (startup race)" from "crashed peer".
102
+ * @param nodeId - The peer's nodeId
103
+ */
104
+ hasEverBeenReachable(nodeId: NodeId): boolean;
97
105
  /**
98
106
  * Subscribe to scheduler events.
99
107
  * @param event - Event name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/network",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Networking middle layer providing basic advertising and discovery as well as cloud fall backs and basic config.",
5
5
  "homepage": "https://github.com/rljson/network",
6
6
  "bugs": "https://github.com/rljson/network/issues",