@hive-p2p/browser 1.0.84 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hive-p2p/browser",
3
- "version": "1.0.84",
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
- }