@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 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
- //if (!isNode) (await import('../libs/simplepeer-9.11.1.min.js')).default;
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
- //PEER: isNode ? (await import('simple-peer')).default : window.SimplePeer
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hive-p2p/server",
3
- "version": "1.0.83",
3
+ "version": "1.0.85",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -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(verbose = 0, mockMode = false) {
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;
@@ -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
- }