@hive-p2p/server 1.0.18 → 1.0.20

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.
@@ -0,0 +1,85 @@
1
+ export class NetworkRendererElements {
2
+ modeSwitchBtn;
3
+ nodeCountElement;
4
+ connectingCountElement;
5
+ neighborCountElement;
6
+ publicNeighborCountElement;
7
+ connectionsCountElement;
8
+ linesCountElement;
9
+
10
+ constructor(
11
+ modeSwitchBtn = document.getElementById('modeSwitchBtn'),
12
+ nodeCountElement = document.getElementById('nodeCount'),
13
+ connectingCountElement = document.getElementById('connectingCount'),
14
+ neighborCountElement = document.getElementById('neighborCount'),
15
+ publicNeighborCountElement = document.getElementById('publicNeighborCount'),
16
+ connectionsCountElement = document.getElementById('connectionsCount'),
17
+ linesCountElement = document.getElementById('linesCount'),
18
+ ) {
19
+ this.modeSwitchBtn = modeSwitchBtn;
20
+ this.nodeCountElement = nodeCountElement;
21
+ this.connectingCountElement = connectingCountElement;
22
+ this.neighborCountElement = neighborCountElement;
23
+ this.publicNeighborCountElement = publicNeighborCountElement;
24
+ this.connectionsCountElement = connectionsCountElement;
25
+ this.linesCountElement = linesCountElement;
26
+ }
27
+ }
28
+
29
+ export class NetworkRendererOptions {
30
+ mode;
31
+ antialias;
32
+ precision;
33
+ nodeRadius;
34
+ nodeBorderRadius;
35
+ attraction;
36
+ repulsion;
37
+ damping;
38
+ centerForce;
39
+ maxVelocity;
40
+ repulsionOpts;
41
+ attractionOpts;
42
+
43
+ /**
44
+ * @param {'2d' | '3d'} mode
45
+ * @param {number} nodeRadius @param {number} nodeBorderRadius @param {number} attraction @param {number} repulsion
46
+ * @param {number} damping @param {number} centerForce @param {number} maxVelocity
47
+ *
48
+ * @param {Object} repulsionOpts
49
+ * @param {number} repulsionOpts.maxDistance
50
+ *
51
+ * @param {Object} attractionOpts
52
+ * @param {number} attractionOpts.minDistance
53
+ * */
54
+ constructor(
55
+ mode = '3d',
56
+ antialias = true, // Enable or disable antialiasing
57
+ precision = "highp", // "lowp"
58
+ nodeRadius = 12,
59
+ nodeBorderRadius = 3,
60
+ attraction = .000001, // .0001
61
+ repulsion = 5_000, // 50000
62
+ damping = .005, // .5
63
+ centerForce = .05, // .0005
64
+ maxVelocity = 3, // .2
65
+ repulsionOpts = {
66
+ maxDistance: 400,
67
+ },
68
+ attractionOpts = {
69
+ minDistance: 100, // 50
70
+ }
71
+ ) {
72
+ this.mode = mode;
73
+ this.antialias = antialias;
74
+ this.precision = precision;
75
+ this.nodeRadius = nodeRadius;
76
+ this.nodeBorderRadius = nodeBorderRadius;
77
+ this.attraction = attraction;
78
+ this.repulsion = repulsion;
79
+ this.damping = damping;
80
+ this.centerForce = centerForce;
81
+ this.maxVelocity = maxVelocity;
82
+ this.repulsionOpts = repulsionOpts;
83
+ this.attractionOpts = attractionOpts;
84
+ }
85
+ }
@@ -0,0 +1,234 @@
1
+ export class Node {
2
+ id;
3
+ status;
4
+ isPublic;
5
+ neighbors;
6
+ velocity = { x: 0, y: 0, z: 0 };
7
+ position = {
8
+ x: (Math.random() - 0.5) * 500,
9
+ y: (Math.random() - 0.5) * 500,
10
+ z: (Math.random() - 0.5) * 500
11
+ };
12
+
13
+ /** Constructor for a Node
14
+ * @param {string} id @param {'unknown' | 'known' | 'connecting' | 'connected' | 'current'} status
15
+ * @param {boolean} isPublic @param {Array<string>} neighbors */
16
+ constructor(id, status, isPublic, neighbors) {
17
+ this.id = id;
18
+ this.status = status;
19
+ this.isPublic = isPublic;
20
+ this.neighbors = neighbors;
21
+ }
22
+ addNeighbor(peerId) {
23
+ if (!this.neighbors.includes(peerId)) this.neighbors.push(peerId);
24
+ }
25
+ removeNeighbor(peerId) {
26
+ this.neighbors = this.neighbors.filter(id => id !== peerId);
27
+ }
28
+ }
29
+ export class NodesStore {
30
+ /** @type {Record<string, Node>} */ store = {};
31
+
32
+ /** @param {Node} node */
33
+ add(node) { this.store[node.id] = node; }
34
+ get(id = 'toto') { return this.store[id]; }
35
+ has(id = 'toto') { return !!this.store[id]; }
36
+ remove(id = 'toto') { delete this.store[id]; }
37
+ getNodesIds() { return Object.keys(this.store); }
38
+ getInfo() {
39
+ const result = {
40
+ total: 0,
41
+ totalPublic: 0,
42
+ connectedPublic: 0,
43
+ connected: 0,
44
+ connecting: 0,
45
+ known: 0,
46
+ unknown: 0,
47
+ maxDistance: 0, // max node distance from center x or y, used for auto zoom
48
+ };
49
+ for (const node of Object.values(this.store)) {
50
+ result.total++;
51
+ result.maxDistance = Math.max(result.maxDistance, Math.abs(node.position.x), Math.abs(node.position.y));
52
+ if (node.isPublic) result.totalPublic++;
53
+ if (node.status === 'connected' && node.isPublic) result.connectedPublic++;
54
+ if (node.status === 'connected') result.connected++;
55
+ else if (node.status === 'connecting') result.connecting++;
56
+ else if (node.status === 'known') result.known++;
57
+ else if (node.status === 'unknown') result.unknown++;
58
+ }
59
+ return result;
60
+ }
61
+ }
62
+
63
+ const lineMaterials = {};
64
+ class PeerLineConnection {
65
+ /** @type {THREE.Line} */ line;
66
+ /** @type {number} in frames */ repaintIgnored = 0;
67
+ /** @type {boolean} */ isHovered = false;
68
+
69
+ #updateLineColor(colorHex = 0x666666, opacity = .4, dashed = false) {
70
+ if (!this.line || this.line === true) return false; // not assigned (physic only)
71
+ this.line.material = this.#getLineMaterial(colorHex, opacity, dashed);
72
+ if (dashed) this.line.computeLineDistances();
73
+ return 'updated';
74
+ }
75
+ #getLineMaterial(colorHex, opacity, dashed) {
76
+ const matKey = `${colorHex.toString(16)}_${opacity}_${dashed}`;
77
+ if (lineMaterials[matKey]) return lineMaterials[matKey];
78
+ const material = dashed
79
+ ? new THREE.LineDashedMaterial({ color: colorHex, transparent: true, opacity, dashSize: 12, gapSize: 20 })
80
+ : new THREE.LineBasicMaterial({ color: colorHex, transparent: true, opacity });
81
+ lineMaterials[matKey] = material;
82
+ return material;
83
+ }
84
+ disposeLine(scene) {
85
+ if (!this.line || this.line === true) return;
86
+ scene.remove(this.line);
87
+ this.line.geometry.dispose();
88
+ this.line.material.dispose();
89
+ this.isHovered = false;
90
+ this.repaintIgnored = 0;
91
+ }
92
+ countIgnoredRepaint() {
93
+ if (this.repaintIgnored-- > 0) return true;
94
+ this.repaintIgnored = 0;
95
+ return false;
96
+ }
97
+ assignOrUpdateLineColor(scene, fromPos = {}, toPos = {}, color = 0x666666, opacity = .4, dashed = false) {
98
+ if (this.line) return this.#updateLineColor(color, opacity, dashed);
99
+ if (!fromPos || !toPos) return false; // skip if missing position
100
+
101
+ const geometry = new THREE.BufferGeometry();
102
+ const p = new Float32Array([fromPos.x, fromPos.y, fromPos.z, toPos.x, toPos.y, toPos.z]);
103
+ geometry.setAttribute('position', new THREE.BufferAttribute(p, 3));
104
+
105
+ const material = this.#getLineMaterial(color, opacity, dashed);
106
+ const line = new THREE.Line(geometry, material);
107
+ scene.add(line);
108
+ this.line = line;
109
+ return 'created';
110
+ }
111
+ }
112
+
113
+ export class ConnectionsStore {
114
+ /** @type {Record<string, PeerLineConnection>} */ store = {};
115
+ nodesStore;
116
+ scene;
117
+ updateBatchMax = 500;
118
+ peerConnsWithLines = {};
119
+
120
+ /** @param {NodesStore} nodesStore */
121
+ constructor(nodesStore, scene) {
122
+ this.nodesStore = nodesStore;
123
+ this.scene = scene;
124
+ }
125
+
126
+ #getKeys(fromId = 'toto', toId = 'tutu') {
127
+ const key1 = `${fromId}:${toId}`;
128
+ const key2 = `${toId}:${fromId}`;
129
+ const validKey = this.store[key1] ? key1 : this.store[key2] ? key2 : null;
130
+ return { key1, key2, validKey };
131
+ }
132
+ set(fromId = 'toto', toId = 'tutu') {
133
+ const { key1, key2, validKey } = this.#getKeys(fromId, toId);
134
+ if (validKey) return { success: false, key: validKey }; // already set
135
+ this.store[key1] = new PeerLineConnection();
136
+ return { success: true, key: key1, peerConn: this.store[key1] };
137
+ }
138
+ unset(fromId = 'toto', toId = 'tutu', force = false) {
139
+ const { key1, key2, validKey } = this.#getKeys(fromId, toId);
140
+ if (!validKey) return;
141
+
142
+ const peerConn = this.store[validKey];
143
+ if (peerConn.repaintIgnored && !force) return; // still ignored
144
+
145
+ delete this.peerConnsWithLines[validKey];
146
+ delete this.store[validKey];
147
+ peerConn.disposeLine(this.scene);
148
+
149
+ // unlink from nodes <> neighbors
150
+ this.nodesStore.get(fromId)?.removeNeighbor(toId);
151
+ this.nodesStore.get(toId)?.removeNeighbor(fromId);
152
+ }
153
+
154
+ // VISUAL LINE
155
+ unassignLine(fromId = 'peer_1', toId = 'peer_2') {
156
+ const { key1, key2, validKey } = this.#getKeys(fromId, toId);
157
+ if (!validKey) return;
158
+ this.store[validKey]?.disposeLine(this.scene);
159
+ delete this.peerConnsWithLines[validKey];
160
+ }
161
+ setHovered(fromId = 'toto', toId = 'tutu') {
162
+ const { key1, key2, validKey } = this.#getKeys(fromId, toId);
163
+ if (!validKey) return;
164
+
165
+ const [ fromPos, toPos ] = [ this.nodesStore.get(fromId)?.position, this.nodesStore.get(toId)?.position ];
166
+ const result = this.store[validKey].assignOrUpdateLineColor(this.scene, fromPos, toPos);
167
+ if (result === false) return;
168
+
169
+ this.peerConnsWithLines[validKey] = this.store[validKey];
170
+ this.store[validKey].isHovered = true;
171
+ if (result !== 'updated') return;
172
+ this.store[validKey].line.userData = { fromId, toId, type: 'connection' };
173
+ }
174
+ resetHovered() {
175
+ for (const key in this.store)
176
+ if (this.store[key].isHovered) this.unset(...key.split(':'), true);
177
+ }
178
+ updateOrAssignLineColor(fromId = 'toto', toId = 'tutu', color = 0x666666, opacity = .4, ignoreRepaintFrames, dashed = false) {
179
+ const { key1, key2, validKey } = this.#getKeys(fromId, toId);
180
+ const peerConn = validKey ? this.store[validKey] : null;
181
+ if (!peerConn) return false;
182
+
183
+ // assign line HERE HERE HERE
184
+ const [ fromPos, toPos ] = [ this.nodesStore.get(fromId)?.position, this.nodesStore.get(toId)?.position ];
185
+ const result = peerConn.assignOrUpdateLineColor(this.scene, fromPos, toPos, color, opacity, dashed);
186
+ if (result === 'created') peerConn.line.userData = { fromId, toId, type: 'connection' };
187
+ if (result && ignoreRepaintFrames) peerConn.repaintIgnored = ignoreRepaintFrames;
188
+ if (result) this.peerConnsWithLines[validKey] = peerConn;
189
+ return result;
190
+ }
191
+
192
+ updateConnections(currentPeerId, hoveredNodeId, colors, mode = '3d') { // positions & colors
193
+ for (const connStr in this.peerConnsWithLines) {
194
+ const peerConn = this.peerConnsWithLines[connStr];
195
+ if (!peerConn) continue;
196
+ const [fromId, toId] = connStr.split(':');
197
+ const fromPos = this.nodesStore.get(fromId)?.position;
198
+ const toPos = this.nodesStore.get(toId)?.position;
199
+ if (!fromPos || !toPos) continue; // skip if missing position
200
+
201
+ const positionAttribute = peerConn.line.geometry.attributes.position;
202
+ positionAttribute.array[0] = fromPos.x;
203
+ positionAttribute.array[1] = fromPos.y;
204
+ positionAttribute.array[2] = mode === '3d' ? fromPos.z : 0;
205
+ positionAttribute.array[3] = toPos.x;
206
+ positionAttribute.array[4] = toPos.y;
207
+ positionAttribute.array[5] = mode === '3d' ? toPos.z : 0;
208
+ positionAttribute.needsUpdate = true;
209
+
210
+ if (this.store[connStr].countIgnoredRepaint()) {
211
+ if (peerConn.line.material.isDashedLineMaterial) peerConn.line.computeLineDistances();
212
+ continue;
213
+ }
214
+
215
+ // Update connection color
216
+ const { connection, currentPeerConnection, hoveredPeer } = colors;
217
+ const isCurrentPeer = fromId === currentPeerId || toId === currentPeerId;
218
+ const isHoveredPeer = fromId === hoveredNodeId || toId === hoveredNodeId;
219
+ const color = isCurrentPeer ? currentPeerConnection : isHoveredPeer ? hoveredPeer : connection;
220
+ const opacity = color === connection ? .33 : .5;
221
+ const result = peerConn.assignOrUpdateLineColor(this.scene, fromPos, toPos, color, opacity);
222
+ if (peerConn.line.material.isDashedLineMaterial) peerConn.line.computeLineDistances();
223
+ if (result) this.peerConnsWithLines[connStr] = peerConn;
224
+ }
225
+ }
226
+ getConnectionsCount() {
227
+ const result = { connsCount: Object.keys(this.store).length, linesCount: 0 };
228
+ for (const peerConn of Object.values(this.store)) if (peerConn.line) result.linesCount++;
229
+ return result;
230
+ }
231
+ destroy() {
232
+ for (const key of Object.keys(this.store)) this.unset(...key.split(':'), true);
233
+ }
234
+ }
@@ -0,0 +1,138 @@
1
+ body {
2
+ margin: 0;
3
+ font-family: Arial, sans-serif;
4
+ background: #1a1a1a;
5
+ color: white;
6
+ overflow: hidden;
7
+ }
8
+
9
+ button {
10
+ cursor: pointer;
11
+ }
12
+
13
+ ::-webkit-scrollbar {
14
+ width: 8px;
15
+ }
16
+ ::-webkit-scrollbar-track {
17
+ background: #353535;
18
+ border-radius: 6px;
19
+ }
20
+ ::-webkit-scrollbar-thumb {
21
+ cursor: pointer;
22
+ background: rgba(76, 175, 80, .5);
23
+ border-radius: 6px;
24
+ }
25
+
26
+
27
+ #peersList {
28
+ position: fixed;
29
+ top: 10px;
30
+ left: 10px;
31
+ background: rgba(0, 0, 0, 0.8);
32
+ border-radius: 8px;
33
+ padding: 10px;
34
+ max-height: 50vh;
35
+ overflow-y: auto;
36
+ overflow-x: hidden;
37
+ min-width: 130px;
38
+ max-width: 200px;
39
+ z-index: 1000;
40
+ }
41
+ #peersList h3 {
42
+ margin: 0 0 10px 0;
43
+ color: #4CAF50;
44
+ }
45
+ #peersList div[data-peer-id] {
46
+ overflow: hidden;
47
+ max-width: 100px;
48
+ padding: 6px 10px;
49
+ margin: 2px 0;
50
+ background: rgba(76, 175, 80, 0.2);
51
+ border-radius: 4px;
52
+ cursor: pointer;
53
+ transition: background 0.2s;
54
+ }
55
+ #peersList div[data-peer-id].selected {
56
+ background: rgba(76, 175, 80, 0.4);
57
+ }
58
+ #peersList div[data-peer-id]:hover {
59
+ background: rgba(76, 175, 80, 0.4);
60
+ }
61
+ .info {
62
+ position: absolute;
63
+ bottom: 10px;
64
+ left: 10px;
65
+ background: rgba(0,0,0,0.8);
66
+ padding: 10px;
67
+ border-radius: 5px;
68
+ font-size: 12px;
69
+ }
70
+ .info pre {
71
+ margin: 5px 0 0 0;
72
+ }
73
+ #modeSwitchBtn {
74
+ position: absolute;
75
+ top: 10px;
76
+ right: 10px;
77
+ }
78
+ .info.right {
79
+ left: auto;
80
+ right: 10px;
81
+ }
82
+ .info.right div {
83
+ padding-bottom: 2px;
84
+ }
85
+
86
+ #simulationSettings {
87
+ position: fixed;
88
+ top: 10px;
89
+ right: 10px;
90
+ background: rgba(0, 0, 0, 0.8);
91
+ border-radius: 8px;
92
+ padding: 10px;
93
+ font-size: 14px;
94
+ z-index: 1000;
95
+ }
96
+ #simulationSettings h3 {
97
+ text-align: right;
98
+ margin: 0 0 10px 0;
99
+ color: #4CAF50;
100
+ }
101
+ #simulationSettings div {
102
+ display: flex;
103
+ justify-content: space-between;
104
+ margin-bottom: 6px;
105
+ gap: 10px;
106
+ }
107
+ #simulationSettings div input {
108
+ text-align: center;
109
+ width: 50px;
110
+ }
111
+
112
+ #currentPeer {
113
+ position: fixed;
114
+ top: 20px;
115
+ right: 20px;
116
+ background: rgba(0, 0, 0, 0.8);
117
+ border-radius: 8px;
118
+ padding: 15px;
119
+ z-index: 1000;
120
+ color: #FFD700;
121
+ }
122
+ #tooltip {
123
+ position: fixed;
124
+ background: rgba(0, 0, 0, 0.9);
125
+ color: white;
126
+ padding: 10px;
127
+ border-radius: 4px;
128
+ pointer-events: none;
129
+ z-index: 1001;
130
+ font-size: 12px;
131
+ display: none;
132
+ max-width: 200px;
133
+ }
134
+ #networkCanvas {
135
+ position: absolute;
136
+ top: 20px;
137
+ left: 20px;
138
+ }
@@ -0,0 +1,60 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>P2P Network Visualization - Three.js</title>
5
+ <link rel="stylesheet" href="./rendering/visualizer.css">
6
+ <script src="./libs/three-4.5.min.js"></script>
7
+ <script src="./rendering/visualizer.mjs" type="module"></script>
8
+ </head>
9
+ <body>
10
+ <div id="peersList" style="display: none;">
11
+ <h3>Peers List</h3>
12
+ </div>
13
+ <div id="simulationSettings" style="display: none;">
14
+ <h3>Simulation Settings</h3>
15
+ <div>
16
+ <label for="publicPeersCount">Public:</label>
17
+ <input type="number" id="publicPeersCount" value="0">
18
+ </div>
19
+ <div>
20
+ <label for="peersCount">Standard:</label>
21
+ <input type="number" id="peersCount" value="0">
22
+ </div>
23
+ <button id="startSimulation" style="width: 100%;">Start</button>
24
+ </div>
25
+ <div id="tooltip"></div>
26
+ <div class="info"> <!-- SIMULATION INFO/STATS-->
27
+ <button id="modeSwitchBtn">2D</button>
28
+ <div>FPS: <span id="fpsCount">0</span></div>
29
+ <div>Nodes: <span id="nodeCount">0</span></div>
30
+ <div>Connecting: <span id="connectingCount">0</span></div>
31
+ <div>Neighbors: <span id="neighborCount">0</span></div>
32
+ <div>PublicNeighbors: <span id="publicNeighborCount">0</span></div>
33
+ <div>Connections: <span id="connectionsCount">0</span></div>
34
+ <div>Lines: <span id="linesCount">0</span></div>
35
+ --------------------------------------
36
+ <div><pre>
37
+ Left-Click = connect
38
+ Right-Click = select
39
+ Left-Handle = pan
40
+ Right-Handle = rotate
41
+ Wheel = zoom
42
+ SpaceBar = pause/resume</pre></div>
43
+ </div>
44
+
45
+ <div class="info right"> <!-- LEGEND -->
46
+ <div style="font-weight: bold;">Peers:</div>
47
+ <div style="color: #929292">● <span>Discovered</span></div>
48
+ <div style="color: #ffffff">● <span>Public Node</span></div>
49
+ <div style="color: #03b5fc">● <span>Connecting</span></div>
50
+ <div style="color: #4CAF50">● <span>Connected (current)</span></div>
51
+ <div style="color: #f216e4">● <span>Twitch OGs</span></div>
52
+
53
+ <div style="padding-top: 10px; font-weight: bold;">Connections:</div>
54
+ <div style="color: #929292">- <span>Connection</span></div>
55
+ <div style="color: #4CAF50">- <span>Connection (current)</span></div>
56
+ <div style="color: #035afc">- <span>Direct message</span></div>
57
+ <div style="color: #f542f5">- <span>Gossip message</span></div>
58
+ </div>
59
+ </body>
60
+ </html>