@hive-p2p/server 1.0.83 → 1.0.85
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/config.mjs +8 -3
- package/core/ice-offer-manager.mjs +0 -7
- package/package.json +1 -1
- package/services/clock.mjs +32 -5
- package/services/clock-v2.mjs +0 -196
package/core/config.mjs
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
if (typeof window === 'undefined' && typeof self !== 'undefined') {
|
|
2
|
+
globalThis.window = self; // PATCH THE WINDOW OBJECT FOR SIMPLEPEER
|
|
3
|
+
//globalThis.document = {};
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
const isNode = typeof self === 'undefined';
|
|
2
|
-
|
|
7
|
+
if (!isNode) (await import('../libs/simplepeer-9.11.1.min.js')).default;
|
|
3
8
|
|
|
4
9
|
// HOLD: GLOBAL CONFIG FOR THE LIBRARY
|
|
5
10
|
// AVOID: CIRCULAR DEPENDENCIES AND TOO MANY FUNCTION/CONSTRUCTOR CONFIG
|
|
@@ -108,8 +113,8 @@ export const TRANSPORTS = {
|
|
|
108
113
|
|
|
109
114
|
WS_CLIENT: WebSocket,
|
|
110
115
|
WS_SERVER: isNode ? (await import('ws')).WebSocketServer : null,
|
|
111
|
-
|
|
112
|
-
PEER: (await import('simple-peer')).default,
|
|
116
|
+
PEER: isNode ? (await import('simple-peer')).default : globalThis.window.SimplePeer
|
|
117
|
+
//PEER: (await import('simple-peer')).default,
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
export const DISCOVERY = {
|
|
@@ -2,13 +2,6 @@ import { CLOCK } from '../services/clock.mjs';
|
|
|
2
2
|
import { NODE, TRANSPORTS, LOG_CSS } from './config.mjs';
|
|
3
3
|
import { xxHash32 } from '../libs/xxhash32.mjs';
|
|
4
4
|
|
|
5
|
-
// WE SHOULD LOAD WRTC ONLY WHEN NEEDED ! => Now done in node.mjs
|
|
6
|
-
/*async function getWrtc() {
|
|
7
|
-
if (typeof globalThis.RTCPeerConnection !== 'undefined') return undefined;
|
|
8
|
-
return (await import('wrtc')).default;
|
|
9
|
-
}
|
|
10
|
-
const wrtc = await getWrtc();*/
|
|
11
|
-
|
|
12
5
|
/** - 'OfferObj' Definition
|
|
13
6
|
* @typedef {Object} OfferObj
|
|
14
7
|
* @property {number} timestamp
|
package/package.json
CHANGED
package/services/clock.mjs
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export class Clock {
|
|
7
|
-
verbose;
|
|
8
|
-
mockMode; // if true, use local time without sync
|
|
7
|
+
verbose = 0;
|
|
8
|
+
mockMode = false; // if true, use local time without sync
|
|
9
9
|
static #instance = null;
|
|
10
10
|
|
|
11
|
+
/* PRIVATE PROPERTIES */
|
|
12
|
+
proxyUrl = null;
|
|
11
13
|
#offset = null; // ms difference from local time
|
|
12
14
|
#syncing = false;
|
|
13
15
|
#lastSync = 0;
|
|
14
16
|
#sources = ['time.google.com', 'time.cloudflare.com', 'pool.ntp.org'];
|
|
15
17
|
|
|
16
|
-
constructor(
|
|
17
|
-
this.verbose = verbose;
|
|
18
|
-
this.mockMode = mockMode;
|
|
18
|
+
constructor() {
|
|
19
19
|
if (Clock.#instance) return Clock.#instance;
|
|
20
20
|
else Clock.#instance = this;
|
|
21
21
|
}
|
|
@@ -72,6 +72,12 @@ export class Clock {
|
|
|
72
72
|
|
|
73
73
|
// PRIVATE METHODS
|
|
74
74
|
async #fetchTimeSamples() { // Fetch time samples from all sources in parallel
|
|
75
|
+
if (this.proxyUrl) {
|
|
76
|
+
// Mode proxy : un seul fetch vers le serveur local
|
|
77
|
+
const sample = await this.#fetchTimeFromProxy();
|
|
78
|
+
return sample ? [sample] : [];
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
const promises = this.#sources.map(source => this.#fetchTimeFromSource(source));
|
|
76
82
|
const results = await Promise.allSettled(promises);
|
|
77
83
|
const samples = [];
|
|
@@ -100,6 +106,27 @@ export class Clock {
|
|
|
100
106
|
};
|
|
101
107
|
} finally { clearTimeout(timeoutId); }
|
|
102
108
|
}
|
|
109
|
+
async #fetchTimeFromProxy() {
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const startTime = Date.now();
|
|
115
|
+
const response = await fetch(this.proxyUrl, { method: 'HEAD', signal: controller.signal });
|
|
116
|
+
const networkLatency = (Date.now() - startTime) / 2;
|
|
117
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
118
|
+
|
|
119
|
+
const serverTime = new Date(response.headers.get('date')).getTime();
|
|
120
|
+
if (isNaN(serverTime)) throw new Error('Invalid date header');
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
source: 'proxy',
|
|
124
|
+
serverTime: serverTime + networkLatency,
|
|
125
|
+
localTime: Date.now(),
|
|
126
|
+
latency: networkLatency * 2
|
|
127
|
+
};
|
|
128
|
+
} finally { clearTimeout(timeoutId); }
|
|
129
|
+
}
|
|
103
130
|
/** @param {Array<{serverTime: number, localTime: number, latency: number}>} samples */
|
|
104
131
|
#calculateOffset(samples) { // Calculate offset from multiple samples
|
|
105
132
|
if (samples.length === 1) return samples[0].serverTime - samples[0].localTime;
|
package/services/clock-v2.mjs
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Synchronized Network Clock
|
|
3
|
-
* Simple, efficient NTP-based time synchronization
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export class Clock {
|
|
7
|
-
verbose;
|
|
8
|
-
mockMode; // if true, use local time without sync
|
|
9
|
-
static #instance = null;
|
|
10
|
-
|
|
11
|
-
#offset = null; // ms difference from local time
|
|
12
|
-
#syncing = false;
|
|
13
|
-
#lastSync = 0;
|
|
14
|
-
#sources = ['time.google.com', 'time.cloudflare.com', 'pool.ntp.org'];
|
|
15
|
-
#browserSources = [
|
|
16
|
-
'worldtimeapi.org/api/timezone/UTC',
|
|
17
|
-
'timeapi.io/api/Time/current/zone?timeZone=UTC',
|
|
18
|
-
'api.github.com'
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
constructor(verbose = 0, mockMode = false) {
|
|
22
|
-
this.verbose = verbose;
|
|
23
|
-
this.mockMode = mockMode;
|
|
24
|
-
if (Clock.#instance) return Clock.#instance;
|
|
25
|
-
else Clock.#instance = this;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// PUBLIC API
|
|
29
|
-
static get instance() { return Clock.#instance || new Clock(); }
|
|
30
|
-
static get time() { return Clock.instance.time; }
|
|
31
|
-
get time() {
|
|
32
|
-
if (this.mockMode) return Date.now();
|
|
33
|
-
if (this.#offset === null) return null;
|
|
34
|
-
return Date.now() + Math.round(this.#offset);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async sync(verbose) {
|
|
38
|
-
if (verbose !== undefined) this.verbose = verbose;
|
|
39
|
-
if (this.mockMode) return Date.now();
|
|
40
|
-
|
|
41
|
-
if (this.#syncing) {
|
|
42
|
-
while (this.#syncing) await new Promise(resolve => setTimeout(resolve, 50));
|
|
43
|
-
return this.time;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
this.#syncing = true;
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const samples = await this.#fetchTimeSamples();
|
|
50
|
-
if (samples.length === 0) {
|
|
51
|
-
console.warn('[Clock] All NTP sources failed, using local time');
|
|
52
|
-
this.#offset = 0;
|
|
53
|
-
return this.time;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
this.#offset = this.#calculateOffset(samples);
|
|
57
|
-
this.#lastSync = Date.now();
|
|
58
|
-
|
|
59
|
-
if (samples.length < this.#sources.length)
|
|
60
|
-
setTimeout(() => this.#backgroundRefine(), 100);
|
|
61
|
-
return this.time;
|
|
62
|
-
} catch (error) {
|
|
63
|
-
console.error('[Clock] Sync failed:', error);
|
|
64
|
-
this.#offset = 0;
|
|
65
|
-
return this.time;
|
|
66
|
-
} finally {
|
|
67
|
-
this.#syncing = false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
get status() {
|
|
72
|
-
if (this.mockMode) return {
|
|
73
|
-
synchronized: true, syncing: false, offset: 0,
|
|
74
|
-
lastSync: Date.now(), age: 0
|
|
75
|
-
};
|
|
76
|
-
return {
|
|
77
|
-
synchronized: this.#offset !== null,
|
|
78
|
-
syncing: this.#syncing,
|
|
79
|
-
offset: this.#offset,
|
|
80
|
-
lastSync: this.#lastSync,
|
|
81
|
-
age: this.#lastSync ? Date.now() - this.#lastSync : null
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// PRIVATE METHODS
|
|
86
|
-
async #fetchTimeSamples() {
|
|
87
|
-
const sources = (typeof window !== 'undefined') ? this.#browserSources : this.#sources;
|
|
88
|
-
const promises = sources.map(source => this.#fetchTimeFromSource(source));
|
|
89
|
-
const results = await Promise.allSettled(promises);
|
|
90
|
-
|
|
91
|
-
const samples = [];
|
|
92
|
-
for (const result of results) {
|
|
93
|
-
if (result.status === 'fulfilled' && result.value) {
|
|
94
|
-
samples.push(result.value);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return samples;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async #fetchTimeFromSource(source) {
|
|
101
|
-
const controller = new AbortController();
|
|
102
|
-
const timeoutId = setTimeout(() => controller.abort(), 5000); // Plus de timeout
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const startTime = Date.now();
|
|
106
|
-
const url = source.startsWith('http') ? source : `https://${source}`;
|
|
107
|
-
|
|
108
|
-
const response = await fetch(url, {
|
|
109
|
-
method: source.includes('api.github.com') ? 'HEAD' : 'GET',
|
|
110
|
-
signal: controller.signal,
|
|
111
|
-
cache: 'no-cache',
|
|
112
|
-
mode: 'cors' // Explicit CORS
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const networkLatency = (Date.now() - startTime) / 2;
|
|
116
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
117
|
-
|
|
118
|
-
let serverTime;
|
|
119
|
-
|
|
120
|
-
if (source.includes('worldtimeapi')) {
|
|
121
|
-
const data = await response.json();
|
|
122
|
-
serverTime = new Date(data.utc_datetime).getTime();
|
|
123
|
-
} else if (source.includes('timeapi')) {
|
|
124
|
-
const data = await response.json();
|
|
125
|
-
serverTime = new Date(data.dateTime).getTime();
|
|
126
|
-
} else {
|
|
127
|
-
const dateHeader = response.headers.get('date');
|
|
128
|
-
if (!dateHeader) throw new Error('No date header');
|
|
129
|
-
serverTime = new Date(dateHeader).getTime();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (isNaN(serverTime)) throw new Error('Invalid time data');
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
source,
|
|
136
|
-
serverTime: serverTime + networkLatency,
|
|
137
|
-
localTime: Date.now(),
|
|
138
|
-
latency: networkLatency * 2
|
|
139
|
-
};
|
|
140
|
-
} catch (error) {
|
|
141
|
-
if (this.verbose) console.warn(`[Clock] Failed to fetch time from ${source}:`, error.message);
|
|
142
|
-
return null; // Retourne explicitement null au lieu d'undefined
|
|
143
|
-
} finally {
|
|
144
|
-
clearTimeout(timeoutId);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
#calculateOffset(samples) {
|
|
149
|
-
// Filter out null samples (au cas où)
|
|
150
|
-
const validSamples = samples.filter(sample => sample !== null);
|
|
151
|
-
if (validSamples.length === 0) return 0;
|
|
152
|
-
if (validSamples.length === 1) return validSamples[0].serverTime - validSamples[0].localTime;
|
|
153
|
-
|
|
154
|
-
validSamples.sort((a, b) => a.latency - b.latency);
|
|
155
|
-
|
|
156
|
-
const offsets = [];
|
|
157
|
-
for (const sample of validSamples)
|
|
158
|
-
offsets.push(sample.serverTime - sample.localTime);
|
|
159
|
-
|
|
160
|
-
offsets.sort((a, b) => a - b);
|
|
161
|
-
const mid = Math.floor(offsets.length / 2);
|
|
162
|
-
|
|
163
|
-
if (offsets.length % 2 === 0)
|
|
164
|
-
return (offsets[mid - 1] + offsets[mid]) / 2;
|
|
165
|
-
return offsets[mid];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async #backgroundRefine() {
|
|
169
|
-
if (this.#syncing) return;
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
const samples = await this.#fetchTimeSamples();
|
|
173
|
-
if (samples.length === 0) return;
|
|
174
|
-
|
|
175
|
-
const newOffset = this.#calculateOffset(samples);
|
|
176
|
-
if (Math.abs(newOffset - this.#offset) > 100)
|
|
177
|
-
this.#offset = newOffset;
|
|
178
|
-
} catch (error) {
|
|
179
|
-
if (this.verbose) console.warn('[Clock] Background refine failed:', error);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export async function CLOCK_TEST() {
|
|
185
|
-
const startTime = Date.now();
|
|
186
|
-
const clock = new Clock(1); // Verbose pour debug
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
await clock.sync();
|
|
190
|
-
console.log('Synchronized in:', Date.now() - startTime, 'ms');
|
|
191
|
-
console.log('Synchronized time:', new Date(clock.time).toISOString());
|
|
192
|
-
console.log('Clock status:', clock.status);
|
|
193
|
-
} catch (error) {
|
|
194
|
-
console.error('Sync failed:', error);
|
|
195
|
-
}
|
|
196
|
-
}
|