Refactor and code designing
This commit is contained in:
@@ -1,45 +1,94 @@
|
||||
import { Injectable, signal, computed } from '@angular/core';
|
||||
|
||||
/** Default timeout (ms) for the NTP-style HTTP sync request. */
|
||||
const DEFAULT_SYNC_TIMEOUT_MS = 5000;
|
||||
|
||||
/**
|
||||
* Maintains a clock-offset between the local system time and the
|
||||
* remote signaling server.
|
||||
*
|
||||
* The offset is estimated using a simple NTP-style round-trip
|
||||
* measurement and is stored as a reactive Angular signal so that
|
||||
* any dependent computed value auto-updates when a new sync occurs.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TimeSyncService {
|
||||
// serverTime - clientTime offset (milliseconds)
|
||||
/**
|
||||
* Internal offset signal:
|
||||
* `serverTime = Date.now() + offset`.
|
||||
*/
|
||||
private readonly _offset = signal<number>(0);
|
||||
private _lastSyncAt = 0;
|
||||
|
||||
/** Epoch timestamp of the most recent successful sync. */
|
||||
private lastSyncTimestamp = 0;
|
||||
|
||||
/** Reactive read-only offset (milliseconds). */
|
||||
readonly offset = computed(() => this._offset());
|
||||
|
||||
// Returns a server-adjusted now() using the current offset
|
||||
/**
|
||||
* Return a server-adjusted "now" timestamp.
|
||||
*
|
||||
* @returns Epoch milliseconds aligned to the server clock.
|
||||
*/
|
||||
now(): number {
|
||||
return Date.now() + this._offset();
|
||||
}
|
||||
|
||||
// Set offset based on a serverTime observed at approximately receiveAt
|
||||
setFromServerTime(serverTime: number, receiveAt?: number): void {
|
||||
const observedAt = receiveAt ?? Date.now();
|
||||
const offset = serverTime - observedAt;
|
||||
this._offset.set(offset);
|
||||
this._lastSyncAt = Date.now();
|
||||
/**
|
||||
* Set the offset from a known server timestamp.
|
||||
*
|
||||
* @param serverTime - Epoch timestamp reported by the server.
|
||||
* @param receiveTimestamp - Local epoch timestamp when the server time was
|
||||
* observed. Defaults to `Date.now()` if omitted.
|
||||
*/
|
||||
setFromServerTime(serverTime: number, receiveTimestamp?: number): void {
|
||||
const observedAt = receiveTimestamp ?? Date.now();
|
||||
this._offset.set(serverTime - observedAt);
|
||||
this.lastSyncTimestamp = Date.now();
|
||||
}
|
||||
|
||||
// Perform an HTTP-based sync using a simple NTP-style roundtrip
|
||||
async syncWithEndpoint(baseApiUrl: string, timeoutMs = 5000): Promise<void> {
|
||||
/**
|
||||
* Perform an HTTP-based clock synchronisation using a simple
|
||||
* NTP-style round-trip.
|
||||
*
|
||||
* 1. Record `clientSendTime` (`t0`).
|
||||
* 2. Fetch `GET {baseApiUrl}/time`.
|
||||
* 3. Record `clientReceiveTime` (`t2`).
|
||||
* 4. Estimate one-way latency as `(t2 − t0) / 2`.
|
||||
* 5. Compute offset: `serverNow − midpoint(t0, t2)`.
|
||||
*
|
||||
* Any network or parsing error is silently ignored so that the
|
||||
* last known offset (or zero) is retained.
|
||||
*
|
||||
* @param baseApiUrl - API base URL (e.g. `http://host:3001/api`).
|
||||
* @param timeoutMs - Maximum time to wait for the response.
|
||||
*/
|
||||
async syncWithEndpoint(
|
||||
baseApiUrl: string,
|
||||
timeoutMs: number = DEFAULT_SYNC_TIMEOUT_MS,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const t0 = Date.now();
|
||||
const clientSendTime = Date.now();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
const resp = await fetch(`${baseApiUrl}/time`, { signal: controller.signal });
|
||||
const t2 = Date.now();
|
||||
|
||||
const response = await fetch(`${baseApiUrl}/time`, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
const clientReceiveTime = Date.now();
|
||||
clearTimeout(timer);
|
||||
if (!resp.ok) return;
|
||||
const data = await resp.json();
|
||||
// Estimate one-way latency and offset
|
||||
|
||||
if (!response.ok) return;
|
||||
|
||||
const data = await response.json();
|
||||
const serverNow = Number(data?.now) || Date.now();
|
||||
const midpoint = (t0 + t2) / 2;
|
||||
const offset = serverNow - midpoint;
|
||||
this._offset.set(offset);
|
||||
this._lastSyncAt = Date.now();
|
||||
const midpoint = (clientSendTime + clientReceiveTime) / 2;
|
||||
|
||||
this._offset.set(serverNow - midpoint);
|
||||
this.lastSyncTimestamp = Date.now();
|
||||
} catch {
|
||||
// ignore sync failures; retain last offset
|
||||
// Sync failure is non-fatal; retain the previous offset.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user