// modules/api-manager/DeviceManager.js import { Logger } from '../../core/logger.js'; /** * Device APIs Manager - Geolocation, Sensors, Battery, Network, Vibration */ export class DeviceManager { constructor(config = {}) { this.config = config; this.activeWatchers = new Map(); this.sensorData = new Map(); // Check API support this.support = { geolocation: 'geolocation' in navigator, deviceMotion: 'DeviceMotionEvent' in window, deviceOrientation: 'DeviceOrientationEvent' in window, vibration: 'vibrate' in navigator, battery: 'getBattery' in navigator, networkInfo: 'connection' in navigator || 'mozConnection' in navigator || 'webkitConnection' in navigator, wakeLock: 'wakeLock' in navigator, bluetooth: 'bluetooth' in navigator, usb: 'usb' in navigator, serial: 'serial' in navigator }; Logger.info('[DeviceManager] Initialized with support:', this.support); // Initialize sensors if available this.initializeSensors(); } /** * Geolocation API */ geolocation = { // Get current position getCurrent: (options = {}) => { return new Promise((resolve, reject) => { if (!this.support.geolocation) { reject(new Error('Geolocation not supported')); return; } const defaultOptions = { enableHighAccuracy: true, timeout: 10000, maximumAge: 60000, ...options }; navigator.geolocation.getCurrentPosition( (position) => { const location = this.enhanceLocationData(position); Logger.info('[DeviceManager] Location acquired:', { lat: location.latitude, lng: location.longitude, accuracy: location.accuracy }); resolve(location); }, (error) => { Logger.error('[DeviceManager] Geolocation failed:', error.message); reject(error); }, defaultOptions ); }); }, // Watch position changes watch: (callback, options = {}) => { if (!this.support.geolocation) { throw new Error('Geolocation not supported'); } const defaultOptions = { enableHighAccuracy: true, timeout: 30000, maximumAge: 10000, ...options }; const watchId = navigator.geolocation.watchPosition( (position) => { const location = this.enhanceLocationData(position); callback(location); }, (error) => { Logger.error('[DeviceManager] Location watch failed:', error.message); callback({ error }); }, defaultOptions ); this.activeWatchers.set(`geo_${watchId}`, { type: 'geolocation', id: watchId, stop: () => { navigator.geolocation.clearWatch(watchId); this.activeWatchers.delete(`geo_${watchId}`); } }); Logger.info('[DeviceManager] Location watch started:', watchId); return { id: watchId, stop: () => { navigator.geolocation.clearWatch(watchId); this.activeWatchers.delete(`geo_${watchId}`); Logger.info('[DeviceManager] Location watch stopped:', watchId); } }; }, // Calculate distance between two points distance: (pos1, pos2) => { const R = 6371; // Earth's radius in km const dLat = this.toRadians(pos2.latitude - pos1.latitude); const dLon = this.toRadians(pos2.longitude - pos1.longitude); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.toRadians(pos1.latitude)) * Math.cos(this.toRadians(pos2.latitude)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const distance = R * c; return { kilometers: distance, miles: distance * 0.621371, meters: distance * 1000 }; } }; /** * Device Motion and Orientation */ motion = { // Start motion detection start: (callback, options = {}) => { if (!this.support.deviceMotion) { throw new Error('Device Motion not supported'); } const handler = (event) => { const motionData = { acceleration: event.acceleration, accelerationIncludingGravity: event.accelerationIncludingGravity, rotationRate: event.rotationRate, interval: event.interval, timestamp: event.timeStamp, // Enhanced data totalAcceleration: this.calculateTotalAcceleration(event.acceleration), shake: this.detectShake(event.accelerationIncludingGravity), orientation: this.getDeviceOrientation(event) }; callback(motionData); }; // Request permission for iOS 13+ if (typeof DeviceMotionEvent.requestPermission === 'function') { DeviceMotionEvent.requestPermission().then(response => { if (response === 'granted') { window.addEventListener('devicemotion', handler); } }); } else { window.addEventListener('devicemotion', handler); } const watcherId = this.generateId('motion'); this.activeWatchers.set(watcherId, { type: 'motion', handler, stop: () => { window.removeEventListener('devicemotion', handler); this.activeWatchers.delete(watcherId); } }); Logger.info('[DeviceManager] Motion detection started'); return { id: watcherId, stop: () => { window.removeEventListener('devicemotion', handler); this.activeWatchers.delete(watcherId); Logger.info('[DeviceManager] Motion detection stopped'); } }; }, // Start orientation detection startOrientation: (callback, options = {}) => { if (!this.support.deviceOrientation) { throw new Error('Device Orientation not supported'); } const handler = (event) => { const orientationData = { alpha: event.alpha, // Z axis (0-360) beta: event.beta, // X axis (-180 to 180) gamma: event.gamma, // Y axis (-90 to 90) absolute: event.absolute, timestamp: event.timeStamp, // Enhanced data compass: this.calculateCompass(event.alpha), tilt: this.calculateTilt(event.beta, event.gamma), rotation: this.getRotationState(event) }; callback(orientationData); }; // Request permission for iOS 13+ if (typeof DeviceOrientationEvent.requestPermission === 'function') { DeviceOrientationEvent.requestPermission().then(response => { if (response === 'granted') { window.addEventListener('deviceorientation', handler); } }); } else { window.addEventListener('deviceorientation', handler); } const watcherId = this.generateId('orientation'); this.activeWatchers.set(watcherId, { type: 'orientation', handler, stop: () => { window.removeEventListener('deviceorientation', handler); this.activeWatchers.delete(watcherId); } }); Logger.info('[DeviceManager] Orientation detection started'); return { id: watcherId, stop: () => { window.removeEventListener('deviceorientation', handler); this.activeWatchers.delete(watcherId); Logger.info('[DeviceManager] Orientation detection stopped'); } }; } }; /** * Vibration API */ vibration = { // Simple vibration vibrate: (pattern) => { if (!this.support.vibration) { Logger.warn('[DeviceManager] Vibration not supported'); return false; } try { navigator.vibrate(pattern); Logger.info('[DeviceManager] Vibration triggered:', pattern); return true; } catch (error) { Logger.error('[DeviceManager] Vibration failed:', error); return false; } }, // Predefined patterns patterns: { short: 200, long: 600, double: [200, 100, 200], triple: [200, 100, 200, 100, 200], sos: [100, 30, 100, 30, 100, 200, 200, 30, 200, 30, 200, 200, 100, 30, 100, 30, 100], heartbeat: [100, 30, 100, 130, 40, 30, 40, 30, 100], notification: [200, 100, 200], success: [100], error: [300, 100, 300], warning: [200, 100, 200, 100, 200] }, // Stop vibration stop: () => { if (this.support.vibration) { navigator.vibrate(0); Logger.info('[DeviceManager] Vibration stopped'); } }, // Haptic feedback helpers success: () => this.vibration.vibrate(this.vibration.patterns.success), error: () => this.vibration.vibrate(this.vibration.patterns.error), warning: () => this.vibration.vibrate(this.vibration.patterns.warning), notification: () => this.vibration.vibrate(this.vibration.patterns.notification) }; /** * Battery API */ battery = { // Get battery status get: async () => { if (!this.support.battery) { Logger.warn('[DeviceManager] Battery API not supported'); return null; } try { const battery = await navigator.getBattery(); const batteryInfo = { level: Math.round(battery.level * 100), charging: battery.charging, chargingTime: battery.chargingTime, dischargingTime: battery.dischargingTime, // Enhanced data status: this.getBatteryStatus(battery), timeRemaining: this.formatBatteryTime(battery) }; Logger.info('[DeviceManager] Battery status:', batteryInfo); return batteryInfo; } catch (error) { Logger.error('[DeviceManager] Battery status failed:', error); return null; } }, // Watch battery changes watch: async (callback) => { if (!this.support.battery) { throw new Error('Battery API not supported'); } try { const battery = await navigator.getBattery(); const events = ['chargingchange', 'levelchange', 'chargingtimechange', 'dischargingtimechange']; const handlers = []; events.forEach(eventType => { const handler = () => { const batteryInfo = { level: Math.round(battery.level * 100), charging: battery.charging, chargingTime: battery.chargingTime, dischargingTime: battery.dischargingTime, status: this.getBatteryStatus(battery), timeRemaining: this.formatBatteryTime(battery), event: eventType }; callback(batteryInfo); }; battery.addEventListener(eventType, handler); handlers.push({ event: eventType, handler }); }); const watcherId = this.generateId('battery'); this.activeWatchers.set(watcherId, { type: 'battery', battery, handlers, stop: () => { handlers.forEach(({ event, handler }) => { battery.removeEventListener(event, handler); }); this.activeWatchers.delete(watcherId); } }); Logger.info('[DeviceManager] Battery watch started'); return { id: watcherId, stop: () => { handlers.forEach(({ event, handler }) => { battery.removeEventListener(event, handler); }); this.activeWatchers.delete(watcherId); Logger.info('[DeviceManager] Battery watch stopped'); } }; } catch (error) { Logger.error('[DeviceManager] Battery watch failed:', error); throw error; } } }; /** * Network Information API */ network = { // Get connection info get: () => { if (!this.support.networkInfo) { Logger.warn('[DeviceManager] Network Information not supported'); return null; } const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; return { effectiveType: connection.effectiveType, downlink: connection.downlink, rtt: connection.rtt, saveData: connection.saveData, // Enhanced data speed: this.getConnectionSpeed(connection), quality: this.getConnectionQuality(connection), recommendation: this.getNetworkRecommendation(connection) }; }, // Watch network changes watch: (callback) => { if (!this.support.networkInfo) { throw new Error('Network Information not supported'); } const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; const handler = () => { const networkInfo = { effectiveType: connection.effectiveType, downlink: connection.downlink, rtt: connection.rtt, saveData: connection.saveData, speed: this.getConnectionSpeed(connection), quality: this.getConnectionQuality(connection), recommendation: this.getNetworkRecommendation(connection) }; callback(networkInfo); }; connection.addEventListener('change', handler); const watcherId = this.generateId('network'); this.activeWatchers.set(watcherId, { type: 'network', connection, handler, stop: () => { connection.removeEventListener('change', handler); this.activeWatchers.delete(watcherId); } }); Logger.info('[DeviceManager] Network watch started'); return { id: watcherId, stop: () => { connection.removeEventListener('change', handler); this.activeWatchers.delete(watcherId); Logger.info('[DeviceManager] Network watch stopped'); } }; } }; /** * Wake Lock API */ wakeLock = { // Request wake lock request: async (type = 'screen') => { if (!this.support.wakeLock) { Logger.warn('[DeviceManager] Wake Lock not supported'); return null; } try { const wakeLock = await navigator.wakeLock.request(type); Logger.info(`[DeviceManager] Wake lock acquired: ${type}`); return { type: wakeLock.type, release: () => { wakeLock.release(); Logger.info(`[DeviceManager] Wake lock released: ${type}`); } }; } catch (error) { Logger.error('[DeviceManager] Wake lock failed:', error); throw error; } } }; // Helper methods initializeSensors() { // Initialize any sensors that need setup if (this.support.deviceMotion || this.support.deviceOrientation) { // Store baseline sensor data for comparison this.sensorData.set('motionBaseline', { x: 0, y: 0, z: 0 }); this.sensorData.set('shakeThreshold', this.config.shakeThreshold || 15); } } enhanceLocationData(position) { return { latitude: position.coords.latitude, longitude: position.coords.longitude, accuracy: position.coords.accuracy, altitude: position.coords.altitude, altitudeAccuracy: position.coords.altitudeAccuracy, heading: position.coords.heading, speed: position.coords.speed, timestamp: position.timestamp, // Enhanced data coordinates: `${position.coords.latitude},${position.coords.longitude}`, accuracyLevel: this.getAccuracyLevel(position.coords.accuracy), mapUrl: `https://maps.google.com/?q=${position.coords.latitude},${position.coords.longitude}` }; } getAccuracyLevel(accuracy) { if (accuracy <= 5) return 'excellent'; if (accuracy <= 10) return 'good'; if (accuracy <= 50) return 'fair'; return 'poor'; } calculateTotalAcceleration(acceleration) { if (!acceleration) return 0; const x = acceleration.x || 0; const y = acceleration.y || 0; const z = acceleration.z || 0; return Math.sqrt(x * x + y * y + z * z); } detectShake(acceleration) { if (!acceleration) return false; const threshold = this.sensorData.get('shakeThreshold'); const x = Math.abs(acceleration.x || 0); const y = Math.abs(acceleration.y || 0); const z = Math.abs(acceleration.z || 0); return (x > threshold || y > threshold || z > threshold); } getDeviceOrientation(event) { const acceleration = event.accelerationIncludingGravity; if (!acceleration) return 'unknown'; const x = acceleration.x || 0; const y = acceleration.y || 0; const z = acceleration.z || 0; if (Math.abs(x) > Math.abs(y) && Math.abs(x) > Math.abs(z)) { return x > 0 ? 'landscape-right' : 'landscape-left'; } else if (Math.abs(y) > Math.abs(z)) { return y > 0 ? 'portrait-upside-down' : 'portrait'; } else { return z > 0 ? 'face-down' : 'face-up'; } } calculateCompass(alpha) { if (alpha === null) return null; const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']; const index = Math.round(alpha / 45) % 8; return { degrees: Math.round(alpha), direction: directions[index], cardinal: this.getCardinalDirection(alpha) }; } getCardinalDirection(alpha) { if (alpha >= 337.5 || alpha < 22.5) return 'North'; if (alpha >= 22.5 && alpha < 67.5) return 'Northeast'; if (alpha >= 67.5 && alpha < 112.5) return 'East'; if (alpha >= 112.5 && alpha < 157.5) return 'Southeast'; if (alpha >= 157.5 && alpha < 202.5) return 'South'; if (alpha >= 202.5 && alpha < 247.5) return 'Southwest'; if (alpha >= 247.5 && alpha < 292.5) return 'West'; if (alpha >= 292.5 && alpha < 337.5) return 'Northwest'; return 'Unknown'; } calculateTilt(beta, gamma) { return { x: Math.round(beta || 0), y: Math.round(gamma || 0), magnitude: Math.round(Math.sqrt((beta || 0) ** 2 + (gamma || 0) ** 2)) }; } getRotationState(event) { const { alpha, beta, gamma } = event; // Determine if device is being rotated significantly const rotationThreshold = 10; const isRotating = Math.abs(beta) > rotationThreshold || Math.abs(gamma) > rotationThreshold; return { isRotating, intensity: isRotating ? Math.max(Math.abs(beta), Math.abs(gamma)) : 0 }; } getBatteryStatus(battery) { const level = battery.level * 100; if (battery.charging) return 'charging'; if (level <= 10) return 'critical'; if (level <= 20) return 'low'; if (level <= 50) return 'medium'; return 'high'; } formatBatteryTime(battery) { const time = battery.charging ? battery.chargingTime : battery.dischargingTime; if (time === Infinity || isNaN(time)) return 'Unknown'; const hours = Math.floor(time / 3600); const minutes = Math.floor((time % 3600) / 60); return `${hours}h ${minutes}m`; } getConnectionSpeed(connection) { const downlink = connection.downlink; if (downlink >= 10) return 'fast'; if (downlink >= 1.5) return 'good'; if (downlink >= 0.5) return 'slow'; return 'very-slow'; } getConnectionQuality(connection) { const effectiveType = connection.effectiveType; switch (effectiveType) { case '4g': return 'excellent'; case '3g': return 'good'; case '2g': return 'poor'; case 'slow-2g': return 'very-poor'; default: return 'unknown'; } } getNetworkRecommendation(connection) { const quality = this.getConnectionQuality(connection); switch (quality) { case 'excellent': return 'Full quality content recommended'; case 'good': return 'Moderate quality content recommended'; case 'poor': return 'Light content only, avoid large files'; case 'very-poor': return 'Text-only content recommended'; default: return 'Monitor connection quality'; } } toRadians(degrees) { return degrees * (Math.PI / 180); } generateId(prefix = 'device') { return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Stop all active watchers */ stopAllWatchers() { this.activeWatchers.forEach(watcher => { watcher.stop(); }); this.activeWatchers.clear(); Logger.info('[DeviceManager] All watchers stopped'); } /** * Get device capabilities summary */ getCapabilities() { return { support: this.support, activeWatchers: this.activeWatchers.size, watcherTypes: Array.from(this.activeWatchers.values()).map(w => w.type) }; } }