Live video on the AT Protocol
1import { Platform } from "react-native";
2
3let timeOffset = 0;
4let hasWarned = false;
5let OriginalDate: DateConstructor = Date;
6
7const CLOCK_DRIFT_THRESHOLD_MS = 5000; // 5 seconds
8
9export function getTimeOffset(): number {
10 return timeOffset;
11}
12
13export function setTimeOffset(offset: number): void {
14 timeOffset = offset;
15}
16
17export function checkClockDrift(serverTime: string): {
18 hasDrift: boolean;
19 driftMs: number;
20 driftSeconds: number;
21} {
22 const serverDate = new Date(serverTime);
23 const clientDate = new Date();
24 const drift = Math.abs(serverDate.getTime() - clientDate.getTime());
25
26 if (drift > CLOCK_DRIFT_THRESHOLD_MS) {
27 const driftSeconds = Math.round(drift / 1000);
28 if (!hasWarned) {
29 hasWarned = true;
30 console.warn(
31 `clock drift detected: ${driftSeconds}s difference from server time. ` +
32 `this may cause issues with time-sensitive operations. ` +
33 `please sync your system clock.`,
34 );
35 }
36 return { hasDrift: true, driftMs: drift, driftSeconds };
37 } else {
38 return {
39 hasDrift: false,
40 driftMs: drift,
41 driftSeconds: Math.round(drift / 1000),
42 };
43 }
44}
45
46export function syncTimeWithServer(
47 serverTime: string,
48 networkLatencyMs: number,
49): void {
50 const serverDate = new OriginalDate(serverTime);
51 const clientDate = new OriginalDate();
52 const offset = serverDate.getTime() - clientDate.getTime() - networkLatencyMs;
53
54 setTimeOffset(offset);
55}
56
57export function getSyncedDate(): Date {
58 const now = new Date();
59 if (timeOffset !== 0) {
60 return new Date(now.getTime() + timeOffset);
61 }
62 return now;
63}
64
65export function getSystemDate(): Date {
66 return new OriginalDate();
67}
68
69export function getSystemTime(): number {
70 return OriginalDate.now();
71}
72
73export function initializeTimeSync(): void {
74 if (Platform.OS !== "web") {
75 return;
76 }
77
78 // store original Date
79 OriginalDate = Date;
80 const OriginalDatePrototype = OriginalDate.prototype;
81
82 // create patched Date constructor
83 function PatchedDate(this: any, ...args: any[]): any {
84 // If called as a function (no `new`), forward to original Date to get the string form
85 if (!(this instanceof PatchedDate)) {
86 return OriginalDate.apply(undefined, args as any);
87 }
88
89 // If called as a constructor, construct a Date with synced time when no args provided
90 if (args.length === 0) {
91 const syncedTime = OriginalDate.now() + timeOffset;
92 return Reflect.construct(OriginalDate, [syncedTime], PatchedDate);
93 }
94
95 // Otherwise construct with the provided arguments
96 return Reflect.construct(OriginalDate, args, PatchedDate);
97 }
98
99 // copy static methods
100 PatchedDate.now = function (): number {
101 return OriginalDate.now() + timeOffset;
102 };
103
104 PatchedDate.parse = OriginalDate.parse;
105 PatchedDate.UTC = OriginalDate.UTC;
106
107 // copy prototype
108 PatchedDate.prototype = OriginalDatePrototype;
109
110 // replace global Date
111 (globalThis as any).Date = PatchedDate;
112}