UNPKG

expo-location

Version:

Allows reading geolocation information from the device. Your app can poll for the current location or subscribe to location update events.

198 lines (181 loc) 5.9 kB
import { PermissionResponse, PermissionStatus, UnavailabilityError } from 'expo-modules-core'; import { LocationAccuracy, LocationLastKnownOptions, LocationObject, LocationOptions, } from './Location.types'; import { LocationEventEmitter } from './LocationEventEmitter'; class GeocoderError extends Error { code: string; constructor() { super('Geocoder service is not available for this device.'); this.code = 'E_NO_GEOCODER'; } } /** * Converts `GeolocationPosition` to JavaScript object. */ function geolocationPositionToJSON(position: LocationObject): LocationObject { const { coords, timestamp } = position; return { coords: { latitude: coords.latitude, longitude: coords.longitude, altitude: coords.altitude, accuracy: coords.accuracy, altitudeAccuracy: coords.altitudeAccuracy, heading: coords.heading, speed: coords.speed, }, timestamp, }; } /** * Checks whether given location didn't exceed given `maxAge` and fits in the required accuracy. */ function isLocationValid(location: LocationObject, options: LocationLastKnownOptions): boolean { const maxAge = typeof options.maxAge === 'number' ? options.maxAge : Infinity; const requiredAccuracy = typeof options.requiredAccuracy === 'number' ? options.requiredAccuracy : Infinity; const locationAccuracy = location.coords.accuracy ?? Infinity; return Date.now() - location.timestamp <= maxAge && locationAccuracy <= requiredAccuracy; } /** * Gets the permission details. The implementation is not very good as it's not * possible to query for permission on all browsers, apparently only the * latest versions will support this. */ async function getPermissionsAsync(shouldAsk = false): Promise<PermissionResponse> { if (!navigator?.permissions?.query) { throw new UnavailabilityError('expo-location', 'navigator.permissions API is not available'); } const permission = await navigator.permissions.query({ name: 'geolocation' }); if (permission.state === 'granted') { return { status: PermissionStatus.GRANTED, granted: true, canAskAgain: true, expires: 0, }; } if (permission.state === 'denied') { return { status: PermissionStatus.DENIED, granted: false, canAskAgain: true, expires: 0, }; } if (shouldAsk) { return new Promise((resolve) => { navigator.geolocation.getCurrentPosition( () => { resolve({ status: PermissionStatus.GRANTED, granted: true, canAskAgain: true, expires: 0, }); }, (positionError: GeolocationPositionError) => { if (positionError.code === positionError.PERMISSION_DENIED) { resolve({ status: PermissionStatus.DENIED, granted: false, canAskAgain: true, expires: 0, }); return; } resolve({ status: PermissionStatus.GRANTED, granted: false, canAskAgain: true, expires: 0, }); } ); }); } // The permission state is 'prompt' when the permission has not been requested // yet, tested on Chrome. return { status: PermissionStatus.UNDETERMINED, granted: false, canAskAgain: true, expires: 0, }; } let lastKnownPosition: LocationObject | null = null; export default { async getProviderStatusAsync(): Promise<{ locationServicesEnabled: boolean }> { return { locationServicesEnabled: 'geolocation' in navigator, }; }, async getLastKnownPositionAsync( options: LocationLastKnownOptions = {} ): Promise<LocationObject | null> { if (lastKnownPosition && isLocationValid(lastKnownPosition, options)) { return lastKnownPosition; } return null; }, async getCurrentPositionAsync(options: LocationOptions): Promise<LocationObject> { return new Promise<LocationObject>((resolve, reject) => { const resolver: PositionCallback = (position) => { lastKnownPosition = geolocationPositionToJSON(position); resolve(lastKnownPosition); }; navigator.geolocation.getCurrentPosition(resolver, reject, { maximumAge: Infinity, enableHighAccuracy: (options.accuracy ?? 0) > LocationAccuracy.Balanced, ...options, }); }); }, async removeWatchAsync(watchId: number): Promise<void> { navigator.geolocation.clearWatch(watchId); }, async watchDeviceHeading(_headingId: number): Promise<void> { console.warn('Location.watchDeviceHeading: is not supported on web'); }, async hasServicesEnabledAsync(): Promise<boolean> { return 'geolocation' in navigator; }, async geocodeAsync(): Promise<any[]> { throw new GeocoderError(); }, async reverseGeocodeAsync(): Promise<any[]> { throw new GeocoderError(); }, async watchPositionImplAsync(watchId: number, options: PositionOptions): Promise<number> { return new Promise((resolve) => { watchId = navigator.geolocation.watchPosition( (position) => { lastKnownPosition = geolocationPositionToJSON(position); LocationEventEmitter.emit('Expo.locationChanged', { watchId, location: lastKnownPosition, }); }, undefined, options ); resolve(watchId); }); }, async requestForegroundPermissionsAsync(): Promise<PermissionResponse> { return getPermissionsAsync(true); }, async requestBackgroundPermissionsAsync(): Promise<PermissionResponse> { return getPermissionsAsync(true); }, async getForegroundPermissionsAsync(): Promise<PermissionResponse> { return getPermissionsAsync(); }, async getBackgroundPermissionsAsync(): Promise<PermissionResponse> { return getPermissionsAsync(); }, };