@hive-p2p/browser 1.0.89 → 1.0.90
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/crypto-codex.mjs +6 -1
- package/core/gossip.mjs +5 -3
- package/core/node.mjs +24 -24
- package/core/topologist.mjs +5 -3
- package/core/unicast.mjs +9 -5
- package/hive-p2p.min.js +3 -3
- package/package.json +1 -1
- package/rendering/visualizer.html +1 -0
- package/rendering/visualizer.mjs +6 -2
package/core/crypto-codex.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { concatBytes } from '@noble/ciphers/utils.js';
|
|
|
9
9
|
export class CryptoCodex {
|
|
10
10
|
argon2 = new Argon2Unified();
|
|
11
11
|
converter = new Converter();
|
|
12
|
-
AVOID_CRYPTO =
|
|
12
|
+
AVOID_CRYPTO = true; // AVOID CRYPTO OPERATIONS (default) => auto-enable when generate() is called, but can be set to true to disable crypto in any case (e.g. for testing with string ids)
|
|
13
13
|
verbose = NODE.DEFAULT_VERBOSE;
|
|
14
14
|
/** @type {string} */ id;
|
|
15
15
|
/** @type {Uint8Array} */ publicKey;
|
|
@@ -92,6 +92,11 @@ export class CryptoCodex {
|
|
|
92
92
|
const cipher = chacha20poly1305(sharedSecret, nonce);
|
|
93
93
|
const encrypted = cipher.encrypt(data);
|
|
94
94
|
return concatBytes(nonce, encrypted);
|
|
95
|
+
// Vanilla =>
|
|
96
|
+
/*const result = new Uint8Array(nonce.length + encrypted.length);
|
|
97
|
+
result.set(nonce, 0);
|
|
98
|
+
result.set(encrypted, nonce.length);
|
|
99
|
+
return result;*/
|
|
95
100
|
}
|
|
96
101
|
/** @param {Uint8Array} encryptedData @param {Uint8Array} sharedSecret */
|
|
97
102
|
decryptData(encryptedData, sharedSecret) {
|
package/core/gossip.mjs
CHANGED
|
@@ -83,7 +83,7 @@ class DegenerateBloomFilter {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export class Gossip {
|
|
86
|
-
/** @type {Record<string,
|
|
86
|
+
/** @type {Record<string, function(GossipMessage)[]>} */ callbacks = { message_handle: [] };
|
|
87
87
|
id; cryptoCodex; arbiter; peerStore; verbose; bloomFilter;
|
|
88
88
|
|
|
89
89
|
/** @param {string} selfId @param {import('./crypto-codex.mjs').CryptoCodex} cryptoCodex @param {import('./arbiter.mjs').Arbiter} arbiter @param {import('./peer-store.mjs').PeerStore} peerStore */
|
|
@@ -96,7 +96,7 @@ export class Gossip {
|
|
|
96
96
|
this.bloomFilter = new DegenerateBloomFilter(cryptoCodex);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
/** @param {string} callbackType @param {
|
|
99
|
+
/** @param {string} callbackType @param {function(GossipMessage)} callback */
|
|
100
100
|
on(callbackType, callback) {
|
|
101
101
|
if (!this.callbacks[callbackType]) this.callbacks[callbackType] = [callback];
|
|
102
102
|
else this.callbacks[callbackType].push(callback);
|
|
@@ -131,6 +131,7 @@ export class Gossip {
|
|
|
131
131
|
|
|
132
132
|
const message = this.cryptoCodex.readGossipMessage(serialized);
|
|
133
133
|
if (!message) return this.arbiter.countPeerAction(from, 'WRONG_SERIALIZATION');
|
|
134
|
+
|
|
134
135
|
const isOk = await this.arbiter.digestMessage(from, message, serialized);
|
|
135
136
|
if (!isOk) return; // invalid message or from banished peer
|
|
136
137
|
if (this.arbiter.isBanished(from)) return; // ignore messages from banished peers
|
|
@@ -144,7 +145,8 @@ export class Gossip {
|
|
|
144
145
|
else console.log(`(${this.id}) Gossip ${topic} from ${senderId} (by: ${from}): ${data}`);
|
|
145
146
|
|
|
146
147
|
this.peerStore.digestPeerNeighbors(senderId, neighborsList);
|
|
147
|
-
for (const cb of this.callbacks[topic] || []) cb(senderId, data, HOPS, message); // specific topic callback
|
|
148
|
+
//for (const cb of this.callbacks[topic] || []) cb(senderId, data, HOPS, message); // specific topic callback
|
|
149
|
+
for (const cb of this.callbacks[topic] || []) cb(message); // specific topic callback with full message object
|
|
148
150
|
if (HOPS < 1) return; // stop forwarding if HOPS is 0
|
|
149
151
|
|
|
150
152
|
const nCount = this.peerStore.neighborsList.length;
|
package/core/node.mjs
CHANGED
|
@@ -9,6 +9,13 @@ import { Topologist } from './topologist.mjs';
|
|
|
9
9
|
import { CryptoCodex } from './crypto-codex.mjs';
|
|
10
10
|
import { NodeServices } from './node-services.mjs';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import('./unicast.mjs').DirectMessage} DirectMessage
|
|
14
|
+
* @typedef {import('./unicast.mjs').ReroutedDirectMessage} ReroutedDirectMessage
|
|
15
|
+
* @typedef {import('./gossip.mjs').GossipMessage} GossipMessage
|
|
16
|
+
* @typedef {import('./topologist.mjs').SignalData} SignalData
|
|
17
|
+
*/
|
|
18
|
+
|
|
12
19
|
/** Create and start a new PublicNode instance.
|
|
13
20
|
* @param {Object} options
|
|
14
21
|
* @param {string[]} options.bootstraps List of bootstrap nodes used as P2P network entry
|
|
@@ -86,12 +93,12 @@ export class Node {
|
|
|
86
93
|
peerStore.on('data', (peerId, data) => this.#onData(peerId, data));
|
|
87
94
|
|
|
88
95
|
// UNICAST LISTENERS
|
|
89
|
-
messager.on('signal_answer', (
|
|
90
|
-
messager.on('signal_offer', (
|
|
91
|
-
messager.on('privacy', (
|
|
96
|
+
messager.on('signal_answer', (msg) => topologist.handleIncomingSignal(msg.senderId, msg.data));
|
|
97
|
+
messager.on('signal_offer', (msg) => topologist.handleIncomingSignal(msg.senderId, msg.data));
|
|
98
|
+
messager.on('privacy', (msg) => this.#handlePrivacy(msg));
|
|
92
99
|
|
|
93
100
|
// GOSSIP LISTENERS
|
|
94
|
-
gossip.on('signal_offer', (
|
|
101
|
+
gossip.on('signal_offer', (msg) => topologist.handleIncomingSignal(msg.senderId, msg.data));
|
|
95
102
|
|
|
96
103
|
if (verbose > 2) console.log(`Node initialized: ${this.id}`);
|
|
97
104
|
}
|
|
@@ -221,8 +228,13 @@ export class Node {
|
|
|
221
228
|
}
|
|
222
229
|
return p;
|
|
223
230
|
}
|
|
224
|
-
/** @param {
|
|
225
|
-
#handlePrivacy(
|
|
231
|
+
/** @param {DirectMessage} msg */
|
|
232
|
+
#handlePrivacy(msg) {
|
|
233
|
+
const { senderId, data: peerPub } = msg;
|
|
234
|
+
if (!(peerPub instanceof Uint8Array)) {
|
|
235
|
+
this.verbose > 1 ? console.warn(`Received invalid privacy message from ${senderId}, expected peer public key as Uint8Array.`) : null;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
226
238
|
if (this.peerStore.privacy[senderId]?.sharedSecret) return; // already have shared secret
|
|
227
239
|
|
|
228
240
|
if (!this.peerStore.privacy[senderId])
|
|
@@ -256,36 +268,24 @@ export class Node {
|
|
|
256
268
|
}
|
|
257
269
|
|
|
258
270
|
// HANDLERS REGISTRATION --------------------------------------------------
|
|
259
|
-
|
|
260
271
|
/** Triggered when a new peer connection is established.
|
|
261
272
|
* @param {function} callback can use arguments: (peerId:string, direction:string) */
|
|
262
273
|
onPeerConnect(callback) { this.peerStore.on('connect', callback); }
|
|
263
|
-
|
|
264
|
-
/** Triggered when a peer connection is closed.
|
|
265
|
-
* @param {function} callback can use arguments: (peerId:string, direction:string) */
|
|
274
|
+
/** Triggered when a peer connection is closed. @param {function} callback can use arguments: (peerId:string, direction:string) */
|
|
266
275
|
onPeerDisconnect(callback) { this.peerStore.on('disconnect', callback); }
|
|
267
|
-
|
|
268
|
-
/** Triggered when a new message is received.
|
|
269
|
-
* @param {function} callback can use arguments: (senderId:string, data:any) */
|
|
276
|
+
/** Triggered when a new message is received. @param {function(DirectMessage)} callback */
|
|
270
277
|
onMessageData(callback, includesPrivate = true) {
|
|
271
278
|
this.messager.on('message', callback);
|
|
272
279
|
if (includesPrivate) this.messager.on('private_message', callback);
|
|
273
280
|
}
|
|
274
|
-
|
|
275
|
-
/** Triggered when a new private message is received.
|
|
276
|
-
* @param {function} callback can use arguments: (senderId:string, data:any) */
|
|
281
|
+
/** Triggered when a new private message is received. @param {function(DirectMessage)} callback */
|
|
277
282
|
onPrivateMessageData(callback) { this.messager.on('private_message', callback); }
|
|
278
|
-
|
|
279
|
-
/** Triggered when a new gossip message is received.
|
|
280
|
-
* @param {function} callback can use arguments: (senderId:string, data:any, HOPS:number) */
|
|
283
|
+
/** Triggered when a new gossip message is received. @param {function(GossipMessage)} callback */
|
|
281
284
|
onGossipData(callback) { this.gossip.on('gossip', callback); }
|
|
282
|
-
|
|
283
|
-
/** Triggered when a new signal offer is received from another peer.
|
|
284
|
-
* @param {function} callback can use arguments: (senderId:string, data:SignalData) */
|
|
285
|
+
/** Triggered when a new signal offer is received from another peer. @param {function(string, SignalData)} callback can use arguments: (senderId:string, data:SignalData) */
|
|
285
286
|
onSignalOffer(callback) { this.messager.on('signal_offer', callback); this.gossip.on('signal_offer', callback); }
|
|
286
|
-
|
|
287
287
|
/** Triggered when a new signal answer is received from another peer.
|
|
288
|
-
* @param {function} callback can use arguments: (senderId:string, data:SignalData) */
|
|
288
|
+
* @param {function(string, SignalData)} callback can use arguments: (senderId:string, data:SignalData) */
|
|
289
289
|
onSignalAnswer(callback) { this.messager.on('signal_answer', callback); }
|
|
290
290
|
|
|
291
291
|
/** Kick a peer. @param {string} peerId @param {string} [reason] */
|
package/core/topologist.mjs
CHANGED
|
@@ -17,7 +17,9 @@ const { SANDBOX, ICE_CANDIDATE_EMITTER, TEST_WS_EVENT_MANAGER } = SIMULATION.ENA
|
|
|
17
17
|
* @property {number} overlap
|
|
18
18
|
* @property {number} neighborsCount
|
|
19
19
|
* @property {number} timestamp
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
|
+
* @typedef {import('./unicast.mjs').DirectMessage} DirectMessage
|
|
22
|
+
* @typedef {import('./gossip.mjs').GossipMessage} GossipMessage */
|
|
21
23
|
|
|
22
24
|
export class OfferQueue {
|
|
23
25
|
maxOffers = 30;
|
|
@@ -88,8 +90,8 @@ export class Topologist {
|
|
|
88
90
|
if (this.phase === 0) this.tryConnectNextBootstrap(neighborsCount, nonPublicNeighborsCount);
|
|
89
91
|
if (this.phase === 1) this.#tryToSpreadSDP(nonPublicNeighborsCount, isHalfReached);
|
|
90
92
|
}
|
|
91
|
-
/** @param {string}
|
|
92
|
-
handleIncomingSignal(senderId, data
|
|
93
|
+
/** @param {string} senderId @param {SignalData} data */
|
|
94
|
+
handleIncomingSignal(senderId, data) {
|
|
93
95
|
if (this.isPublicNode || !senderId || this.peerStore.isKicked(senderId)) return;
|
|
94
96
|
if (data.signal?.type !== 'offer' && data.signal?.type !== 'answer') return;
|
|
95
97
|
|
package/core/unicast.mjs
CHANGED
|
@@ -14,8 +14,10 @@ export class DirectMessage { // TYPE DEFINITION
|
|
|
14
14
|
signature;
|
|
15
15
|
signatureStart; // position in the serialized message where the signature starts
|
|
16
16
|
expectedEnd; // expected length of the serialized message
|
|
17
|
+
/** @type {string[] | undefined} */ newRoute; // for re-routing patch
|
|
18
|
+
get senderId() { return this.newRoute ? this.newRoute[0] : this.route[0]; }
|
|
17
19
|
|
|
18
|
-
/** @param {string} type @param {number} timestamp @param {string[]} neighborsList @param {string[]} route @param {
|
|
20
|
+
/** @param {string} type @param {number} timestamp @param {string[]} neighborsList @param {string[]} route @param {Uint8Array} pubkey @param {string | Uint8Array | Object} data @param {Uint8Array | undefined} signature @param {number} signatureStart @param {number} expectedEnd */
|
|
19
21
|
constructor(type, timestamp, neighborsList, route, pubkey, data, signature, signatureStart, expectedEnd) {
|
|
20
22
|
this.type = type; this.timestamp = timestamp; this.neighborsList = neighborsList;
|
|
21
23
|
this.route = route; this.pubkey = pubkey; this.data = data; this.signature = signature; this.signatureStart = signatureStart; this.expectedEnd = expectedEnd;
|
|
@@ -30,7 +32,8 @@ export class DirectMessage { // TYPE DEFINITION
|
|
|
30
32
|
traveledRoute.push(route[i]);
|
|
31
33
|
if (route[i] === selfId) { selfPosition = i; break; }
|
|
32
34
|
}
|
|
33
|
-
const senderId = route[0];
|
|
35
|
+
//const senderId = route[0];
|
|
36
|
+
const senderId = this.senderId;
|
|
34
37
|
const targetId = route[route.length - 1];
|
|
35
38
|
const prevId = selfPosition > 0 ? route[selfPosition - 1] : null;
|
|
36
39
|
const nextId = (selfPosition !== -1) ? route[selfPosition + 1] : null;
|
|
@@ -51,7 +54,7 @@ export class ReroutedDirectMessage extends DirectMessage {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export class UnicastMessager {
|
|
54
|
-
/** @type {Record<string,
|
|
57
|
+
/** @type {Record<string, function(DirectMessage)[]>} */ callbacks = { message_handle: [] };
|
|
55
58
|
id; cryptoCodex; arbiter; peerStore; verbose; pathFinder;
|
|
56
59
|
|
|
57
60
|
maxHops = UNICAST.MAX_HOPS;
|
|
@@ -68,7 +71,7 @@ export class UnicastMessager {
|
|
|
68
71
|
this.pathFinder = new RouteBuilder(this.id, this.peerStore);
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
/** @param {string} callbackType @param {
|
|
74
|
+
/** @param {string} callbackType @param {function(DirectMessage)} callback */
|
|
72
75
|
on(callbackType, callback) {
|
|
73
76
|
if (!this.callbacks[callbackType]) this.callbacks[callbackType] = [callback];
|
|
74
77
|
else this.callbacks[callbackType].push(callback);
|
|
@@ -135,7 +138,8 @@ export class UnicastMessager {
|
|
|
135
138
|
this.peerStore.digestPeerNeighbors(senderId, message.neighborsList);
|
|
136
139
|
if (from !== senderId) this.arbiter.adjustTrust(from, TRUST_VALUES.UNICAST_RELAYED, 'Relayed unicast message');
|
|
137
140
|
if (DISCOVERY.ON_UNICAST.DIGEST_TRAVELED_ROUTE) this.peerStore.digestValidRoute(traveledRoute);
|
|
138
|
-
if (this.id === targetId) { for (const cb of this.callbacks[message.type] || []) cb(senderId, message.data); return; } // message for self
|
|
141
|
+
//if (this.id === targetId) { for (const cb of this.callbacks[message.type] || []) cb(senderId, message.data); return; } // message for self
|
|
142
|
+
if (this.id === targetId) { for (const cb of this.callbacks[message.type] || []) cb(message); return; } // message for self
|
|
139
143
|
|
|
140
144
|
// re-send the message to the next peer in the route
|
|
141
145
|
const { success, reason } = this.#sendMessageToPeer(nextId, serialized);
|