@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.
- package/core/ice-offer-manager.mjs +18 -0
- package/core/node.mjs +37 -19
- package/core/topologist.mjs +6 -11
- package/package.json +1 -1
|
@@ -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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
}
|
package/core/topologist.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|