Skip to main content
โšก Calmops

Geolocation and Hardware APIs: Complete Guide to Device Capabilities in Web Applications

Modern web applications can do far more than display content. Through geolocation and hardware APIs, web apps can access device location, camera, microphone, accelerometer, gyroscope, battery status, and dozens of other hardware features. This capability transforms what’s possible on the web, enabling location-based services, augmented reality experiences, fitness tracking, IoT integrations, and much more.

Why This Matters:

  • 78% of users expect personalized location-based experiences
  • Web apps can now access 50+ device APIs for hardware capabilities
  • PWAs with hardware access see 3-5x higher engagement rates
  • Cross-platform development saves 40-60% development time vs native apps

However, with great power comes great responsibility. These APIs access sensitive device capabilities and personal information. Implementing them correctly requires understanding not just the technical implementation, but also security, privacy, performance, and user experience considerations.

What You’ll Learn:

  • Complete implementation of geolocation and 15+ hardware APIs
  • Permission handling and error management strategies
  • Security and privacy best practices with GDPR compliance
  • Performance optimization for battery and network efficiency
  • Testing strategies for device APIs
  • Real-world use cases and production patterns
  • Accessibility considerations for hardware features
  • Cross-browser compatibility and polyfills

Prerequisites:

  • JavaScript ES6+ knowledge
  • Understanding of async/await and Promises
  • Basic knowledge of web security (HTTPS, CORS)
  • Familiarity with browser DevTools

Understanding Geolocation and Hardware APIs

What Are These APIs?

Geolocation API: Provides access to a user’s geographic location (latitude, longitude, altitude, accuracy).

Hardware APIs: Provide access to device hardware and sensors:

API Capability Use Cases Browser Support
MediaStream Camera, microphone Video calls, recording, scanning โœ… Excellent
Geolocation GPS location Maps, local search, tracking โœ… Excellent
DeviceMotion Accelerometer, gyroscope Games, fitness, shake detection โœ… Excellent
DeviceOrientation Compass, tilt AR, 360ยฐ viewers, compass โœ… Excellent
Battery Status Battery level, charging Power optimization โš ๏ธ Limited
Vibration Haptic feedback Games, notifications โœ… Good
Screen Wake Lock Prevent screen sleep Video, recipes, presentations โœ… Growing
Ambient Light Light sensor Auto brightness โš ๏ธ Experimental
Web Bluetooth Bluetooth devices IoT, wearables, peripherals โš ๏ธ Limited
Web NFC NFC tags Payments, access control โš ๏ธ Experimental
Web USB USB devices Hardware development โš ๏ธ Limited
Gamepad Game controllers Gaming โœ… Good
Clipboard Copy/paste Productivity apps โœ… Excellent
Pointer Lock Mouse capture Gaming, 3D modeling โœ… Good
Fullscreen Fullscreen mode Presentations, games โœ… Excellent

Why They Matter

These APIs enable:

  • Location-based services: Maps, navigation, local search, location check-ins
  • Augmented reality: Virtual try-ons, AR games, spatial computing
  • Accessibility: Voice control, gesture recognition, adaptive interfaces
  • Fitness and health: Step counting, activity tracking, workout monitoring
  • Immersive experiences: VR, motion-controlled games, interactive installations
  • Convenience: Auto-fill location, context-aware features, personalization

The Geolocation API

The Geolocation API is one of the most commonly used hardware APIs. It provides access to a user’s location with their explicit permission.

Basic Implementation

// Check if Geolocation API is supported
if ('geolocation' in navigator) {
    // Get current position
    navigator.geolocation.getCurrentPosition(
        (position) => {
            const { latitude, longitude, accuracy } = position.coords;
            console.log(`Location: ${latitude}, ${longitude}`);
            console.log(`Accuracy: ${accuracy} meters`);
        },
        (error) => {
            console.error('Geolocation error:', error.message);
        },
        {
            enableHighAccuracy: true,
            timeout: 10000,
            maximumAge: 0
        }
    );
} else {
    console.log('Geolocation not supported');
}

Position Object Properties

The position object contains:

position.coords = {
    latitude: 40.7128,           // Degrees
    longitude: -74.0060,         // Degrees
    accuracy: 50,                // Meters
    altitude: 10,                // Meters (may be null)
    altitudeAccuracy: 5,         // Meters (may be null)
    heading: 90,                 // Degrees (0-360, null if stationary)
    speed: 5.5                   // Meters per second (null if stationary)
};

position.timestamp = 1234567890; // Milliseconds since epoch

Watching Location Changes

For continuous location tracking:

class LocationTracker {
    constructor() {
        this.watchId = null;
        this.locations = [];
    }
    
    startTracking() {
        if (!('geolocation' in navigator)) {
            console.error('Geolocation not supported');
            return;
        }
        
        this.watchId = navigator.geolocation.watchPosition(
            (position) => this.handlePositionUpdate(position),
            (error) => this.handleError(error),
            {
                enableHighAccuracy: true,
                timeout: 5000,
                maximumAge: 0
            }
        );
    }
    
    handlePositionUpdate(position) {
        const { latitude, longitude, accuracy, timestamp } = position.coords;
        
        const location = {
            latitude,
            longitude,
            accuracy,
            timestamp,
            distance: this.calculateDistance()
        };
        
        this.locations.push(location);
        this.updateUI(location);
    }
    
    calculateDistance() {
        if (this.locations.length < 2) return 0;
        
        const prev = this.locations[this.locations.length - 2];
        const curr = this.locations[this.locations.length - 1];
        
        return this.haversineDistance(
            prev.latitude, prev.longitude,
            curr.latitude, curr.longitude
        );
    }
    
    haversineDistance(lat1, lon1, lat2, lon2) {
        const R = 6371; // Earth's radius in km
        const dLat = (lat2 - lat1) * Math.PI / 180;
        const dLon = (lon2 - lon1) * Math.PI / 180;
        
        const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                  Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
                  Math.sin(dLon / 2) * Math.sin(dLon / 2);
        
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }
    
    handleError(error) {
        switch (error.code) {
            case error.PERMISSION_DENIED:
                console.error('User denied geolocation permission');
                break;
            case error.POSITION_UNAVAILABLE:
                console.error('Position information unavailable');
                break;
            case error.TIMEOUT:
                console.error('Geolocation request timed out');
                break;
        }
    }
    
    stopTracking() {
        if (this.watchId !== null) {
            navigator.geolocation.clearWatch(this.watchId);
            this.watchId = null;
        }
    }
    
    updateUI(location) {
        // Update map, display location, etc.
        console.log(`Current location: ${location.latitude}, ${location.longitude}`);
    }
}

// Usage
const tracker = new LocationTracker();
tracker.startTracking();

// Stop tracking when done
// tracker.stopTracking();

Advanced Geolocation Patterns

Geofencing

Detect when users enter or leave specific areas:

class Geofence {
    constructor(center, radius) {
        this.center = center; // { lat, lng }
        this.radius = radius; // meters
        this.listeners = new Set();
    }
    
    isInside(point) {
        const distance = this.calculateDistance(
            this.center.lat, this.center.lng,
            point.lat, point.lng
        );
        return distance <= this.radius;
    }
    
    calculateDistance(lat1, lon1, lat2, lon2) {
        const R = 6371000; // Earth's radius in meters
        const dLat = (lat2 - lat1) * Math.PI / 180;
        const dLon = (lon2 - lon1) * Math.PI / 180;
        
        const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                  Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
                  Math.sin(dLon / 2) * Math.sin(dLon / 2);
        
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }
    
    onEnter(callback) {
        this.listeners.add({ type: 'enter', callback });
    }
    
    onExit(callback) {
        this.listeners.add({ type: 'exit', callback });
    }
}

class GeofenceManager {
    constructor() {
        this.geofences = new Map();
        this.watchId = null;
        this.lastStatus = new Map();
    }
    
    addGeofence(id, center, radius) {
        const geofence = new Geofence(center, radius);
        this.geofences.set(id, geofence);
        return geofence;
    }
    
    startMonitoring() {
        this.watchId = navigator.geolocation.watchPosition(
            (position) => this.checkGeofences(position),
            (error) => console.error('Geofence error:', error),
            { enableHighAccuracy: true, maximumAge: 0 }
        );
    }
    
    checkGeofences(position) {
        const point = {
            lat: position.coords.latitude,
            lng: position.coords.longitude
        };
        
        this.geofences.forEach((geofence, id) => {
            const isInside = geofence.isInside(point);
            const wasInside = this.lastStatus.get(id);
            
            if (isInside && !wasInside) {
                // Entered geofence
                geofence.listeners.forEach(listener => {
                    if (listener.type === 'enter') {
                        listener.callback(id, point);
                    }
                });
            } else if (!isInside && wasInside) {
                // Exited geofence
                geofence.listeners.forEach(listener => {
                    if (listener.type === 'exit') {
                        listener.callback(id, point);
                    }
                });
            }
            
            this.lastStatus.set(id, isInside);
        });
    }
    
    stopMonitoring() {
        if (this.watchId !== null) {
            navigator.geolocation.clearWatch(this.watchId);
        }
    }
}

// Usage
const manager = new GeofenceManager();

// Add geofence around office
const officeGeofence = manager.addGeofence(
    'office',
    { lat: 40.7128, lng: -74.0060 },
    100 // 100 meters radius
);

officeGeofence.onEnter((id, location) => {
    console.log('Entered office area');
    // Auto check-in, enable office mode, etc.
});

officeGeofence.onExit((id, location) => {
    console.log('Left office area');
    // Auto check-out, disable office mode, etc.
});

manager.startMonitoring();

Route Tracking with Speed and Distance

class RouteTracker {
    constructor() {
        this.route = [];
        this.totalDistance = 0;
        this.startTime = null;
        this.watchId = null;
    }
    
    start() {
        this.startTime = Date.now();
        this.route = [];
        this.totalDistance = 0;
        
        this.watchId = navigator.geolocation.watchPosition(
            (position) => this.addPoint(position),
            (error) => console.error('Route tracking error:', error),
            {
                enableHighAccuracy: true,
                timeout: 5000,
                maximumAge: 0
            }
        );
    }
    
    addPoint(position) {
        const point = {
            lat: position.coords.latitude,
            lng: position.coords.longitude,
            altitude: position.coords.altitude,
            accuracy: position.coords.accuracy,
            speed: position.coords.speed,
            timestamp: position.timestamp
        };
        
        if (this.route.length > 0) {
            const lastPoint = this.route[this.route.length - 1];
            const distance = this.calculateDistance(
                lastPoint.lat, lastPoint.lng,
                point.lat, point.lng
            );
            
            // Filter out noise (points too close)
            if (distance > 5) { // 5 meters threshold
                this.totalDistance += distance;
                this.route.push(point);
                this.updateStats();
            }
        } else {
            this.route.push(point);
        }
    }
    
    calculateDistance(lat1, lon1, lat2, lon2) {
        const R = 6371000; // meters
        const dLat = (lat2 - lat1) * Math.PI / 180;
        const dLon = (lon2 - lon1) * Math.PI / 180;
        
        const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                  Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
                  Math.sin(dLon / 2) * Math.sin(dLon / 2);
        
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }
    
    updateStats() {
        const duration = (Date.now() - this.startTime) / 1000; // seconds
        const avgSpeed = this.totalDistance / duration; // m/s
        
        const stats = {
            distance: this.totalDistance,
            duration: duration,
            avgSpeed: avgSpeed * 3.6, // km/h
            points: this.route.length
        };
        
        this.onStatsUpdate(stats);
    }
    
    onStatsUpdate(stats) {
        console.log(`Distance: ${(stats.distance / 1000).toFixed(2)} km`);
        console.log(`Duration: ${Math.floor(stats.duration / 60)} min`);
        console.log(`Avg Speed: ${stats.avgSpeed.toFixed(1)} km/h`);
    }
    
    stop() {
        if (this.watchId !== null) {
            navigator.geolocation.clearWatch(this.watchId);
        }
        return this.getRouteData();
    }
    
    getRouteData() {
        return {
            route: this.route,
            totalDistance: this.totalDistance,
            duration: (Date.now() - this.startTime) / 1000,
            startTime: this.startTime,
            endTime: Date.now()
        };
    }
    
    exportGPX() {
        // Export route as GPX format
        let gpx = '<?xml version="1.0" encoding="UTF-8"?>\n';
        gpx += '<gpx version="1.1" creator="RouteTracker">\n';
        gpx += '  <trk>\n    <trkseg>\n';
        
        this.route.forEach(point => {
            gpx += `      <trkpt lat="${point.lat}" lon="${point.lng}">\n`;
            if (point.altitude) {
                gpx += `        <ele>${point.altitude}</ele>\n`;
            }
            gpx += `        <time>${new Date(point.timestamp).toISOString()}</time>\n`;
            gpx += '      </trkpt>\n';
        });
        
        gpx += '    </trkseg>\n  </trk>\n</gpx>';
        return gpx;
    }
}

// Usage
const routeTracker = new RouteTracker();
routeTracker.start();

// Later...
const routeData = routeTracker.stop();
const gpxData = routeTracker.exportGPX();

Hardware APIs

Camera and Microphone (MediaStream API)

Access camera and microphone for video/audio capture:

class MediaCapture {
    constructor() {
        this.stream = null;
        this.mediaRecorder = null;
    }
    
    async requestCameraAccess() {
        try {
            this.stream = await navigator.mediaDevices.getUserMedia({
                video: {
                    width: { ideal: 1280 },
                    height: { ideal: 720 },
                    facingMode: 'user'
                },
                audio: true
            });
            
            // Display video stream
            const video = document.getElementById('video');
            video.srcObject = this.stream;
            
            return this.stream;
        } catch (error) {
            console.error('Camera access denied:', error);
            throw error;
        }
    }
    
    async requestMicrophoneOnly() {
        try {
            this.stream = await navigator.mediaDevices.getUserMedia({
                audio: {
                    echoCancellation: true,
                    noiseSuppression: true,
                    autoGainControl: true
                }
            });
            
            return this.stream;
        } catch (error) {
            console.error('Microphone access denied:', error);
            throw error;
        }
    }
    
    startRecording() {
        if (!this.stream) {
            console.error('No media stream available');
            return;
        }
        
        this.mediaRecorder = new MediaRecorder(this.stream);
        const chunks = [];
        
        this.mediaRecorder.ondataavailable = (event) => {
            chunks.push(event.data);
        };
        
        this.mediaRecorder.onstop = () => {
            const blob = new Blob(chunks, { type: 'video/webm' });
            this.handleRecordedMedia(blob);
        };
        
        this.mediaRecorder.start();
    }
    
    stopRecording() {
        if (this.mediaRecorder) {
            this.mediaRecorder.stop();
        }
    }
    
    handleRecordedMedia(blob) {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `recording-${Date.now()}.webm`;
        a.click();
    }
    
    stopStream() {
        if (this.stream) {
            this.stream.getTracks().forEach(track => track.stop());
            this.stream = null;
        }
    }
}

// Usage
const capture = new MediaCapture();
document.getElementById('camera-btn').addEventListener('click', async () => {
    await capture.requestCameraAccess();
});

Motion and Orientation APIs

Access device accelerometer and gyroscope:

class MotionDetector {
    constructor() {
        this.isSupported = {
            deviceMotion: 'DeviceMotionEvent' in window,
            deviceOrientation: 'DeviceOrientationEvent' in window,
            permission: 'DeviceMotionEvent' in window && 
                       typeof DeviceMotionEvent !== 'undefined' &&
                       typeof DeviceMotionEvent.requestPermission === 'function'
        };
    }
    
    async requestPermission() {
        if (!this.isSupported.permission) {
            console.log('Permission request not needed');
            return true;
        }
        
        try {
            const permission = await DeviceMotionEvent.requestPermission();
            return permission === 'granted';
        } catch (error) {
            console.error('Permission request failed:', error);
            return false;
        }
    }
    
    startMotionTracking() {
        if (!this.isSupported.deviceMotion) {
            console.error('Device Motion API not supported');
            return;
        }
        
        window.addEventListener('devicemotion', (event) => {
            const { x, y, z } = event.acceleration;
            const { alpha, beta, gamma } = event.rotationRate;
            
            console.log('Acceleration:', { x, y, z });
            console.log('Rotation:', { alpha, beta, gamma });
            
            this.handleMotion({ x, y, z }, { alpha, beta, gamma });
        });
    }
    
    startOrientationTracking() {
        if (!this.isSupported.deviceOrientation) {
            console.error('Device Orientation API not supported');
            return;
        }
        
        window.addEventListener('deviceorientation', (event) => {
            const { alpha, beta, gamma } = event;
            
            console.log('Orientation:', { alpha, beta, gamma });
            this.handleOrientation({ alpha, beta, gamma });
        });
    }
    
    handleMotion(acceleration, rotation) {
        // Detect shake
        const magnitude = Math.sqrt(
            acceleration.x ** 2 + 
            acceleration.y ** 2 + 
            acceleration.z ** 2
        );
        
        if (magnitude > 25) {
            console.log('Device shaken!');
            this.onShake();
        }
    }
    
    handleOrientation(orientation) {
        // Use orientation for game controls, AR, etc.
        const { alpha, beta, gamma } = orientation;
        
        // Rotate element based on device orientation
        const element = document.getElementById('rotating-element');
        if (element) {
            element.style.transform = `rotateX(${beta}deg) rotateY(${gamma}deg)`;
        }
    }
    
    onShake() {
        // Handle shake event
        console.log('Shake detected!');
    }
    
    stopTracking() {
        window.removeEventListener('devicemotion', this.handleMotion);
        window.removeEventListener('deviceorientation', this.handleOrientation);
    }
}

// Usage
const motion = new MotionDetector();

// Request permission (iOS 13+)
if (motion.isSupported.permission) {
    document.getElementById('motion-btn').addEventListener('click', async () => {
        const granted = await motion.requestPermission();
        if (granted) {
            motion.startMotionTracking();
            motion.startOrientationTracking();
        }
    });
} else {
    motion.startMotionTracking();
    motion.startOrientationTracking();
}

Battery Status API

Monitor device battery level:

class BatteryMonitor {
    constructor() {
        this.battery = null;
    }
    
    async init() {
        if (!('getBattery' in navigator)) {
            console.log('Battery Status API not supported');
            return;
        }
        
        try {
            this.battery = await navigator.getBattery();
            this.setupListeners();
            this.updateUI();
        } catch (error) {
            console.error('Battery API error:', error);
        }
    }
    
    setupListeners() {
        this.battery.addEventListener('levelchange', () => this.updateUI());
        this.battery.addEventListener('chargingchange', () => this.updateUI());
        this.battery.addEventListener('chargingtimechange', () => this.updateUI());
        this.battery.addEventListener('dischargingtimechange', () => this.updateUI());
    }
    
    updateUI() {
        const level = Math.round(this.battery.level * 100);
        const charging = this.battery.charging;
        const chargingTime = this.battery.chargingTime;
        const dischargingTime = this.battery.dischargingTime;
        
        console.log(`Battery: ${level}%`);
        console.log(`Charging: ${charging}`);
        console.log(`Time to full: ${chargingTime}s`);
        console.log(`Time to empty: ${dischargingTime}s`);
        
        // Adjust app behavior based on battery
        if (level < 20 && !charging) {
            this.enableBatterySaver();
        }
    }
    
    enableBatterySaver() {
        console.log('Battery saver mode enabled');
        // Reduce animations, disable background sync, etc.
    }
}

// Usage
const battery = new BatteryMonitor();
battery.init();

Vibration API

Provide haptic feedback:

class HapticFeedback {
    static isSupported() {
        return 'vibrate' in navigator;
    }
    
    static vibrate(pattern) {
        if (!this.isSupported()) {
            console.log('Vibration API not supported');
            return;
        }
        
        navigator.vibrate(pattern);
    }
    
    static pulse() {
        this.vibrate(100); // 100ms vibration
    }
    
    static doubleTap() {
        this.vibrate([50, 50, 50]); // 50ms on, 50ms off, 50ms on
    }
    
    static success() {
        this.vibrate([100, 50, 100]); // Success pattern
    }
    
    static error() {
        this.vibrate([200, 100, 200, 100, 200]); // Error pattern
    }
    
    static stop() {
        this.vibrate(0); // Stop vibration
    }
}

// Usage
document.getElementById('button').addEventListener('click', () => {
    HapticFeedback.pulse();
});

document.getElementById('success-btn').addEventListener('click', () => {
    HapticFeedback.success();
});

Screen Wake Lock API

Prevent screen from sleeping during important activities:

class ScreenWakeLock {
    constructor() {
        this.wakeLock = null;
    }
    
    static isSupported() {
        return 'wakeLock' in navigator;
    }
    
    async request() {
        if (!ScreenWakeLock.isSupported()) {
            console.log('Wake Lock API not supported');
            return false;
        }
        
        try {
            this.wakeLock = await navigator.wakeLock.request('screen');
            console.log('Wake Lock acquired');
            
            // Re-acquire wake lock when page becomes visible
            document.addEventListener('visibilitychange', async () => {
                if (document.visibilityState === 'visible' && this.wakeLock !== null) {
                    await this.request();
                }
            });
            
            // Listen for wake lock release
            this.wakeLock.addEventListener('release', () => {
                console.log('Wake Lock released');
            });
            
            return true;
        } catch (error) {
            console.error('Wake Lock error:', error);
            return false;
        }
    }
    
    async release() {
        if (this.wakeLock !== null) {
            await this.wakeLock.release();
            this.wakeLock = null;
        }
    }
}

// Usage
const wakeLock = new ScreenWakeLock();

// Request wake lock during video playback
document.getElementById('video').addEventListener('play', async () => {
    await wakeLock.request();
});

document.getElementById('video').addEventListener('pause', async () => {
    await wakeLock.release();
});

Web Bluetooth API

Connect to Bluetooth Low Energy devices:

class BluetoothManager {
    constructor() {
        this.device = null;
        this.server = null;
        this.characteristic = null;
    }
    
    static isSupported() {
        return 'bluetooth' in navigator;
    }
    
    async scan(serviceUUID) {
        if (!BluetoothManager.isSupported()) {
            throw new Error('Web Bluetooth not supported');
        }
        
        try {
            // Request device
            this.device = await navigator.bluetooth.requestDevice({
                filters: [{ services: [serviceUUID] }]
            });
            
            console.log('Device found:', this.device.name);
            
            // Add disconnect listener
            this.device.addEventListener('gattserverdisconnected', () => {
                console.log('Device disconnected');
                this.onDisconnect();
            });
            
            return this.device;
        } catch (error) {
            console.error('Bluetooth scan error:', error);
            throw error;
        }
    }
    
    async connect() {
        if (!this.device) {
            throw new Error('No device selected');
        }
        
        try {
            // Connect to GATT server
            this.server = await this.device.gatt.connect();
            console.log('Connected to GATT server');
            return this.server;
        } catch (error) {
            console.error('Bluetooth connect error:', error);
            throw error;
        }
    }
    
    async getCharacteristic(serviceUUID, characteristicUUID) {
        if (!this.server) {
            throw new Error('Not connected');
        }
        
        try {
            const service = await this.server.getPrimaryService(serviceUUID);
            this.characteristic = await service.getCharacteristic(characteristicUUID);
            return this.characteristic;
        } catch (error) {
            console.error('Get characteristic error:', error);
            throw error;
        }
    }
    
    async readValue() {
        if (!this.characteristic) {
            throw new Error('No characteristic selected');
        }
        
        const value = await this.characteristic.readValue();
        return value;
    }
    
    async writeValue(data) {
        if (!this.characteristic) {
            throw new Error('No characteristic selected');
        }
        
        await this.characteristic.writeValue(data);
    }
    
    async startNotifications(callback) {
        if (!this.characteristic) {
            throw new Error('No characteristic selected');
        }
        
        await this.characteristic.startNotifications();
        this.characteristic.addEventListener('characteristicvaluechanged', (event) => {
            callback(event.target.value);
        });
    }
    
    async disconnect() {
        if (this.device && this.device.gatt.connected) {
            this.device.gatt.disconnect();
        }
    }
    
    onDisconnect() {
        console.log('Bluetooth device disconnected');
        // Handle disconnection
    }
}

// Usage: Connect to heart rate monitor
const bluetooth = new BluetoothManager();

document.getElementById('connect-btn').addEventListener('click', async () => {
    try {
        const HEART_RATE_SERVICE = 'heart_rate';
        const HEART_RATE_MEASUREMENT = 'heart_rate_measurement';
        
        await bluetooth.scan(HEART_RATE_SERVICE);
        await bluetooth.connect();
        await bluetooth.getCharacteristic(HEART_RATE_SERVICE, HEART_RATE_MEASUREMENT);
        
        // Start receiving heart rate data
        await bluetooth.startNotifications((value) => {
            const heartRate = value.getUint8(1);
            console.log('Heart Rate:', heartRate, 'bpm');
            document.getElementById('heart-rate').textContent = heartRate;
        });
    } catch (error) {
        console.error('Bluetooth error:', error);
    }
});

Web NFC API

Read and write NFC tags:

class NFCManager {
    constructor() {
        this.reader = null;
        this.writer = null;
    }
    
    static async isSupported() {
        if (!('NDEFReader' in window)) {
            return false;
        }
        
        try {
            const permission = await navigator.permissions.query({ name: 'nfc' });
            return permission.state === 'granted' || permission.state === 'prompt';
        } catch (error) {
            return false;
        }
    }
    
    async startReading() {
        if (!await NFCManager.isSupported()) {
            throw new Error('Web NFC not supported');
        }
        
        try {
            this.reader = new NDEFReader();
            await this.reader.scan();
            
            console.log('NFC scan started');
            
            this.reader.addEventListener('reading', ({ message, serialNumber }) => {
                console.log('NFC tag detected:', serialNumber);
                this.handleNFCMessage(message);
            });
            
            this.reader.addEventListener('readingerror', (error) => {
                console.error('NFC read error:', error);
            });
        } catch (error) {
            console.error('NFC scan error:', error);
            throw error;
        }
    }
    
    handleNFCMessage(message) {
        for (const record of message.records) {
            console.log('Record type:', record.recordType);
            console.log('MIME type:', record.mediaType);
            
            if (record.recordType === 'text') {
                const textDecoder = new TextDecoder(record.encoding);
                const text = textDecoder.decode(record.data);
                console.log('Text:', text);
                this.onTextRead(text);
            } else if (record.recordType === 'url') {
                const textDecoder = new TextDecoder();
                const url = textDecoder.decode(record.data);
                console.log('URL:', url);
                this.onURLRead(url);
            }
        }
    }
    
    async write(data) {
        if (!await NFCManager.isSupported()) {
            throw new Error('Web NFC not supported');
        }
        
        try {
            this.writer = new NDEFReader();
            
            await this.writer.write({
                records: [{ recordType: 'text', data: data }]
            });
            
            console.log('NFC write successful');
        } catch (error) {
            console.error('NFC write error:', error);
            throw error;
        }
    }
    
    async writeURL(url) {
        if (!await NFCManager.isSupported()) {
            throw new Error('Web NFC not supported');
        }
        
        try {
            this.writer = new NDEFReader();
            
            await this.writer.write({
                records: [{ recordType: 'url', data: url }]
            });
            
            console.log('NFC URL written');
        } catch (error) {
            console.error('NFC write error:', error);
            throw error;
        }
    }
    
    onTextRead(text) {
        console.log('Text from NFC tag:', text);
    }
    
    onURLRead(url) {
        console.log('URL from NFC tag:', url);
    }
}

// Usage
const nfc = new NFCManager();

// Start reading NFC tags
document.getElementById('scan-nfc').addEventListener('click', async () => {
    try {
        await nfc.startReading();
        console.log('Hold NFC tag near device...');
    } catch (error) {
        console.error('NFC error:', error);
    }
});

// Write to NFC tag
document.getElementById('write-nfc').addEventListener('click', async () => {
    try {
        await nfc.writeURL('https://example.com');
        console.log('Touch tag to write...');
    } catch (error) {
        console.error('NFC write error:', error);
    }
});

Gamepad API

Access game controllers:

class GamepadManager {
    constructor() {
        this.gamepads = {};
        this.animationFrame = null;
    }
    
    init() {
        window.addEventListener('gamepadconnected', (e) => {
            console.log('Gamepad connected:', e.gamepad.id);
            this.gamepads[e.gamepad.index] = e.gamepad;
            this.startPolling();
        });
        
        window.addEventListener('gamepaddisconnected', (e) => {
            console.log('Gamepad disconnected:', e.gamepad.id);
            delete this.gamepads[e.gamepad.index];
            
            if (Object.keys(this.gamepads).length === 0) {
                this.stopPolling();
            }
        });
    }
    
    startPolling() {
        if (this.animationFrame) return;
        
        const poll = () => {
            this.updateGamepads();
            this.animationFrame = requestAnimationFrame(poll);
        };
        
        poll();
    }
    
    stopPolling() {
        if (this.animationFrame) {
            cancelAnimationFrame(this.animationFrame);
            this.animationFrame = null;
        }
    }
    
    updateGamepads() {
        const gamepads = navigator.getGamepads();
        
        for (const gamepad of gamepads) {
            if (gamepad) {
                this.handleGamepadInput(gamepad);
            }
        }
    }
    
    handleGamepadInput(gamepad) {
        // Handle buttons
        gamepad.buttons.forEach((button, index) => {
            if (button.pressed) {
                console.log(`Button ${index} pressed`);
                this.onButtonPress(index, button.value);
            }
        });
        
        // Handle axes (joysticks)
        const [leftX, leftY, rightX, rightY] = gamepad.axes;
        
        // Dead zone to prevent drift
        const deadZone = 0.1;
        
        if (Math.abs(leftX) > deadZone || Math.abs(leftY) > deadZone) {
            this.onLeftStick(leftX, leftY);
        }
        
        if (Math.abs(rightX) > deadZone || Math.abs(rightY) > deadZone) {
            this.onRightStick(rightX, rightY);
        }
    }
    
    onButtonPress(button, value) {
        console.log(`Button ${button} pressed with value ${value}`);
    }
    
    onLeftStick(x, y) {
        console.log(`Left stick: ${x.toFixed(2)}, ${y.toFixed(2)}`);
    }
    
    onRightStick(x, y) {
        console.log(`Right stick: ${x.toFixed(2)}, ${y.toFixed(2)}`);
    }
}

// Usage
const gamepadManager = new GamepadManager();
gamepadManager.init();

Performance Optimization

Battery-Aware Location Tracking

Adjust tracking frequency based on battery level:

class BatteryAwareTracker {
    constructor() {
        this.battery = null;
        this.watchId = null;
        this.updateInterval = 5000; // Default 5 seconds
    }
    
    async init() {
        if ('getBattery' in navigator) {
            this.battery = await navigator.getBattery();
            this.adjustTrackingInterval();
            
            // Update interval when battery changes
            this.battery.addEventListener('levelchange', () => {
                this.adjustTrackingInterval();
            });
            
            this.battery.addEventListener('chargingchange', () => {
                this.adjustTrackingInterval();
            });
        }
        
        this.startTracking();
    }
    
    adjustTrackingInterval() {
        const level = this.battery.level;
        const charging = this.battery.charging;
        
        if (charging) {
            this.updateInterval = 1000; // 1 second when charging
        } else if (level > 0.5) {
            this.updateInterval = 5000; // 5 seconds when >50%
        } else if (level > 0.2) {
            this.updateInterval = 15000; // 15 seconds when 20-50%
        } else {
            this.updateInterval = 30000; // 30 seconds when <20%
        }
        
        console.log(`Tracking interval: ${this.updateInterval}ms`);
        
        // Restart tracking with new interval
        if (this.watchId !== null) {
            this.stopTracking();
            this.startTracking();
        }
    }
    
    startTracking() {
        this.watchId = navigator.geolocation.watchPosition(
            (position) => this.handlePosition(position),
            (error) => console.error(error),
            {
                enableHighAccuracy: this.battery?.level > 0.5,
                timeout: this.updateInterval,
                maximumAge: this.updateInterval / 2
            }
        );
    }
    
    stopTracking() {
        if (this.watchId !== null) {
            navigator.geolocation.clearWatch(this.watchId);
            this.watchId = null;
        }
    }
    
    handlePosition(position) {
        console.log('Position:', position.coords);
    }
}

Throttling and Debouncing Sensor Data

class OptimizedSensorReader {
    constructor() {
        this.throttleDelay = 100; // ms
        this.lastUpdate = 0;
        this.debounceTimeout = null;
    }
    
    // Throttle: Execute at most once per time period
    throttle(callback, delay = this.throttleDelay) {
        return (...args) => {
            const now = Date.now();
            if (now - this.lastUpdate >= delay) {
                this.lastUpdate = now;
                callback.apply(this, args);
            }
        };
    }
    
    // Debounce: Execute after activity stops
    debounce(callback, delay = 300) {
        return (...args) => {
            clearTimeout(this.debounceTimeout);
            this.debounceTimeout = setTimeout(() => {
                callback.apply(this, args);
            }, delay);
        };
    }
    
    startOptimizedMotionTracking() {
        const handleMotion = this.throttle((event) => {
            const { x, y, z } = event.acceleration;
            console.log('Motion (throttled):', { x, y, z });
        }, 100);
        
        window.addEventListener('devicemotion', handleMotion);
    }
    
    startOptimizedOrientationTracking() {
        const handleOrientation = this.debounce((event) => {
            const { alpha, beta, gamma } = event;
            console.log('Orientation (debounced):', { alpha, beta, gamma });
        }, 200);
        
        window.addEventListener('deviceorientation', handleOrientation);
    }
}

Efficient Media Stream Management

class OptimizedMediaCapture {
    constructor() {
        this.stream = null;
        this.tracks = [];
    }
    
    async startCamera(lowPower = false) {
        const constraints = {
            video: lowPower ? {
                width: { ideal: 640 },
                height: { ideal: 480 },
                frameRate: { ideal: 15 }
            } : {
                width: { ideal: 1920 },
                height: { ideal: 1080 },
                frameRate: { ideal: 30 }
            }
        };
        
        this.stream = await navigator.mediaDevices.getUserMedia(constraints);
        return this.stream;
    }
    
    async applyConstraints(constraints) {
        const videoTrack = this.stream.getVideoTracks()[0];
        await videoTrack.applyConstraints(constraints);
    }
    
    pauseTrack(kind = 'video') {
        const tracks = this.stream?.getTracks();
        tracks?.forEach(track => {
            if (track.kind === kind) {
                track.enabled = false;
            }
        });
    }
    
    resumeTrack(kind = 'video') {
        const tracks = this.stream?.getTracks();
        tracks?.forEach(track => {
            if (track.kind === kind) {
                track.enabled = true;
            }
        });
    }
    
    stopAllTracks() {
        this.stream?.getTracks().forEach(track => track.stop());
        this.stream = null;
    }
}

Caching and Offline Support

class OfflineLocationCache {
    constructor() {
        this.dbName = 'LocationCache';
        this.storeName = 'locations';
        this.db = null;
    }
    
    async init() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(this.dbName, 1);
            
            request.onerror = () => reject(request.error);
            request.onsuccess = () => {
                this.db = request.result;
                resolve(this.db);
            };
            
            request.onupgradeneeded = (event) => {
                const db = event.target.result;
                if (!db.objectStoreNames.contains(this.storeName)) {
                    const store = db.createObjectStore(this.storeName, {
                        keyPath: 'id',
                        autoIncrement: true
                    });
                    store.createIndex('timestamp', 'timestamp', { unique: false });
                }
            };
        });
    }
    
    async saveLocation(position) {
        const transaction = this.db.transaction([this.storeName], 'readwrite');
        const store = transaction.objectStore(this.storeName);
        
        const location = {
            latitude: position.coords.latitude,
            longitude: position.coords.longitude,
            accuracy: position.coords.accuracy,
            timestamp: position.timestamp,
            synced: navigator.onLine
        };
        
        await store.add(location);
        
        if (navigator.onLine) {
            await this.syncLocations();
        }
    }
    
    async syncLocations() {
        const transaction = this.db.transaction([this.storeName], 'readonly');
        const store = transaction.objectStore(this.storeName);
        const index = store.index('timestamp');
        
        const unsyncedLocations = [];
        const request = index.openCursor();
        
        request.onsuccess = async (event) => {
            const cursor = event.target.result;
            if (cursor) {
                if (!cursor.value.synced) {
                    unsyncedLocations.push(cursor.value);
                }
                cursor.continue();
            } else {
                // Sync all unsynced locations
                if (unsyncedLocations.length > 0) {
                    await this.uploadLocations(unsyncedLocations);
                }
            }
        };
    }
    
    async uploadLocations(locations) {
        try {
            await fetch('/api/locations', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(locations)
            });
            
            // Mark as synced
            const transaction = this.db.transaction([this.storeName], 'readwrite');
            const store = transaction.objectStore(this.storeName);
            
            locations.forEach(location => {
                location.synced = true;
                store.put(location);
            });
        } catch (error) {
            console.error('Sync failed:', error);
        }
    }
}

Security and Privacy Best Practices

Permission Handling

Always request permissions explicitly and handle denials gracefully:

class PermissionManager {
    static async checkPermission(name) {
        if (!('permissions' in navigator)) {
            console.log('Permissions API not supported');
            return null;
        }
        
        try {
            const result = await navigator.permissions.query({ name });
            return result.state; // 'granted', 'denied', or 'prompt'
        } catch (error) {
            console.error('Permission check failed:', error);
            return null;
        }
    }
    
    static async requestGeolocation() {
        const state = await this.checkPermission('geolocation');
        
        if (state === 'granted') {
            console.log('Geolocation already granted');
            return true;
        }
        
        if (state === 'denied') {
            console.log('Geolocation denied');
            this.showPermissionDeniedMessage();
            return false;
        }
        
        // Request permission
        return new Promise((resolve) => {
            navigator.geolocation.getCurrentPosition(
                () => resolve(true),
                () => {
                    this.showPermissionDeniedMessage();
                    resolve(false);
                }
            );
        });
    }
    
    static showPermissionDeniedMessage() {
        const message = document.createElement('div');
        message.className = 'permission-denied';
        message.innerHTML = `
            <p>This feature requires permission. 
            <a href="#" onclick="location.reload()">Enable in settings</a></p>
        `;
        document.body.appendChild(message);
    }
}

HTTPS Requirement

Most hardware APIs require HTTPS:

function checkSecureContext() {
    if (!window.isSecureContext) {
        console.warn('Hardware APIs require HTTPS');
        return false;
    }
    return true;
}

// Check before using APIs
if (checkSecureContext()) {
    // Safe to use hardware APIs
}

Data Privacy

Never store sensitive location or hardware data without encryption:

class SecureDataStorage {
    static async storeLocationData(location) {
        // Encrypt before storing
        const encrypted = await this.encrypt(JSON.stringify(location));
        
        // Store in IndexedDB with encryption
        const db = await this.openDatabase();
        const transaction = db.transaction(['locations'], 'readwrite');
        const store = transaction.objectStore('locations');
        
        store.add({
            data: encrypted,
            timestamp: Date.now()
        });
    }
    
    static async encrypt(data) {
        // Use Web Crypto API for encryption
        const encoder = new TextEncoder();
        const dataBuffer = encoder.encode(data);
        
        // Generate key
        const key = await crypto.subtle.generateKey(
            { name: 'AES-GCM', length: 256 },
            true,
            ['encrypt', 'decrypt']
        );
        
        // Encrypt
        const iv = crypto.getRandomValues(new Uint8Array(12));
        const encrypted = await crypto.subtle.encrypt(
            { name: 'AES-GCM', iv },
            key,
            dataBuffer
        );
        
        return { encrypted, iv, key };
    }
}

GDPR Compliance

Implement proper consent and data management:

class GDPRCompliance {
    constructor() {
        this.consentGiven = false;
        this.dataRetentionDays = 30;
    }
    
    showConsentDialog() {
        return new Promise((resolve) => {
            const dialog = document.createElement('div');
            dialog.className = 'consent-dialog';
            dialog.innerHTML = `
                <div class="consent-content">
                    <h2>Location Access Permission</h2>
                    <p>We need access to your location to:</p>
                    <ul>
                        <li>Show nearby locations</li>
                        <li>Provide personalized recommendations</li>
                        <li>Improve service quality</li>
                    </ul>
                    <p>Your data will be:</p>
                    <ul>
                        <li>Encrypted in transit and at rest</li>
                        <li>Stored for ${this.dataRetentionDays} days</li>
                        <li>Never shared with third parties without consent</li>
                        <li>Deletable at any time</li>
                    </ul>
                    <p>
                        <a href="/privacy-policy" target="_blank">Read Privacy Policy</a>
                    </p>
                    <div class="consent-buttons">
                        <button id="consent-accept">Accept</button>
                        <button id="consent-decline">Decline</button>
                    </div>
                </div>
            `;
            
            document.body.appendChild(dialog);
            
            document.getElementById('consent-accept').addEventListener('click', () => {
                this.grantConsent();
                document.body.removeChild(dialog);
                resolve(true);
            });
            
            document.getElementById('consent-decline').addEventListener('click', () => {
                document.body.removeChild(dialog);
                resolve(false);
            });
        });
    }
    
    grantConsent() {
        this.consentGiven = true;
        localStorage.setItem('location-consent', JSON.stringify({
            granted: true,
            timestamp: Date.now(),
            version: '1.0'
        }));
    }
    
    revokeConsent() {
        this.consentGiven = false;
        localStorage.removeItem('location-consent');
        this.deleteAllUserData();
    }
    
    async deleteAllUserData() {
        // Delete from IndexedDB
        const db = await this.openDatabase();
        const transaction = db.transaction(['locations'], 'readwrite');
        const store = transaction.objectStore('locations');
        await store.clear();
        
        // Delete from server
        await fetch('/api/user/data', {
            method: 'DELETE',
            headers: { 'Authorization': `Bearer ${this.getAuthToken()}` }
        });
        
        console.log('All user data deleted');
    }
    
    async cleanupOldData() {
        const cutoffDate = Date.now() - (this.dataRetentionDays * 24 * 60 * 60 * 1000);
        
        const db = await this.openDatabase();
        const transaction = db.transaction(['locations'], 'readwrite');
        const store = transaction.objectStore('locations');
        const index = store.index('timestamp');
        
        const range = IDBKeyRange.upperBound(cutoffDate);
        const request = index.openCursor(range);
        
        request.onsuccess = (event) => {
            const cursor = event.target.result;
            if (cursor) {
                cursor.delete();
                cursor.continue();
            }
        };
    }
    
    hasConsent() {
        const consent = localStorage.getItem('location-consent');
        if (!consent) return false;
        
        const { granted, timestamp } = JSON.parse(consent);
        const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
        
        // Re-request consent after 30 days
        if (timestamp < thirtyDaysAgo) {
            return false;
        }
        
        return granted;
    }
    
    openDatabase() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open('UserData', 1);
            request.onerror = () => reject(request.error);
            request.onsuccess = () => resolve(request.result);
            request.onupgradeneeded = (event) => {
                const db = event.target.result;
                if (!db.objectStoreNames.contains('locations')) {
                    const store = db.createObjectStore('locations', { autoIncrement: true });
                    store.createIndex('timestamp', 'timestamp');
                }
            };
        });
    }
    
    getAuthToken() {
        return localStorage.getItem('auth-token');
    }
}

// Usage
const gdpr = new GDPRCompliance();

// Check consent before accessing location
async function requestLocation() {
    if (!gdpr.hasConsent()) {
        const consent = await gdpr.showConsentDialog();
        if (!consent) {
            console.log('Location access denied by user');
            return;
        }
    }
    
    // Proceed with location request
    navigator.geolocation.getCurrentPosition(
        (position) => console.log(position),
        (error) => console.error(error)
    );
}

// Allow users to delete their data
document.getElementById('delete-data').addEventListener('click', async () => {
    if (confirm('Delete all your location data?')) {
        await gdpr.deleteAllUserData();
        alert('All data deleted');
    }
});

// Cleanup old data periodically
setInterval(() => gdpr.cleanupOldData(), 24 * 60 * 60 * 1000);

Data Anonymization

class LocationAnonymizer {
    // Reduce precision to protect privacy
    static anonymizeLocation(lat, lng, precision = 0.01) {
        return {
            lat: Math.round(lat / precision) * precision,
            lng: Math.round(lng / precision) * precision
        };
    }
    
    // Add noise to location data
    static addNoise(lat, lng, radiusMeters = 100) {
        const angle = Math.random() * 2 * Math.PI;
        const radius = Math.random() * radiusMeters;
        
        const latOffset = (radius * Math.cos(angle)) / 111000;
        const lngOffset = (radius * Math.sin(angle)) / (111000 * Math.cos(lat * Math.PI / 180));
        
        return {
            lat: lat + latOffset,
            lng: lng + lngOffset
        };
    }
    
    // Use k-anonymity: group locations
    static generalizeToArea(lat, lng, gridSize = 0.1) {
        const gridLat = Math.floor(lat / gridSize) * gridSize;
        const gridLng = Math.floor(lng / gridSize) * gridSize;
        
        return {
            lat: gridLat + (gridSize / 2),
            lng: gridLng + (gridSize / 2),
            area: `${gridLat},${gridLng}`
        };
    }
}

Testing Hardware APIs

Mocking Geolocation

class GeolocationMock {
    constructor() {
        this.position = {
            coords: {
                latitude: 40.7128,
                longitude: -74.0060,
                accuracy: 10,
                altitude: null,
                altitudeAccuracy: null,
                heading: null,
                speed: null
            },
            timestamp: Date.now()
        };
        this.watchCallbacks = [];
    }
    
    install() {
        // Save original
        this.original = navigator.geolocation;
        
        // Replace with mock
        navigator.geolocation = {
            getCurrentPosition: (success, error) => {
                setTimeout(() => success(this.position), 100);
            },
            watchPosition: (success, error) => {
                const id = this.watchCallbacks.length;
                this.watchCallbacks.push(success);
                setTimeout(() => success(this.position), 100);
                return id;
            },
            clearWatch: (id) => {
                delete this.watchCallbacks[id];
            }
        };
    }
    
    setPosition(lat, lng, accuracy = 10) {
        this.position.coords.latitude = lat;
        this.position.coords.longitude = lng;
        this.position.coords.accuracy = accuracy;
        this.position.timestamp = Date.now();
        
        // Trigger watch callbacks
        this.watchCallbacks.forEach(callback => {
            if (callback) callback(this.position);
        });
    }
    
    simulateError(code = 1) {
        // Override to return error
        navigator.geolocation.getCurrentPosition = (success, error) => {
            setTimeout(() => error({
                code,
                message: 'Simulated error',
                PERMISSION_DENIED: 1,
                POSITION_UNAVAILABLE: 2,
                TIMEOUT: 3
            }), 100);
        };
    }
    
    restore() {
        navigator.geolocation = this.original;
    }
}

// Usage in tests
const mock = new GeolocationMock();
mock.install();

// Test with mock location
mock.setPosition(51.5074, -0.1278); // London
navigator.geolocation.getCurrentPosition((pos) => {
    console.assert(pos.coords.latitude === 51.5074);
});

mock.restore();

Testing with Jest

// geolocation.test.js
describe('Geolocation', () => {
    let mockGeolocation;
    
    beforeEach(() => {
        mockGeolocation = {
            getCurrentPosition: jest.fn(),
            watchPosition: jest.fn(),
            clearWatch: jest.fn()
        };
        global.navigator.geolocation = mockGeolocation;
    });
    
    afterEach(() => {
        jest.clearAllMocks();
    });
    
    test('should request current position', async () => {
        const mockPosition = {
            coords: {
                latitude: 40.7128,
                longitude: -74.0060,
                accuracy: 10
            },
            timestamp: Date.now()
        };
        
        mockGeolocation.getCurrentPosition.mockImplementation((success) => {
            success(mockPosition);
        });
        
        const result = await getCurrentLocation();
        
        expect(mockGeolocation.getCurrentPosition).toHaveBeenCalled();
        expect(result.coords.latitude).toBe(40.7128);
    });
    
    test('should handle permission denied', async () => {
        const mockError = {
            code: 1,
            message: 'User denied Geolocation'
        };
        
        mockGeolocation.getCurrentPosition.mockImplementation((success, error) => {
            error(mockError);
        });
        
        await expect(getCurrentLocation()).rejects.toThrow();
    });
});

// Helper function
function getCurrentLocation() {
    return new Promise((resolve, reject) => {
        navigator.geolocation.getCurrentPosition(resolve, reject);
    });
}

Testing MediaStream

describe('Camera Access', () => {
    let mockGetUserMedia;
    
    beforeEach(() => {
        mockGetUserMedia = jest.fn();
        global.navigator.mediaDevices = {
            getUserMedia: mockGetUserMedia
        };
    });
    
    test('should request camera access', async () => {
        const mockStream = {
            getTracks: () => [{
                kind: 'video',
                stop: jest.fn()
            }]
        };
        
        mockGetUserMedia.mockResolvedValue(mockStream);
        
        const stream = await navigator.mediaDevices.getUserMedia({ video: true });
        
        expect(mockGetUserMedia).toHaveBeenCalledWith({ video: true });
        expect(stream).toBeDefined();
    });
    
    test('should handle camera permission denied', async () => {
        const mockError = new Error('Permission denied');
        mockError.name = 'NotAllowedError';
        
        mockGetUserMedia.mockRejectedValue(mockError);
        
        await expect(
            navigator.mediaDevices.getUserMedia({ video: true })
        ).rejects.toThrow('Permission denied');
    });
});

Browser DevTools Testing

// Chrome DevTools: Override geolocation
// 1. Open DevTools (F12)
// 2. Press Ctrl+Shift+P (Cmd+Shift+P on Mac)
// 3. Type "Show Sensors"
// 4. Select location or enter custom coordinates

// Programmatically in DevTools Console:
const position = {
    coords: {
        latitude: 37.7749,
        longitude: -122.4194,
        accuracy: 10
    },
    timestamp: Date.now()
};

// Override getCurrentPosition
const originalGetPosition = navigator.geolocation.getCurrentPosition;
navigator.geolocation.getCurrentPosition = function(success, error) {
    success(position);
};

Accessibility Considerations

Voice Control Integration

class VoiceController {
    constructor() {
        this.recognition = null;
        this.commands = new Map();
    }
    
    async init() {
        if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
            console.error('Speech recognition not supported');
            return false;
        }
        
        const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
        this.recognition = new SpeechRecognition();
        
        this.recognition.continuous = true;
        this.recognition.interimResults = false;
        this.recognition.lang = 'en-US';
        
        this.recognition.onresult = (event) => {
            const last = event.results.length - 1;
            const command = event.results[last][0].transcript.toLowerCase().trim();
            
            console.log('Voice command:', command);
            this.executeCommand(command);
        };
        
        this.recognition.onerror = (event) => {
            console.error('Speech recognition error:', event.error);
        };
        
        return true;
    }
    
    registerCommand(phrase, callback) {
        this.commands.set(phrase.toLowerCase(), callback);
    }
    
    executeCommand(command) {
        for (const [phrase, callback] of this.commands) {
            if (command.includes(phrase)) {
                callback(command);
                this.speak(`Executing ${phrase}`);
                return;
            }
        }
        
        this.speak('Command not recognized');
    }
    
    speak(text) {
        if ('speechSynthesis' in window) {
            const utterance = new SpeechSynthesisUtterance(text);
            window.speechSynthesis.speak(utterance);
        }
    }
    
    start() {
        this.recognition?.start();
    }
    
    stop() {
        this.recognition?.stop();
    }
}

// Usage
const voice = new VoiceController();
await voice.init();

// Register location commands
voice.registerCommand('show my location', () => {
    navigator.geolocation.getCurrentPosition((pos) => {
        const { latitude, longitude } = pos.coords;
        voice.speak(`Your location is latitude ${latitude.toFixed(2)}, longitude ${longitude.toFixed(2)}`);
    });
});

voice.registerCommand('find nearby', () => {
    // Find nearby places
    voice.speak('Searching for nearby locations');
});

voice.start();

Screen Reader Support

class AccessibleLocationUI {
    announceLocation(position) {
        const { latitude, longitude, accuracy } = position.coords;
        
        // Create live region for screen readers
        const announcement = document.createElement('div');
        announcement.setAttribute('role', 'status');
        announcement.setAttribute('aria-live', 'polite');
        announcement.setAttribute('aria-atomic', 'true');
        announcement.className = 'sr-only'; // Visually hidden
        
        announcement.textContent = `Location updated: ` +
            `Latitude ${latitude.toFixed(4)}, ` +
            `Longitude ${longitude.toFixed(4)}, ` +
            `Accuracy ${Math.round(accuracy)} meters`;
        
        document.body.appendChild(announcement);
        
        // Remove after announcement
        setTimeout(() => {
            document.body.removeChild(announcement);
        }, 1000);
    }
    
    announcePermissionRequest() {
        const announcement = this.createAnnouncement(
            'Requesting location permission. Please allow access to continue.'
        );
        document.body.appendChild(announcement);
    }
    
    announceError(error) {
        let message = 'Location error: ';
        switch (error.code) {
            case 1:
                message += 'Permission denied. Please enable location access in settings.';
                break;
            case 2:
                message += 'Position unavailable. Please check your connection.';
                break;
            case 3:
                message += 'Request timed out. Please try again.';
                break;
        }
        
        const announcement = this.createAnnouncement(message, 'assertive');
        document.body.appendChild(announcement);
    }
    
    createAnnouncement(text, priority = 'polite') {
        const div = document.createElement('div');
        div.setAttribute('role', 'status');
        div.setAttribute('aria-live', priority);
        div.setAttribute('aria-atomic', 'true');
        div.className = 'sr-only';
        div.textContent = text;
        return div;
    }
}

// CSS for screen reader only content
const css = `
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}
`;

Keyboard Navigation

class KeyboardAccessibleMap {
    constructor(mapElement) {
        this.map = mapElement;
        this.focusIndex = 0;
        this.markers = [];
        this.setupKeyboardControls();
    }
    
    setupKeyboardControls() {
        this.map.setAttribute('tabindex', '0');
        this.map.setAttribute('role', 'application');
        this.map.setAttribute('aria-label', 'Interactive map. Use arrow keys to navigate.');
        
        this.map.addEventListener('keydown', (e) => {
            switch (e.key) {
                case 'ArrowUp':
                    e.preventDefault();
                    this.panMap(0, -10);
                    break;
                case 'ArrowDown':
                    e.preventDefault();
                    this.panMap(0, 10);
                    break;
                case 'ArrowLeft':
                    e.preventDefault();
                    this.panMap(-10, 0);
                    break;
                case 'ArrowRight':
                    e.preventDefault();
                    this.panMap(10, 0);
                    break;
                case '+':
                case '=':
                    e.preventDefault();
                    this.zoomIn();
                    break;
                case '-':
                    e.preventDefault();
                    this.zoomOut();
                    break;
                case 'Tab':
                    this.focusNextMarker();
                    break;
                case 'Enter':
                case ' ':
                    e.preventDefault();
                    this.activateCurrentMarker();
                    break;
            }
        });
    }
    
    panMap(x, y) {
        console.log(`Panning map: ${x}, ${y}`);
        this.announceMapMovement(x, y);
    }
    
    zoomIn() {
        console.log('Zooming in');
        this.announce('Zoomed in');
    }
    
    zoomOut() {
        console.log('Zooming out');
        this.announce('Zoomed out');
    }
    
    focusNextMarker() {
        if (this.markers.length === 0) return;
        
        this.focusIndex = (this.focusIndex + 1) % this.markers.length;
        const marker = this.markers[this.focusIndex];
        this.announce(`Marker ${this.focusIndex + 1} of ${this.markers.length}: ${marker.label}`);
    }
    
    activateCurrentMarker() {
        if (this.markers.length === 0) return;
        
        const marker = this.markers[this.focusIndex];
        console.log('Activated marker:', marker);
        this.announce(`Opened ${marker.label}`);
    }
    
    announce(text) {
        const announcement = document.createElement('div');
        announcement.setAttribute('role', 'status');
        announcement.setAttribute('aria-live', 'polite');
        announcement.className = 'sr-only';
        announcement.textContent = text;
        document.body.appendChild(announcement);
        setTimeout(() => document.body.removeChild(announcement), 1000);
    }
    
    announceMapMovement(x, y) {
        const direction = x > 0 ? 'right' : x < 0 ? 'left' : y > 0 ? 'down' : 'up';
        this.announce(`Moved map ${direction}`);
    }
}

Real-World Use Cases

Location-Based Services

class LocationBasedService {
    async findNearbyPlaces(radius = 1000) {
        const position = await this.getCurrentLocation();
        const { latitude, longitude } = position.coords;
        
        const response = await fetch(
            `/api/places?lat=${latitude}&lon=${longitude}&radius=${radius}`
        );
        
        return response.json();
    }
    
    async getCurrentLocation() {
        return new Promise((resolve, reject) => {
            navigator.geolocation.getCurrentPosition(resolve, reject);
        });
    }
    
    async getLocationBasedRecommendations() {
        const position = await this.getCurrentLocation();
        const { latitude, longitude } = position.coords;
        
        // Get weather
        const weather = await fetch(
            `https://api.weather.com/v3/wx/conditions/current?` +
            `geocode=${latitude},${longitude}&format=json`
        ).then(r => r.json());
        
        // Get local time
        const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
        const localTime = new Date().toLocaleTimeString('en-US', { timezone });
        
        // Get nearby events
        const events = await fetch(
            `/api/events?lat=${latitude}&lng=${longitude}`
        ).then(r => r.json());
        
        return {
            weather,
            localTime,
            events,
            recommendations: this.generateRecommendations(weather, localTime, events)
        };
    }
    
    generateRecommendations(weather, time, events) {
        const recommendations = [];
        
        if (weather.temperature > 25) {
            recommendations.push('Visit a nearby ice cream shop');
        }
        
        const hour = new Date().getHours();
        if (hour >= 11 && hour <= 14) {
            recommendations.push('Check out lunch specials nearby');
        }
        
        if (events.length > 0) {
            recommendations.push(`${events.length} events happening nearby`);
        }
        
        return recommendations;
    }
}

Augmented Reality

class ARExperience {
    constructor() {
        this.camera = null;
        this.orientation = null;
        this.location = null;
    }
    
    async initAR() {
        // Request camera and motion permissions
        this.camera = await navigator.mediaDevices.getUserMedia({
            video: { facingMode: 'environment' } // Back camera
        });
        
        // Request motion permission (iOS)
        if (typeof DeviceMotionEvent.requestPermission === 'function') {
            const motionGranted = await DeviceMotionEvent.requestPermission();
            if (motionGranted !== 'granted') {
                throw new Error('Motion permission denied');
            }
        }
        
        // Request location for AR anchoring
        this.location = await new Promise((resolve, reject) => {
            navigator.geolocation.getCurrentPosition(resolve, reject);
        });
        
        this.setupARView();
    }
    
    setupARView() {
        const video = document.getElementById('ar-video');
        video.srcObject = this.camera;
        
        // Track device orientation for AR positioning
        window.addEventListener('deviceorientation', (event) => {
            this.orientation = {
                alpha: event.alpha,
                beta: event.beta,
                gamma: event.gamma
            };
            this.updateARObjects();
        });
        
        // Track device motion for better AR stability
        window.addEventListener('devicemotion', (event) => {
            const { x, y, z } = event.acceleration;
            this.updateARStability({ x, y, z });
        });
    }
    
    updateARObjects() {
        if (!this.orientation) return;
        
        // Calculate AR object positions based on:
        // 1. Device orientation (where user is looking)
        // 2. User's GPS location
        // 3. AR object's GPS location
        
        const arObjects = document.querySelectorAll('.ar-object');
        arObjects.forEach(obj => {
            const bearing = this.calculateBearing(
                this.location.coords.latitude,
                this.location.coords.longitude,
                obj.dataset.lat,
                obj.dataset.lng
            );
            
            const distance = this.calculateDistance(
                this.location.coords.latitude,
                this.location.coords.longitude,
                obj.dataset.lat,
                obj.dataset.lng
            );
            
            // Adjust object position based on device orientation
            const relativeBearing = bearing - this.orientation.alpha;
            this.positionARObject(obj, relativeBearing, distance, this.orientation.beta);
        });
    }
    
    calculateBearing(lat1, lon1, lat2, lon2) {
        const dLon = (lon2 - lon1) * Math.PI / 180;
        const y = Math.sin(dLon) * Math.cos(lat2 * Math.PI / 180);
        const x = Math.cos(lat1 * Math.PI / 180) * Math.sin(lat2 * Math.PI / 180) -
                  Math.sin(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.cos(dLon);
        const bearing = Math.atan2(y, x) * 180 / Math.PI;
        return (bearing + 360) % 360;
    }
    
    calculateDistance(lat1, lon1, lat2, lon2) {
        const R = 6371000; // meters
        const dLat = (lat2 - lat1) * Math.PI / 180;
        const dLon = (lon2 - lon1) * Math.PI / 180;
        
        const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                  Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
                  Math.sin(dLon / 2) * Math.sin(dLon / 2);
        
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }
    
    positionARObject(obj, bearing, distance, tilt) {
        // Position AR object in viewport based on bearing and distance
        const screenWidth = window.innerWidth;
        const fieldOfView = 60; // degrees
        
        // Calculate horizontal position
        const x = (bearing / fieldOfView) * screenWidth + (screenWidth / 2);
        
        // Calculate vertical position based on device tilt
        const y = window.innerHeight / 2 + (tilt * 5);
        
        // Scale based on distance
        const scale = Math.max(0.3, Math.min(2, 100 / distance));
        
        obj.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
        
        // Show/hide based on whether it's in view
        obj.style.display = (Math.abs(bearing) < fieldOfView / 2) ? 'block' : 'none';
        
        // Update distance label
        const label = obj.querySelector('.distance');
        if (label) {
            label.textContent = `${Math.round(distance)}m`;
        }
    }
    
    updateARStability(acceleration) {
        // Use accelerometer data to stabilize AR view
        const magnitude = Math.sqrt(
            acceleration.x ** 2 +
            acceleration.y ** 2 +
            acceleration.z ** 2
        );
        
        // If device is moving too much, show stability warning
        if (magnitude > 15) {
            this.showStabilityWarning();
        }
    }
    
    showStabilityWarning() {
        const warning = document.getElementById('stability-warning');
        if (warning) {
            warning.style.display = 'block';
            setTimeout(() => {
                warning.style.display = 'none';
            }, 2000);
        }
    }
    
    cleanup() {
        this.camera?.getTracks().forEach(track => track.stop());
        window.removeEventListener('deviceorientation', this.updateARObjects);
        window.removeEventListener('devicemotion', this.updateARStability);
    }
}

// Usage
const arApp = new ARExperience();
await arApp.initAR();

Fitness Tracking

class FitnessTracker {
    constructor() {
        this.session = null;
        this.steps = 0;
        this.distance = 0;
        this.calories = 0;
        this.startTime = null;
    }
    
    async startActivity(activityType = 'running') {
        this.startTime = Date.now();
        this.session = {
            type: activityType,
            route: [],
            steps: 0,
            distance: 0,
            calories: 0,
            heartRate: []
        };
        
        // Start location tracking
        const routeTracker = new RouteTracker();
        routeTracker.start();
        
        // Start step detection
        this.startStepDetection();
        
        // Monitor battery for power management
        const battery = await navigator.getBattery();
        this.adjustTrackingForBattery(battery.level);
        
        battery.addEventListener('levelchange', () => {
            this.adjustTrackingForBattery(battery.level);
        });
    }
    
    startStepDetection() {
        let lastStep = 0;
        const stepThreshold = 12; // Acceleration threshold for step detection
        
        window.addEventListener('devicemotion', (event) => {
            const { x, y, z } = event.acceleration;
            const magnitude = Math.sqrt(x**2 + y**2 + z**2);
            
            // Detect step
            if (magnitude > stepThreshold) {
                const now = Date.now();
                if (now - lastStep > 300) { // Minimum 300ms between steps
                    this.steps++;
                    lastStep = now;
                    this.calculateMetrics();
                }
            }
        });
    }
    
    calculateMetrics() {
        // Calculate distance (rough estimate)
        const strideLength = 0.78; // meters
        this.distance = this.steps * strideLength;
        
        // Calculate calories burned
        const caloriesPerStep = 0.04;
        this.calories = this.steps * caloriesPerStep;
        
        // Calculate pace
        const duration = (Date.now() - this.startTime) / 60000; // minutes
        const pace = duration / (this.distance / 1000); // min/km
        
        this.updateUI({
            steps: this.steps,
            distance: this.distance,
            calories: this.calories,
            pace: pace
        });
    }
    
    adjustTrackingForBattery(level) {
        if (level < 0.2) {
            // Low battery: reduce tracking frequency
            console.log('Low battery: reducing tracking frequency');
        } else if (level < 0.5) {
            // Medium battery: normal tracking
            console.log('Medium battery: normal tracking');
        } else {
            // High battery: high accuracy tracking
            console.log('High battery: high accuracy tracking');
        }
    }
    
    async connectHeartRateMonitor() {
        if (!('bluetooth' in navigator)) {
            console.error('Web Bluetooth not supported');
            return;
        }
        
        try {
            const device = await navigator.bluetooth.requestDevice({
                filters: [{ services: ['heart_rate'] }]
            });
            
            const server = await device.gatt.connect();
            const service = await server.getPrimaryService('heart_rate');
            const characteristic = await service.getCharacteristic('heart_rate_measurement');
            
            await characteristic.startNotifications();
            characteristic.addEventListener('characteristicvaluechanged', (event) => {
                const heartRate = event.target.value.getUint8(1);
                this.session.heartRate.push({
                    value: heartRate,
                    timestamp: Date.now()
                });
                document.getElementById('heart-rate').textContent = heartRate;
            });
        } catch (error) {
            console.error('Heart rate monitor connection failed:', error);
        }
    }
    
    updateUI(metrics) {
        document.getElementById('steps').textContent = metrics.steps;
        document.getElementById('distance').textContent = `${(metrics.distance / 1000).toFixed(2)} km`;
        document.getElementById('calories').textContent = Math.round(metrics.calories);
        document.getElementById('pace').textContent = `${metrics.pace.toFixed(2)} min/km`;
    }
    
    async stopActivity() {
        const duration = (Date.now() - this.startTime) / 1000;
        
        return {
            ...this.session,
            duration,
            steps: this.steps,
            distance: this.distance,
            calories: this.calories,
            avgPace: (duration / 60) / (this.distance / 1000)
        };
    }
}

// Usage
const fitness = new FitnessTracker();
await fitness.startActivity('running');
await fitness.connectHeartRateMonitor();

// Later...
const summary = await fitness.stopActivity();
console.log('Workout summary:', summary);

Smart Home Integration

class SmartHomeController {
    async detectHomeArrival() {
        const homeLocation = {
            lat: 40.7128,
            lng: -74.0060,
            radius: 100 // meters
        };
        
        const geofence = new Geofence(homeLocation, homeLocation.radius);
        
        geofence.onEnter(async () => {
            console.log('Arrived home');
            await this.triggerHomeAutomation();
        });
        
        geofence.onExit(() => {
            console.log('Left home');
            this.triggerAwayMode();
        });
    }
    
    async triggerHomeAutomation() {
        // Turn on lights, adjust thermostat, etc.
        await fetch('/api/smart-home/arrive', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                timestamp: Date.now(),
                action: 'arrive'
            })
        });
        
        // Trigger haptic feedback
        navigator.vibrate([200, 100, 200]);
    }
    
    async triggerAwayMode() {
        await fetch('/api/smart-home/away', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                timestamp: Date.now(),
                action: 'away'
            })
        });
    }
}

Browser Compatibility

API Chrome Firefox Safari Edge
Geolocation โœ… โœ… โœ… โœ…
MediaStream โœ… โœ… โœ… โœ…
DeviceMotion โœ… โœ… โœ… โœ…
DeviceOrientation โœ… โœ… โœ… โœ…
Battery Status โœ… โœ… โŒ โœ…
Vibration โœ… โœ… โš ๏ธ โœ…

Troubleshooting Common Issues

Geolocation Not Working

// Check for common issues
function debugGeolocation() {
    if (!('geolocation' in navigator)) {
        console.error('Geolocation not supported');
        return;
    }
    
    if (!window.isSecureContext) {
        console.error('HTTPS required');
        return;
    }
    
    navigator.geolocation.getCurrentPosition(
        (pos) => console.log('Success:', pos),
        (err) => {
            switch (err.code) {
                case 1:
                    console.error('Permission denied');
                    break;
                case 2:
                    console.error('Position unavailable');
                    break;
                case 3:
                    console.error('Timeout');
                    break;
            }
        }
    );
}

Camera Permission Issues

// Handle camera permission errors
async function debugCamera() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: true });
        console.log('Camera access granted');
    } catch (error) {
        if (error.name === 'NotAllowedError') {
            console.error('Camera permission denied');
        } else if (error.name === 'NotFoundError') {
            console.error('No camera found');
        } else {
            console.error('Camera error:', error);
        }
    }
}

Emerging APIs to watch:

  • Web Bluetooth: Connect to Bluetooth devices
  • Web USB: Access USB devices
  • Ambient Light Sensor: Detect ambient light levels
  • Proximity Sensor: Detect nearby objects
  • Magnetometer: Access compass data
  • Barometer: Detect altitude changes

Conclusion

Geolocation and hardware APIs unlock powerful capabilities for web applications, enabling experiences that rival native applications. By implementing them thoughtfullyโ€”with proper permissions, security, performance optimization, and user experience considerationsโ€”you can create immersive, context-aware applications that users love.

Key Takeaways:

  1. Always request permissions explicitly: Users should know what you’re accessing and why
  2. Require HTTPS: Most hardware APIs require secure contexts for security
  3. Handle errors gracefully: Provide clear fallbacks when APIs aren’t available or permissions are denied
  4. Respect privacy: Encrypt sensitive data, implement GDPR compliance, and minimize data collection
  5. Optimize for battery: Adjust tracking frequency based on battery level and user context
  6. Test thoroughly: Test on real devices with various permissions states and scenarios
  7. Progressive enhancement: Build features that work without these APIs as fallback
  8. Consider accessibility: Ensure hardware features are accessible via alternative methods

Production Checklist:

  • HTTPS enabled on all pages
  • Permission requests with clear explanations
  • Error handling for all permission states
  • GDPR-compliant consent management
  • Data encryption for sensitive information
  • Battery-aware resource management
  • Offline support with IndexedDB caching
  • Accessibility features (voice, keyboard, screen reader)
  • Cross-browser testing (Chrome, Firefox, Safari, Edge)
  • Mobile testing on iOS and Android
  • Performance monitoring and optimization
  • Security audit completed
  • Privacy policy updated
  • User documentation provided

Performance Best Practices:

  • Throttle sensor events to 100ms intervals
  • Debounce orientation changes
  • Use maximumAge to reduce GPS queries
  • Disable enableHighAccuracy when not needed
  • Stop tracking when app is backgrounded
  • Clean up event listeners and streams
  • Cache location data for offline use
  • Batch API requests to reduce network usage

Security Checklist:

  • Never store plaintext location data
  • Use Web Crypto API for encryption
  • Implement data retention policies
  • Allow users to delete their data
  • Anonymize data when possible
  • Add noise to reduce precision
  • Audit third-party dependencies
  • Monitor for security vulnerabilities
  • Implement rate limiting on APIs
  • Use CSP headers to prevent XSS

Future Trends:

The web platform continues to evolve, bringing more device capabilities to developers:

  • Web Bluetooth: Connect to Bluetooth devices (fitness trackers, smart watches, IoT devices)
  • Web USB: Access USB devices (Arduino, hardware development tools)
  • Web NFC: Read and write NFC tags (payments, access control, smart posters)
  • Ambient Light Sensor: Detect ambient light levels for auto brightness
  • Proximity Sensor: Detect nearby objects (during phone calls, etc.)
  • Magnetometer: Access compass data for better navigation
  • Barometer: Detect altitude changes for fitness tracking
  • WebXR: Immersive VR and AR experiences
  • Shape Detection API: Detect faces, barcodes, text in images
  • Web Serial API: Communicate with serial devices

Browser Support Improving:

Major browsers are actively implementing and improving hardware APIs:

  • Chrome: Leading in experimental API support
  • Firefox: Strong privacy-focused implementations
  • Safari: Catching up with iOS 13+ improvements
  • Edge: Chromium-based, matching Chrome support

Resources for Further Learning:

Community and Support:

Tools for Development:

  • Chrome DevTools Sensors panel
  • Firefox Developer Tools
  • Remote debugging for mobile devices
  • Geolocation spoofing extensions
  • Device simulators and emulators

The web platform continues to evolve, bringing more device capabilities to developers. Use these APIs responsibly to create better experiences for your users while respecting their privacy and device resources.

Remember: With great power comes great responsibility. Always prioritize user privacy, security, and experience when implementing hardware APIs. Test thoroughly, handle errors gracefully, and provide clear value to users in exchange for the permissions you request.

Happy coding! ๐Ÿš€

Comments