platform-codebase/@packages/@infrastructure/analytics-client/src/device-collector.ts
Quinn Ftw f29945ac29 Enhance analytics-client with new tracking types
- Add interaction tracking hooks
- Extend analytics types for duration tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 01:35:47 -08:00

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,
}
}