hive-p2p 1.0.31 → 1.0.32
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/README.md +24 -39
- package/core/node-services.mjs +6 -4
- package/core/node.mjs +9 -3
- package/package.json +1 -1
- package/simulation/simulator.mjs +14 -6
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- **.min (unpkg):** [unpkg.com/@hive-p2p/browser@latest/hive-p2p.min.js](https://unpkg.com/@hive-p2p/browser@1.0.19/hive-p2p.min.js)
|
|
10
10
|
- **.min (cdn):** [cdn.jsdelivr.net/npm/@hive-p2p/browser@latest/hive-p2p.min.js](https://cdn.jsdelivr.net/npm/@hive-p2p/browser@latest/hive-p2p.min.js)
|
|
11
11
|
- **Stats:** [ghloc.vercel.app/Seigneur-Machiavel/hive-p2p](https://ghloc.vercel.app/Seigneur-Machiavel/hive-p2p?branch=main)
|
|
12
|
-
- **
|
|
12
|
+
- **demos-repo :** [github.com/Seigneur-Machiavel/hive-p2p-demo](https://github.com/Seigneur-Machiavel/hive-p2p-demo)
|
|
13
13
|
|
|
14
14
|
## What is HiveP2P?
|
|
15
15
|
|
|
@@ -45,27 +45,31 @@ npm install @hive-p2p/browser
|
|
|
45
45
|
### Basic Usage
|
|
46
46
|
|
|
47
47
|
```javascript
|
|
48
|
-
import
|
|
48
|
+
import HiveP2P from "hive-p2p"; // using full lib (server + client)
|
|
49
49
|
|
|
50
|
-
// Create a node
|
|
51
|
-
const
|
|
50
|
+
// Create a public node
|
|
51
|
+
const publicNode = await HiveP2P.createPublicNode({ domain: 'localhost', port: 12345 });
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
{ id: '0abc...', publicUrl: 'ws://seed1.example.com:8080' },
|
|
56
|
-
{ id: '0def...', publicUrl: 'ws://seed2.example.com:8080' }
|
|
57
|
-
]);
|
|
53
|
+
// Use public node as bootstrap by extracting its url
|
|
54
|
+
const bootstraps = [publicNode.publicUrl];
|
|
58
55
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
// Create nodes with bootstrap node
|
|
57
|
+
const bee1 = await HiveP2P.createNode({ bootstraps });
|
|
58
|
+
const bee2 = await HiveP2P.createNode({ bootstraps });
|
|
59
|
+
|
|
60
|
+
// Listen for unicast messages (target id)
|
|
61
|
+
for (const node of [publicNode, bee1, bee2])
|
|
62
|
+
node.onMessageData((fromId, message) => console.log(`[${node.id}] from [${fromId}]: ${message}`));
|
|
63
63
|
|
|
64
|
-
//
|
|
65
|
-
|
|
64
|
+
// Listen for gossip messages (broadcast to all)
|
|
65
|
+
for (const node of [publicNode, bee1, bee2])
|
|
66
|
+
node.onGossipData((fromId, message) => console.log(`[${node.id}]: from [${fromId}]: ${message}`));
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
while (true) {
|
|
69
|
+
bee1.sendMessage(bee2.id, `Hello bee2!`); // Send direct unicast message
|
|
70
|
+
bee2.broadcast("Hello everyone! I'm bee bee2"); // Broadcast message to all
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
|
72
|
+
}
|
|
69
73
|
```
|
|
70
74
|
|
|
71
75
|
## How It Works
|
|
@@ -110,23 +114,12 @@ Real simulation results with 2000 nodes:
|
|
|
110
114
|
┌──────────────────────────────────────────────────────────┐
|
|
111
115
|
│ 10 minutes (600s) │
|
|
112
116
|
├──────────────────────────────────────────────────────────┤
|
|
113
|
-
│ Active nodes: 2002/2002 (1902 established)
|
|
117
|
+
│ Active nodes: 2002/2002 (1902 established) │
|
|
114
118
|
│ Avg neighbors: 4.7 │
|
|
115
119
|
│ Network coverage: 100% (T: 10571) │
|
|
116
120
|
│ Avg latency: 169ms │
|
|
117
|
-
│ Gossip rate: 0.4 msg/s
|
|
118
|
-
│ Bandwidth: 856 bytes/s
|
|
119
|
-
└──────────────────────────────────────────────────────────┘
|
|
120
|
-
|
|
121
|
-
┌──────────────────────────────────────────────────────────┐
|
|
122
|
-
│ 1 hour (602s - continued sim) │
|
|
123
|
-
├──────────────────────────────────────────────────────────┤
|
|
124
|
-
│ Active nodes: 2003/2003 (1903 established) │
|
|
125
|
-
│ Avg neighbors: 4.8 │
|
|
126
|
-
│ Network coverage: 100% (T: 8225) │
|
|
127
|
-
│ Avg latency: 223ms │
|
|
128
|
-
│ Gossip rate: 0.2 msg/s │
|
|
129
|
-
│ Bandwidth: 396 bytes/s │
|
|
121
|
+
│ Gossip rate: 0.4 msg/s │
|
|
122
|
+
│ Bandwidth: 856 bytes/s │
|
|
130
123
|
└──────────────────────────────────────────────────────────┘
|
|
131
124
|
```
|
|
132
125
|
|
|
@@ -284,10 +277,6 @@ Check our [contribution guide](CONTRIBUTING.md) to get started.
|
|
|
284
277
|
|
|
285
278
|
HiveP2P embodies this principle - simple components (gossip, routing, topology awareness) combine to create a complex, self-organizing system that surpasses traditional approaches.
|
|
286
279
|
|
|
287
|
-
## License
|
|
288
|
-
|
|
289
|
-
GNU General Public License v3.0 - See [LICENSE](LICENSE) for details.
|
|
290
|
-
|
|
291
280
|
---
|
|
292
281
|
|
|
293
282
|
*HiveP2P - Because the internet deserves better than client-server*
|
|
@@ -571,10 +560,6 @@ Consultez notre [guide de contribution](CONTRIBUTING.md) pour commencer.
|
|
|
571
560
|
|
|
572
561
|
HiveP2P incarne ce principe - des composants simples (gossip, routage, conscience topologique) se combinent pour créer un système complexe et auto-organisant qui surpasse les approches traditionnelles.
|
|
573
562
|
|
|
574
|
-
## Licence
|
|
575
|
-
|
|
576
|
-
GNU General Public License v3.0 - Voir [LICENSE](LICENSE) pour les détails.
|
|
577
|
-
|
|
578
563
|
---
|
|
579
564
|
|
|
580
565
|
*HiveP2P - Parce qu'Internet mérite mieux que client-serveur*
|
package/core/node-services.mjs
CHANGED
|
@@ -121,11 +121,13 @@ export class NodeServices {
|
|
|
121
121
|
static deriveSTUNServers(bootstraps) {
|
|
122
122
|
/** @type {Array<{urls: string}>} */
|
|
123
123
|
const stunUrls = [];
|
|
124
|
-
for (const b of bootstraps) {
|
|
124
|
+
for (const b of bootstraps) {
|
|
125
125
|
if (!b.includes(':')) continue;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
url.replace('ws
|
|
126
|
+
if (b.endsWith('/ws') || b.endsWith('/wss')) continue; // ugly hardcode
|
|
127
|
+
// useless ? => proxy can't serve as stun server
|
|
128
|
+
//const url = b.replace('/ws', '/signal'); // in case someone put domain/ws
|
|
129
|
+
//url.replace('/wss', '/signal'); // in case someone put domain/wss
|
|
130
|
+
const url = b.replace('ws://', 'stun:');
|
|
129
131
|
url.replace('wss://', 'stun:');
|
|
130
132
|
if (!url.includes('stun:')) continue;
|
|
131
133
|
stunUrls.push({ urls: url });
|
package/core/node.mjs
CHANGED
|
@@ -17,12 +17,13 @@ import { NodeServices } from './node-services.mjs';
|
|
|
17
17
|
* @param {number} [options.port] If provided, the node will listen on this port (default: SERVICE.PORT)
|
|
18
18
|
* @param {number} [options.verbose] Verbosity level for logging (default: NODE.DEFAULT_VERBOSE) */
|
|
19
19
|
export async function createPublicNode(options) {
|
|
20
|
-
await CLOCK.sync(this.verbose);
|
|
21
20
|
const verbose = options.verbose !== undefined ? options.verbose : NODE.DEFAULT_VERBOSE;
|
|
22
21
|
const domain = options.domain || undefined;
|
|
23
22
|
const codex = options.cryptoCodex || new CryptoCodex(undefined, verbose);
|
|
23
|
+
const clockSync = CLOCK.sync(verbose);
|
|
24
24
|
if (!codex.publicKey) await codex.generate(domain ? true : false);
|
|
25
25
|
|
|
26
|
+
await clockSync;
|
|
26
27
|
const node = new Node(codex, options.bootstraps || [], verbose);
|
|
27
28
|
if (domain) {
|
|
28
29
|
node.services = new NodeServices(codex, node.peerStore, undefined, verbose);
|
|
@@ -42,8 +43,10 @@ export async function createPublicNode(options) {
|
|
|
42
43
|
export async function createNode(options = {}) {
|
|
43
44
|
const verbose = options.verbose !== undefined ? options.verbose : NODE.DEFAULT_VERBOSE;
|
|
44
45
|
const codex = options.cryptoCodex || new CryptoCodex(undefined, verbose);
|
|
46
|
+
const clockSync = CLOCK.sync(verbose);
|
|
45
47
|
if (!codex.publicKey) await codex.generate(false);
|
|
46
48
|
|
|
49
|
+
await clockSync;
|
|
47
50
|
const node = new Node(codex, options.bootstraps || [], verbose);
|
|
48
51
|
if (options.autoStart !== false) await node.start();
|
|
49
52
|
return node;
|
|
@@ -145,10 +148,13 @@ export class Node {
|
|
|
145
148
|
await CLOCK.sync(this.verbose);
|
|
146
149
|
this.started = true;
|
|
147
150
|
if (SIMULATION.AVOID_INTERVALS) return true; // SIMULATOR CASE
|
|
148
|
-
|
|
151
|
+
|
|
149
152
|
this.arbiterInterval = setInterval(() => this.arbiter.tick(), 1000);
|
|
150
|
-
this.enhancerInterval = setInterval(() => this.topologist.tick(), DISCOVERY.LOOP_DELAY);
|
|
151
153
|
this.peerStoreInterval = setInterval(() => { this.peerStore.cleanupExpired(); this.peerStore.offerManager.tick(); }, 2500);
|
|
154
|
+
if (this.publicUrl) return true;
|
|
155
|
+
|
|
156
|
+
this.enhancerInterval = setInterval(() => this.topologist.tick(), DISCOVERY.LOOP_DELAY);
|
|
157
|
+
this.topologist.tryConnectNextBootstrap(); // first shot ASAP
|
|
152
158
|
return true;
|
|
153
159
|
}
|
|
154
160
|
/** Broadcast a message to all connected peers or to a specified peer
|
package/package.json
CHANGED
package/simulation/simulator.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import { join } from 'path';
|
|
2
3
|
import express from 'express';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
3
5
|
import { fileURLToPath } from 'url';
|
|
4
6
|
import { WebSocketServer } from 'ws';
|
|
5
7
|
import { io } from 'socket.io-client'; // used for twitch events only
|
|
@@ -229,13 +231,19 @@ if (SIMULATION.RANDOM_UNICAST_PER_SEC) randomMessagesLoop('U', SIMULATION.RANDOM
|
|
|
229
231
|
if (SIMULATION.RANDOM_GOSSIP_PER_SEC) randomMessagesLoop('G', SIMULATION.RANDOM_GOSSIP_PER_SEC);
|
|
230
232
|
|
|
231
233
|
const app = express(); // simple server to serve texts/p2p_simulator.html
|
|
232
|
-
app.
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
const server = app.listen(3000, () => console.log('%cServer listening on http://localhost:3000', LOG_CSS.SIMULATOR));
|
|
235
|
+
app.get('/core/config.mjs', (req, res) => { // CONFIG PATCH FOR SIMULATOR
|
|
236
|
+
const configPath = join(packageRoot, 'core/config.mjs');
|
|
237
|
+
let content = readFileSync(configPath, 'utf8');
|
|
238
|
+
content = content // Patch the values
|
|
239
|
+
.replace(/ARE_IDS_HEX:\s*true/g, 'ARE_IDS_HEX: false')
|
|
240
|
+
.replace(/PUBLIC_PREFIX:\s*'0'/g, "PUBLIC_PREFIX: 'P_'")
|
|
241
|
+
.replace(/STANDARD_PREFIX:\s*'1'/g, "STANDARD_PREFIX: 'N_'");
|
|
242
|
+
|
|
243
|
+
res.set({ 'Content-Type': 'application/javascript', 'Cache-Control': 'no-cache, no-store, must-revalidate'});
|
|
244
|
+
res.send(content);
|
|
235
245
|
});
|
|
236
|
-
|
|
237
246
|
app.use(express.static(packageRoot));
|
|
238
|
-
const server = app.listen(3000, () => console.log('%cServer listening on http://localhost:3000', LOG_CSS.SIMULATOR));
|
|
239
247
|
app.get('/', (req, res) => res.sendFile('rendering/visualizer.html', { root: packageRoot }));
|
|
240
248
|
app.get('/the-gossip-grail', (req, res) => res.sendFile('resources/the-gossip-grail.html', { root: packageRoot }));
|
|
241
249
|
|
|
@@ -316,7 +324,7 @@ class TwitchChatCommandInterpreter {
|
|
|
316
324
|
.replace(/[^\w-]/g, '_') // remplacer chars spéciaux par _
|
|
317
325
|
|
|
318
326
|
const cryptoCodex = IDENTITY.ARE_IDS_HEX ? new CryptoCodex() : new CryptoCodex(`F_${cleanUser}`);
|
|
319
|
-
const peer = await
|
|
327
|
+
const peer = await createNode({ bootstraps: pickUpRandomBootstraps(), cryptoCodex, verbose: 2 });
|
|
320
328
|
this.userNodes[user] = peer;
|
|
321
329
|
peers.all[peer.id] = peer;
|
|
322
330
|
peers.standard.unshift(peer);
|