expo-osm-sdk
Version:
OpenStreetMap component for React Native with Expo
371 lines โข 14.8 kB
JavaScript
;
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