UNPKG

expo-osm-sdk

Version:

OpenStreetMap component for React Native with Expo

371 lines โ€ข 14.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.useLocationTracking = void 0; const react_1 = __importStar(require("react")); /** * Enhanced location tracking hook with bulletproof error handling * * This hook provides comprehensive error handling for all GPS and permission scenarios: * - Permission denied/revoked scenarios * - GPS disabled/unavailable * - No signal (indoor/underground) * - Timeout situations * - View lifecycle issues * - Network problems * - Hardware failures * * @param osmViewRef - Reference to the OSM view component * @param options - Hook configuration options * @returns Location tracking state and methods with comprehensive error handling * * @example * ```tsx * function MyMapComponent() { * const mapRef = useRef<OSMViewRef>(null); * const { * isTracking, * currentLocation, * error, * startTracking, * stopTracking, * getCurrentLocation, * retryLastOperation, * getHealthStatus * } = useLocationTracking(mapRef, { * maxRetryAttempts: 3, * fallbackLocation: { latitude: 0, longitude: 0 }, * onError: (error) => { * console.error('Location error:', error.userMessage); * // Show user-friendly error message * if (error.canRetry) { * // Show retry button * } * } * }); * * return ( * <View> * <OSMView ref={mapRef} /> * {error && ( * <View> * <Text>{error.userMessage}</Text> * <Text>{error.suggestedAction}</Text> * {error.canRetry && ( * <Button title="Retry" onPress={retryLastOperation} /> * )} * </View> * )} * </View> * ); * } * ``` */ const useLocationTracking = (osmViewRef, options = {}) => { const { autoStart = false, onLocationChange, onError, maxWaitTime = 10000, maxRetryAttempts = 3, fallbackLocation } = options; // State const [isTracking, setIsTracking] = (0, react_1.useState)(false); const [status, setStatus] = (0, react_1.useState)('idle'); const [currentLocation, setCurrentLocation] = (0, react_1.useState)(null); const [error, setError] = (0, react_1.useState)(null); // Internal state for retry logic const lastOperation = (0, react_1.useRef)(null); const retryAttempts = (0, react_1.useRef)(0); const hasAutoStarted = (0, react_1.useRef)(false); // Create enhanced error object const createLocationError = (0, react_1.useCallback)((type, originalMessage, canRetry = true) => { const errorConfig = { permission_denied: { userMessage: 'Location permission was denied', suggestedAction: 'Please enable location permission in your device settings' }, permission_restricted: { userMessage: 'Location access is restricted', suggestedAction: 'Location access is restricted by system policies' }, gps_disabled: { userMessage: 'GPS is disabled', suggestedAction: 'Please enable location services in your device settings' }, no_signal: { userMessage: 'Unable to get GPS signal', suggestedAction: 'Move to an area with clear sky view or try again later' }, timeout: { userMessage: 'Location request timed out', suggestedAction: 'Move to an area with better GPS reception and try again' }, view_not_ready: { userMessage: 'Map is not ready', suggestedAction: 'Please wait for the map to load and try again' }, unknown: { userMessage: 'Unknown location error occurred', suggestedAction: 'Please try again or restart the app' } }; const config = errorConfig[type]; return { type, message: originalMessage, userMessage: config.userMessage, canRetry, suggestedAction: config.suggestedAction }; }, []); // Enhanced error handling wrapper const safeCall = (0, react_1.useCallback)(async (operation, method, canRetry = true) => { try { if (!osmViewRef.current) { throw new Error('OSM view reference not available'); } console.log(`๐Ÿ” useLocationTracking: Starting ${operation}`); lastOperation.current = operation; const result = await method(); console.log(`โœ… useLocationTracking: ${operation} successful`); // Reset retry attempts on success retryAttempts.current = 0; setError(null); return result; } catch (err) { const errorMessage = err instanceof Error ? err.message : `${operation} failed`; console.error(`โŒ useLocationTracking: ${operation} failed:`, errorMessage); // Determine error type from error message let errorType = 'unknown'; if (errorMessage.includes('permission') && errorMessage.includes('denied')) { errorType = 'permission_denied'; } else if (errorMessage.includes('permission') && errorMessage.includes('restricted')) { errorType = 'permission_restricted'; } else if (errorMessage.includes('GPS') || errorMessage.includes('location services')) { errorType = 'gps_disabled'; } else if (errorMessage.includes('timeout') || errorMessage.includes('Timeout')) { errorType = 'timeout'; } else if (errorMessage.includes('view') || errorMessage.includes('View')) { errorType = 'view_not_ready'; } else if (errorMessage.includes('signal') || errorMessage.includes('No recent location')) { errorType = 'no_signal'; } const locationError = createLocationError(errorType, errorMessage, canRetry); setError(locationError); setStatus('error'); onError?.(locationError); return null; } }, [osmViewRef, onError, createLocationError]); // System health check const getHealthStatus = (0, react_1.useCallback)(async () => { const health = { isSupported: true, // Assume supported hasPermission: false, isGpsEnabled: false, isViewReady: false, lastLocationAge: null, networkAvailable: true // Assume available }; try { // Check view readiness if (osmViewRef.current?.isViewReady) { health.isViewReady = await osmViewRef.current.isViewReady(); } // Check last location age if (currentLocation) { // Estimate age (we don't have timestamp, so approximate) health.lastLocationAge = 0; // Would need timestamp from location data } // Note: GPS and permission checks would need platform-specific APIs // This is a basic implementation } catch (error) { console.warn('Health check failed:', error); } return health; }, [osmViewRef, currentLocation]); // Start location tracking with comprehensive error handling const startTracking = (0, react_1.useCallback)(async () => { if (isTracking || status === 'starting') { return true; // Already tracking or starting } setStatus('starting'); setError(null); const success = await safeCall('startLocationTracking', async () => { await osmViewRef.current.startLocationTracking(); return true; }); if (success) { setIsTracking(true); setStatus('active'); console.log('โœ… Location tracking started successfully'); return true; } else { setStatus('error'); setIsTracking(false); return false; } }, [isTracking, status, safeCall, osmViewRef]); // Stop location tracking with error handling const stopTracking = (0, react_1.useCallback)(async () => { if (!isTracking || status === 'stopping') { return true; // Already stopped or stopping } setStatus('stopping'); setError(null); const success = await safeCall('stopLocationTracking', async () => { await osmViewRef.current.stopLocationTracking(); return true; }, false); // Don't retry stop operations setIsTracking(false); setStatus(success ? 'idle' : 'error'); if (success) { console.log('โœ… Location tracking stopped successfully'); return true; } else { return false; } }, [isTracking, status, safeCall, osmViewRef]); // Get current location with fallback mechanisms const getCurrentLocationSafe = (0, react_1.useCallback)(async () => { let result = await safeCall('getCurrentLocation', async () => { return await osmViewRef.current.getCurrentLocation(); }); if (result) { setCurrentLocation(result); onLocationChange?.(result); return result; } // Fallback: try to get any recent location if (currentLocation) { console.log('๐Ÿ“ Using cached location as fallback'); return currentLocation; } // Final fallback: use provided fallback location if (fallbackLocation) { console.log('๐Ÿ“ Using provided fallback location'); setCurrentLocation(fallbackLocation); onLocationChange?.(fallbackLocation); return fallbackLocation; } return null; }, [safeCall, osmViewRef, onLocationChange, currentLocation, fallbackLocation]); // Wait for fresh location with enhanced timeout handling const waitForLocation = (0, react_1.useCallback)(async (timeoutSeconds = 30) => { let result = await safeCall('waitForLocation', async () => { return await osmViewRef.current.waitForLocation(timeoutSeconds); }); if (result) { setCurrentLocation(result); onLocationChange?.(result); return result; } // If wait failed, try getCurrentLocation as fallback console.log('๐Ÿ“ waitForLocation failed, trying getCurrentLocation fallback...'); return await getCurrentLocationSafe(); }, [safeCall, osmViewRef, onLocationChange, getCurrentLocationSafe]); // Retry last failed operation const retryLastOperation = (0, react_1.useCallback)(async () => { if (!lastOperation.current || retryAttempts.current >= maxRetryAttempts) { console.log('โŒ Cannot retry: max attempts reached or no last operation'); return false; } retryAttempts.current++; console.log(`๐Ÿ”„ Retrying ${lastOperation.current} (attempt ${retryAttempts.current}/${maxRetryAttempts})`); setError(null); switch (lastOperation.current) { case 'startLocationTracking': return await startTracking(); case 'getCurrentLocation': const location = await getCurrentLocationSafe(); return location !== null; case 'waitForLocation': const waitResult = await waitForLocation(); return waitResult !== null; default: return false; } }, [lastOperation, retryAttempts, maxRetryAttempts, startTracking, getCurrentLocationSafe, waitForLocation]); // Clear error state const clearError = (0, react_1.useCallback)(() => { setError(null); if (status === 'error') { setStatus('idle'); } }, [status]); // Auto-start with error handling react_1.default.useEffect(() => { if (autoStart && !hasAutoStarted.current && !isTracking && status === 'idle') { hasAutoStarted.current = true; console.log('๐Ÿš€ Auto-starting location tracking with error handling'); startTracking().catch(err => { console.error('Auto-start failed:', err); }); } }, [autoStart, isTracking, status, startTracking]); // Cleanup on unmount react_1.default.useEffect(() => { return () => { if (isTracking) { console.log('๐Ÿงน Cleaning up location tracking on unmount'); stopTracking().catch(err => { console.error('Cleanup failed:', err); }); } }; }, [isTracking, stopTracking]); return { // State isTracking, status, currentLocation, error, // Actions startTracking, stopTracking, getCurrentLocation: getCurrentLocationSafe, waitForLocation, retryLastOperation, // Utility clearError, getHealthStatus, }; }; exports.useLocationTracking = useLocationTracking; //# sourceMappingURL=useLocationTracking.js.map