@hive-p2p/server 1.0.23 → 1.0.25
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/arbiter.mjs +1 -1
- package/core/config.mjs +1 -1
- package/core/crypto-codex.mjs +2 -2
- package/core/node-services.mjs +18 -21
- package/core/node.mjs +2 -1
- package/core/topologist.mjs +27 -22
- package/package.json +1 -1
package/core/arbiter.mjs
CHANGED
|
@@ -67,7 +67,7 @@ export class Arbiter {
|
|
|
67
67
|
adjustTrust(peerId, delta, reason = 'na') { // Internal and API use - return true if peer isn't banished
|
|
68
68
|
if (peerId === this.id) return; // self
|
|
69
69
|
if (delta) this.trustBalances[peerId] = Math.min(MAX_TRUST, (this.trustBalances[peerId] || 0) + delta);
|
|
70
|
-
if (delta && this.verbose >
|
|
70
|
+
if (delta && this.verbose > 3) console.log(`%c(Arbiter: ${this.id}) ${peerId} +${delta}ms (${reason}). Updated: ${this.trustBalances[peerId]}ms.`, LOG_CSS.ARBITER);
|
|
71
71
|
if (this.isBanished(peerId) && this.verbose > 1) console.log(`%c(Arbiter: ${this.id}) Peer ${peerId} is now banished.`, LOG_CSS.ARBITER);
|
|
72
72
|
}
|
|
73
73
|
isBanished(peerId = 'toto') { return (this.trustBalances[peerId] || 0) < 0; }
|
package/core/config.mjs
CHANGED
|
@@ -104,7 +104,7 @@ export const TRANSPORTS = {
|
|
|
104
104
|
/** Time to wait for signal before destroying WTRC connection | Default: 8_000 (8 seconds) */
|
|
105
105
|
SIGNAL_CREATION_TIMEOUT: 8_000,
|
|
106
106
|
/** Time to consider an SDP offer as valid | Default: 40_000 (40 seconds) */
|
|
107
|
-
SDP_OFFER_EXPIRATION: 40_000,
|
|
107
|
+
SDP_OFFER_EXPIRATION: 40_000,
|
|
108
108
|
|
|
109
109
|
WS_CLIENT: WebSocket,
|
|
110
110
|
WS_SERVER: isNode ? (await import('ws')).WebSocketServer : null,
|
package/core/crypto-codex.mjs
CHANGED
|
@@ -188,7 +188,7 @@ export class CryptoCodex {
|
|
|
188
188
|
/** @param {Uint8Array | ArrayBuffer} serialized @return {GossipMessage | null } */
|
|
189
189
|
readGossipMessage(serialized) {
|
|
190
190
|
if (this.verbose > 3) console.log(`%creadGossipMessage ${serialized.byteLength} bytes`, LOG_CSS.CRYPTO_CODEX);
|
|
191
|
-
if (this.verbose >
|
|
191
|
+
if (this.verbose > 4) console.log(`%c${serialized}`, LOG_CSS.CRYPTO_CODEX);
|
|
192
192
|
try { // 1, 1, 1, 8, 4, 32, X, 64, 1
|
|
193
193
|
const { marker, dataCode, neighLength, timestamp, dataLength, pubkey, associatedId } = this.readBufferHeader(serialized);
|
|
194
194
|
const topic = GOSSIP.MARKERS_BYTES[marker];
|
|
@@ -208,7 +208,7 @@ export class CryptoCodex {
|
|
|
208
208
|
/** @param {Uint8Array | ArrayBuffer} serialized @return {DirectMessage | ReroutedDirectMessage | null} */
|
|
209
209
|
readUnicastMessage(serialized) {
|
|
210
210
|
if (this.verbose > 3) console.log(`%creadUnicastMessage ${serialized.byteLength} bytes`, LOG_CSS.CRYPTO_CODEX);
|
|
211
|
-
if (this.verbose >
|
|
211
|
+
if (this.verbose > 4) console.log(`%c${serialized}`, LOG_CSS.CRYPTO_CODEX);
|
|
212
212
|
try { // 1, 1, 1, 8, 4, 32, X, 1, X, 64
|
|
213
213
|
const { marker, dataCode, neighLength, timestamp, dataLength, pubkey } = this.readBufferHeader(serialized, false);
|
|
214
214
|
const type = UNICAST.MARKERS_BYTES[marker];
|
package/core/node-services.mjs
CHANGED
|
@@ -59,27 +59,23 @@ export class NodeServices {
|
|
|
59
59
|
ws.on('message', (data) => { // When peer proves his id, we can handle data normally
|
|
60
60
|
if (remoteId) for (const cb of this.peerStore.callbacks.data) cb(remoteId, data);
|
|
61
61
|
else { // FIRST MESSAGE SHOULD BE HANDSHAKE WITH ID
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (const cb of this.peerStore.callbacks.connect) cb(remoteId, 'in');
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error(`Error handling WebSocket message on Node #${this.id}:`, error);
|
|
82
|
-
}
|
|
62
|
+
const d = new Uint8Array(data); if (d[0] > 127) return; // not unicast, ignore
|
|
63
|
+
const message = this.cryptoCodex.readUnicastMessage(d);
|
|
64
|
+
if (!message) return; // invalid unicast message, ignore
|
|
65
|
+
|
|
66
|
+
const { route, type, neighborsList } = message;
|
|
67
|
+
if (type !== 'handshake' || route.length !== 2) return;
|
|
68
|
+
|
|
69
|
+
const { signatureStart, pubkey, signature } = message;
|
|
70
|
+
const signedData = d.subarray(0, signatureStart);
|
|
71
|
+
if (!this.cryptoCodex.verifySignature(pubkey, signature, signedData)) return;
|
|
72
|
+
|
|
73
|
+
remoteId = route[0];
|
|
74
|
+
this.peerStore.digestPeerNeighbors(remoteId, neighborsList); // Update known store
|
|
75
|
+
this.peerStore.connecting[remoteId]?.out?.close(); // close outgoing connection if any
|
|
76
|
+
if (!this.peerStore.connecting[remoteId]) this.peerStore.connecting[remoteId] = {};
|
|
77
|
+
this.peerStore.connecting[remoteId].in = new PeerConnection(remoteId, ws, 'in', true);
|
|
78
|
+
for (const cb of this.peerStore.callbacks.connect) cb(remoteId, 'in');
|
|
83
79
|
}
|
|
84
80
|
});
|
|
85
81
|
ws.send(this.cryptoCodex.createUnicastMessage('handshake', null, [this.id, this.id], this.peerStore.neighborsList));
|
|
@@ -93,6 +89,7 @@ export class NodeServices {
|
|
|
93
89
|
this.stunServer.send(this.#buildSTUNResponse(msg, rinfo), rinfo.port, rinfo.address);
|
|
94
90
|
});
|
|
95
91
|
this.stunServer.bind(port, host);
|
|
92
|
+
if (this.verbose > 2) console.log(`%cSTUN server listening on ${host}:${port}`, LOG_CSS.SERVICE);
|
|
96
93
|
}
|
|
97
94
|
#isValidSTUNRequest(msg) {
|
|
98
95
|
if (msg.length < 20) return false;
|
package/core/node.mjs
CHANGED
|
@@ -16,7 +16,8 @@ import { NodeServices } from './node-services.mjs';
|
|
|
16
16
|
* @param {string} [options.domain] If provided, the node will operate as a public node and start necessary services (e.g., WebSocket server)
|
|
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
|
-
export async function createPublicNode(options) {
|
|
19
|
+
export async function createPublicNode(options) {
|
|
20
|
+
await CLOCK.sync(this.verbose);
|
|
20
21
|
const verbose = options.verbose !== undefined ? options.verbose : NODE.DEFAULT_VERBOSE;
|
|
21
22
|
const domain = options.domain || undefined;
|
|
22
23
|
const codex = options.cryptoCodex || new CryptoCodex(undefined, verbose);
|
package/core/topologist.mjs
CHANGED
|
@@ -144,9 +144,18 @@ export class Topologist {
|
|
|
144
144
|
}
|
|
145
145
|
/** Get overlap information for multiple peers @param {string[]} peerIds */
|
|
146
146
|
#getOverlaps(peerIds = []) { return peerIds.map(id => ({ id, ...this.#getOverlap(id) })); }
|
|
147
|
+
#getFullWsUrl(url) {
|
|
148
|
+
// Auto-detect protocol: use wss:// if in browser + HTTPS
|
|
149
|
+
const isBrowser = typeof window !== 'undefined';
|
|
150
|
+
const isSecure = isBrowser && window.location.protocol === 'https:';
|
|
151
|
+
const protocol = isSecure ? 'wss://' : 'ws://';
|
|
152
|
+
|
|
153
|
+
// Build full URL if not already prefixed
|
|
154
|
+
return url.startsWith('ws') ? url : `${protocol}${url}`;
|
|
155
|
+
}
|
|
147
156
|
#connectToPublicNode(publicUrl = 'localhost:8080') {
|
|
148
157
|
let remoteId = null;
|
|
149
|
-
const ws = new TRANSPORTS.WS_CLIENT(publicUrl); ws.binaryType = 'arraybuffer';
|
|
158
|
+
const ws = new TRANSPORTS.WS_CLIENT(this.#getFullWsUrl(publicUrl)); ws.binaryType = 'arraybuffer';
|
|
150
159
|
ws.onerror = (error) => console.error(`WebSocket error:`, error.stack);
|
|
151
160
|
ws.onopen = () => {
|
|
152
161
|
this.bootstrapsConnectionState.set(publicUrl, true);
|
|
@@ -157,27 +166,23 @@ export class Topologist {
|
|
|
157
166
|
ws.onmessage = (data) => {
|
|
158
167
|
if (remoteId) for (const cb of this.peerStore.callbacks.data) cb(remoteId, data.data);
|
|
159
168
|
else { // FIRST MESSAGE SHOULD BE HANDSHAKE WITH ID
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
for (const cb of this.peerStore.callbacks.connect) cb(remoteId, 'out');
|
|
178
|
-
} catch (error) {
|
|
179
|
-
console.error(`Error handling WebSocket message on Node #${this.id}:`, error);
|
|
180
|
-
}
|
|
169
|
+
const d = new Uint8Array(data.data); if (d[0] > 127) return; // not unicast, ignore
|
|
170
|
+
const message = this.cryptoCodex.readUnicastMessage(d);
|
|
171
|
+
if (!message) return; // invalid unicast message, ignore
|
|
172
|
+
|
|
173
|
+
const { route, type, neighborsList } = message;
|
|
174
|
+
if (type !== 'handshake' || route.length !== 2) return;
|
|
175
|
+
|
|
176
|
+
const { signatureStart, pubkey, signature } = message;
|
|
177
|
+
const signedData = d.subarray(0, signatureStart);
|
|
178
|
+
if (!this.cryptoCodex.verifySignature(pubkey, signature, signedData)) return;
|
|
179
|
+
|
|
180
|
+
remoteId = route[0];
|
|
181
|
+
this.peerStore.digestPeerNeighbors(remoteId, neighborsList); // Update known store
|
|
182
|
+
this.peerStore.connecting[remoteId]?.in?.close(); // close incoming connection if any
|
|
183
|
+
if (!this.peerStore.connecting[remoteId]) this.peerStore.connecting[remoteId] = {};
|
|
184
|
+
this.peerStore.connecting[remoteId].out = new PeerConnection(remoteId, ws, 'out', true);
|
|
185
|
+
for (const cb of this.peerStore.callbacks.connect) cb(remoteId, 'out');
|
|
181
186
|
}
|
|
182
187
|
};
|
|
183
188
|
ws.send(this.cryptoCodex.createUnicastMessage('handshake', null, [this.id, this.id], this.peerStore.neighborsList));
|