@@ -3,6 +3,29 @@ import { env } from '$env/dynamic/public';
33const SECRET = env . PUBLIC_CONSOLE_FINGERPRINT_KEY ?? '' ;
44const CACHE_TTL_MS = 60 * 60 * 1000 ; // 1 hour
55
6+ /** Cached server timestamp and the local time it was fetched at, for interpolation. */
7+ let serverTimeCache : { serverSecs : number ; fetchedAtMs : number } | null = null ;
8+
9+ /**
10+ * Cache the server's clock so fingerprint timestamps always align with the
11+ * backend's clock, regardless of local clock drift.
12+ *
13+ * @param serverTimeSecs - the server's unix timestamp in seconds
14+ * (e.g. parsed from a response Date header)
15+ */
16+ export function syncServerTime ( serverTimeSecs : number ) : void {
17+ if ( serverTimeCache ) return ;
18+ serverTimeCache = { serverSecs : serverTimeSecs , fetchedAtMs : Date . now ( ) } ;
19+ }
20+
21+ function getServerTimestamp ( ) : number {
22+ if ( ! serverTimeCache ) {
23+ return Math . floor ( Date . now ( ) / 1000 ) ;
24+ }
25+ const elapsedSecs = Math . floor ( ( Date . now ( ) - serverTimeCache . fetchedAtMs ) / 1000 ) ;
26+ return serverTimeCache . serverSecs + elapsedSecs ;
27+ }
28+
629async function sha256 ( message : string ) : Promise < string > {
730 if ( ! crypto ?. subtle ) {
831 console . warn ( 'crypto.subtle unavailable, fingerprinting disabled' ) ;
@@ -204,7 +227,7 @@ export async function generateFingerprintToken(): Promise<string> {
204227
205228 const signals : BrowserSignals = {
206229 ...staticSignals ,
207- timestamp : Math . floor ( Date . now ( ) / 1000 )
230+ timestamp : getServerTimestamp ( )
208231 } ;
209232
210233 const payload = JSON . stringify ( signals ) ;
0 commit comments