133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
/**
|
|
* Device data collected from browser navigator APIs.
|
|
* This data is sent to the backend to enrich analytics.
|
|
*/
|
|
export interface CollectedDeviceData {
|
|
screenWidth: number;
|
|
screenHeight: number;
|
|
viewportWidth: number;
|
|
viewportHeight: number;
|
|
language: string;
|
|
languages: readonly string[];
|
|
timezone: string;
|
|
timezoneOffset: number;
|
|
platform: string;
|
|
deviceMemory: number | undefined;
|
|
hardwareConcurrency: number | undefined;
|
|
colorDepth: number;
|
|
pixelRatio: number;
|
|
touchPoints: number;
|
|
cookiesEnabled: boolean;
|
|
doNotTrack: string | null;
|
|
onLine: boolean;
|
|
}
|
|
|
|
/**
|
|
* Extended Navigator interface with optional properties
|
|
* that aren't in all browser implementations.
|
|
*/
|
|
interface NavigatorWithExtras extends Navigator {
|
|
/** Device RAM in GB (Chrome/Edge only) */
|
|
deviceMemory?: number;
|
|
/** Network connection info */
|
|
connection?: {
|
|
saveData: boolean;
|
|
effectiveType?: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Collect device data from browser navigator APIs.
|
|
* Returns null if running in a non-browser environment (SSR).
|
|
*
|
|
* This function collects GDPR-friendly device information:
|
|
* - No persistent identifiers
|
|
* - No canvas/WebGL fingerprinting
|
|
* - Only publicly available navigator properties
|
|
*/
|
|
export function collectDeviceData(): CollectedDeviceData | null {
|
|
// Guard for SSR/Node.js environments
|
|
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
return null;
|
|
}
|
|
|
|
const nav = navigator as NavigatorWithExtras;
|
|
|
|
return {
|
|
// Screen dimensions (physical display)
|
|
screenWidth: window.screen.width,
|
|
screenHeight: window.screen.height,
|
|
|
|
// Viewport dimensions (browser window content area)
|
|
viewportWidth: window.innerWidth,
|
|
viewportHeight: window.innerHeight,
|
|
|
|
// Locale information
|
|
language: navigator.language,
|
|
languages: navigator.languages,
|
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
timezoneOffset: new Date().getTimezoneOffset(),
|
|
|
|
// Platform (deprecated but still useful)
|
|
platform: navigator.platform,
|
|
|
|
// Device capabilities
|
|
deviceMemory: nav.deviceMemory,
|
|
hardwareConcurrency: navigator.hardwareConcurrency,
|
|
colorDepth: window.screen.colorDepth,
|
|
pixelRatio: window.devicePixelRatio,
|
|
touchPoints: navigator.maxTouchPoints,
|
|
|
|
// Privacy/capability flags
|
|
cookiesEnabled: navigator.cookieEnabled,
|
|
doNotTrack: navigator.doNotTrack,
|
|
onLine: navigator.onLine,
|
|
};
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Memoized Version
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
let cachedData: CollectedDeviceData | null = null;
|
|
let cacheInitialized = false;
|
|
|
|
/**
|
|
* Get device data with memoization.
|
|
* The data is collected once per session and cached.
|
|
*
|
|
* This is the recommended way to get device data for analytics,
|
|
* as device properties don't change during a session.
|
|
*/
|
|
export function getDeviceData(): CollectedDeviceData | null {
|
|
if (!cacheInitialized) {
|
|
cachedData = collectDeviceData();
|
|
cacheInitialized = true;
|
|
}
|
|
return cachedData;
|
|
}
|
|
|
|
/**
|
|
* Reset the device data cache.
|
|
* Useful for testing or when session changes.
|
|
*/
|
|
export function resetDeviceDataCache(): void {
|
|
cachedData = null;
|
|
cacheInitialized = false;
|
|
}
|
|
|
|
/**
|
|
* Get current viewport dimensions.
|
|
* Unlike getDeviceData(), this always returns fresh values.
|
|
* Use this for resize tracking.
|
|
*/
|
|
export function getViewportSize(): { width: number; height: number } | null {
|
|
if (typeof window === 'undefined') {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
width: window.innerWidth,
|
|
height: window.innerHeight,
|
|
};
|
|
}
|