@hive-p2p/server 1.0.49 → 1.0.50

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.
@@ -139,6 +139,24 @@ export class OfferManager { // Manages the creation of SDP offers and handling o
139
139
 
140
140
  if (this.verbose > 0) console.error(`transportInstance ERROR => `, error.stack);
141
141
  };
142
+
143
+ bestReadyOffer(maxSent = 0, avoidCloseExpiration = true) { // the most recent offer that is not used
144
+ /** @type {OfferObj | null} */ let readyOffer = null;
145
+ /** @type {string | null} */ let offerHash = null;
146
+ /** @type {number | null} */ let since = null;
147
+ for (const hash in this.offers) {
148
+ const offer = this.offers[hash];
149
+ const { isUsed, sentCounter, signal, timestamp } = offer;
150
+ if (isUsed || sentCounter > maxSent) continue; // already used or already sent at least once
151
+ const createdSince = CLOCK.time - timestamp;
152
+ if (avoidCloseExpiration && createdSince > TRANSPORTS.SDP_OFFER_EXPIRATION / 2) continue; // old, don't spread
153
+ if (since && createdSince > since) continue; // already have a better (more recent) offer
154
+ readyOffer = offer; offerHash = hash; since = createdSince;
155
+ break;
156
+ }
157
+
158
+ return { offerHash, readyOffer, since };
159
+ }
142
160
  /** @param {string} remoteId @param {{type: 'answer', sdp: Record<string, string>}} signal @param {string} offerHash @param {number} timestamp receptionTimestamp */
143
161
  addSignalAnswer(remoteId, signal, offerHash, timestamp) {
144
162
  if (!signal || signal.type !== 'answer' || !offerHash) return; // ignore non-answers or missing offerHash
package/core/node.mjs CHANGED
@@ -140,23 +140,11 @@ export class Node {
140
140
  else this.messager.handleDirectMessage(peerId, d);
141
141
  }
142
142
 
143
- // PUBLIC API
143
+ // PUBLIC API -------------------------------------------------------------
144
144
  /** @returns {string | undefined} */
145
145
  get publicUrl() { return this.services?.publicUrl; }
146
146
  get time() { return CLOCK.time; }
147
147
 
148
- /** Triggered when a new peer connection is established.
149
- * @param {function} callback can use arguments: (peerId:string, direction:string) */
150
- onPeerConnect(callback) { this.peerStore.on('connect', callback); }
151
-
152
- /** Triggered when a new message is received.
153
- * @param {function} callback can use arguments: (senderId:string, data:any) */
154
- onMessageData(callback) { this.messager.on('message', callback); }
155
-
156
- /** Triggered when a new gossip message is received.
157
- * @param {function} callback can use arguments: (senderId:string, topic:string, data:any) */
158
- onGossipData(callback) { this.gossip.on('gossip', callback); }
159
-
160
148
  async start() {
161
149
  await CLOCK.sync(this.verbose);
162
150
  this.started = true;
@@ -170,19 +158,23 @@ export class Node {
170
158
  this.topologist.tryConnectNextBootstrap(); // first shot ASAP
171
159
  return true;
172
160
  }
161
+
173
162
  /** Broadcast a message to all connected peers or to a specified peer
174
163
  * @param {string | Uint8Array | Object} data @param {string} topic @param {string} [targetId] default: broadcast to all
175
164
  * @param {number} [timestamp] default: CLOCK.time @param {number} [HOPS] default: GOSSIP.HOPS[topic] || GOSSIP.HOPS.default */
176
165
  broadcast(data, topic, HOPS) { this.gossip.broadcastToAll(data, topic, HOPS); }
166
+
177
167
  /** @param {string} remoteId @param {string | Uint8Array | Object} data @param {string} type */
178
168
  sendMessage(remoteId, data, type, spread = 1) { this.messager.sendUnicast(remoteId, data, type, spread); }
179
- async tryConnectToPeer(targetId = 'toto', retry = 5) { // TO REFACTO
180
- console.info('FUNCTION DISABLED FOR NOW');
181
- /*if (this.peerStore.connected[targetId]) return; // already connected
169
+
170
+ /** Send a connection request to a peer */
171
+ async tryConnectToPeer(targetId = 'toto', retry = 5) {
172
+ if (this.peerStore.connected[targetId]) return; // already connected
182
173
  do {
183
- if (this.peerStore.offerManager.readyOffer) break;
184
- else await new Promise(r => setTimeout(r, 1000)); // build in progress...
185
- } while (retry-- > 0);*/
174
+ const { offerHash, readyOffer } = this.peerStore.offerManager.bestReadyOffer(100, false);
175
+ if (!offerHash || !readyOffer) await new Promise(r => setTimeout(r, 1000)); // build in progress...
176
+ else this.messager.sendUnicast(targetId, { signal: readyOffer.signal, offerHash }, 'signal_offer', 1);
177
+ } while (retry-- > 0);
186
178
  }
187
179
  destroy() {
188
180
  if (this.enhancerInterval) clearInterval(this.enhancerInterval);
@@ -193,4 +185,30 @@ export class Node {
193
185
  if (this.wsServer) this.wsServer.close();
194
186
  if (this.stunServer) this.stunServer.close();
195
187
  }
188
+
189
+ // HANDLERS REGISTRATION --------------------------------------------------
190
+
191
+ /** Triggered when a new peer connection is established.
192
+ * @param {function} callback can use arguments: (peerId:string, direction:string) */
193
+ onPeerConnect(callback) { this.peerStore.on('connect', callback); }
194
+
195
+ /** Triggered when a peer connection is closed.
196
+ * @param {function} callback can use arguments: (peerId:string, direction:string) */
197
+ onPeerDisconnect(callback) { this.peerStore.on('disconnect', callback); }
198
+
199
+ /** Triggered when a new message is received.
200
+ * @param {function} callback can use arguments: (senderId:string, data:any) */
201
+ onMessageData(callback) { this.messager.on('message', callback); }
202
+
203
+ /** Triggered when a new gossip message is received.
204
+ * @param {function} callback can use arguments: (senderId:string, topic:string, data:any) */
205
+ onGossipData(callback) { this.gossip.on('gossip', callback); }
206
+
207
+ /** Triggered when a new signal offer is received from another peer.
208
+ * @param {function} callback can use arguments: (senderId:string, data:SignalData) */
209
+ onSignalOffer(callback) { this.messager.on('signal_offer', callback); this.gossip.on('signal_offer', callback); }
210
+
211
+ /** Triggered when a new signal answer is received from another peer.
212
+ * @param {function} callback can use arguments: (senderId:string, data:SignalData) */
213
+ onSignalAnswer(callback) { this.messager.on('signal_answer', callback); }
196
214
  }
@@ -59,6 +59,7 @@ export class Topologist {
59
59
  this.TWICE_TARGET = this.NEIGHBORS_TARGET * 2;
60
60
  }
61
61
 
62
+ automation = { incomingOffer: true, incomingAnswer: true, spreadOffers: true, connectBootstraps: true };
62
63
  phase = 0; nextBootstrapIndex = 0;
63
64
  maxBonus = NODE.CONNECTION_UPGRADE_TIMEOUT * .2; // 20% of 15sec: 3sec max
64
65
  get isPublicNode() { return this.services?.publicUrl ? true : false; }
@@ -97,6 +98,7 @@ export class Topologist {
97
98
  if (isTooMany || connected[senderId]) return;
98
99
 
99
100
  if (signal.type === 'answer') { // ANSWER SHORT CIRCUIT => Rich should connect poor, and poor should connect rich.
101
+ if (!this.automation.incomingAnswer) return;
100
102
  if (this.peerStore.addConnectingPeer(senderId, signal, offerHash) !== true) return;
101
103
  const delta = Math.abs(nonPublicNeighborsCount - this.#getOverlap(senderId).nonPublicCount);
102
104
  const bonusPerDeltaPoint = this.maxBonus / this.NEIGHBORS_TARGET; // from 0 to maxBonus
@@ -105,6 +107,7 @@ export class Topologist {
105
107
  }
106
108
 
107
109
  // OFFER
110
+ if (!this.automation.incomingOffer) return;
108
111
  if (nonPublicNeighborsCount > this.TWICE_TARGET) return; // we are over connected, ignore the offer
109
112
  const { overlap, nonPublicCount } = this.#getOverlap(senderId);
110
113
  if (nonPublicCount > this.TWICE_TARGET) return; // the sender is over connected, ignore the offer
@@ -114,6 +117,7 @@ export class Topologist {
114
117
  this.offersQueue.pushSortTrim(offerItem);
115
118
  }
116
119
  tryConnectNextBootstrap(neighborsCount = 0, nonPublicNeighborsCount = 0) {
120
+ if (!this.automation.connectBootstraps) return;
117
121
  if (this.bootstraps.length === 0) return;
118
122
  const publicConnectedCount = neighborsCount - nonPublicNeighborsCount;
119
123
  let connectingCount = 0;
@@ -197,23 +201,14 @@ export class Topologist {
197
201
  };
198
202
  }
199
203
  #tryToSpreadSDP(nonPublicNeighborsCount = 0, isHalfReached = false) { // LOOP TO SELECT ONE UNSEND READY OFFER AND BROADCAST IT
204
+ if (!this.automation.spreadOffers) return;
200
205
  if (!this.peerStore.neighborsList.length) return; // no neighbors, no need to spread offers
201
206
  // LIMIT OFFER SPREADING IF WE ARE CONNECTING TO MANY PEERS, LOWER GOSSIP TRAFFIC
202
207
  const connectingCount = Object.keys(this.peerStore.connecting).length;
203
208
  const ingPlusEd = connectingCount + nonPublicNeighborsCount;
204
209
 
205
210
  // SELECT BEST READY OFFER BASED ON TIMESTAMP
206
- let [ offerHash, readyOffer, since ] = [ null, null, null ];
207
- for (const hash in this.peerStore.offerManager.offers) {
208
- const offer = this.peerStore.offerManager.offers[hash];
209
- const { isUsed, sentCounter, signal, timestamp } = offer;
210
- if (isUsed || sentCounter > 0) continue; // already used or already sent at least once
211
- const createdSince = CLOCK.time - timestamp;
212
- if (createdSince > TRANSPORTS.SDP_OFFER_EXPIRATION / 2) continue; // old, don't spread
213
- if (since && createdSince > since) continue; // already have a better (more recent) offer
214
- readyOffer = offer; offerHash = hash; since = createdSince;
215
- break;
216
- }
211
+ const { offerHash, readyOffer } = this.peerStore.offerManager.bestReadyOffer(0, true);
217
212
  if (!offerHash || !readyOffer) return; // no ready offer to spread
218
213
 
219
214
  // IF WE ARE CONNECTED TO LESS 2 (WRTC) AND NOT TO MUCH CONNECTING, WE CAN BROADCAST IT TO ALL
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hive-p2p/server",
3
- "version": "1.0.49",
3
+ "version": "1.0.50",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },