1import { Platform } from 'expo-modules-core';
2
3import ExpoLocation from './ExpoLocation';
4import { LocationObject, LocationAccuracy, LocationOptions } from './Location.types';
5import { LocationSubscriber } from './LocationSubscribers';
6
7type GeolocationSuccessCallback = (data: LocationObject) => void;
8type GeolocationErrorCallback = (error: any) => void;
9
10type GeolocationOptions = {
11  enableHighAccuracy?: boolean;
12};
13
14declare const global: any;
15
16// @needsAudit
17/**
18 * Polyfills `navigator.geolocation` for interop with the core React Native and Web API approach to geolocation.
19 */
20export function installWebGeolocationPolyfill(): void {
21  if (Platform.OS !== 'web') {
22    // Make sure `window.navigator` is defined in the global scope.
23    if (!('window' in global)) {
24      global.window = global;
25    }
26    if (!('navigator' in global.window)) {
27      global.window.navigator = {};
28    }
29
30    // @ts-ignore
31    window.navigator.geolocation = {
32      getCurrentPosition,
33      watchPosition,
34      clearWatch,
35
36      // We don't polyfill stopObserving, this is an internal method that probably should not even exist
37      // in react-native docs
38      stopObserving: () => {},
39    };
40  }
41}
42
43function convertGeolocationOptions(options: GeolocationOptions): LocationOptions {
44  return {
45    accuracy: options.enableHighAccuracy ? LocationAccuracy.High : LocationAccuracy.Balanced,
46  };
47}
48
49function getCurrentPosition(
50  success: GeolocationSuccessCallback,
51  error: GeolocationErrorCallback = () => {},
52  options: GeolocationOptions = {}
53): void {
54  _getCurrentPositionAsyncWrapper(success, error, options);
55}
56
57// This function exists to let us continue to return undefined from getCurrentPosition, while still
58// using async/await for the internal implementation of it
59async function _getCurrentPositionAsyncWrapper(
60  success: GeolocationSuccessCallback,
61  error: GeolocationErrorCallback,
62  options: GeolocationOptions
63): Promise<any> {
64  try {
65    await ExpoLocation.requestPermissionsAsync();
66    const result = await ExpoLocation.getCurrentPositionAsync(convertGeolocationOptions(options));
67    success(result);
68  } catch (e) {
69    error(e);
70  }
71}
72
73// Polyfill: navigator.geolocation.watchPosition
74function watchPosition(
75  success: GeolocationSuccessCallback,
76  error: GeolocationErrorCallback,
77  options: GeolocationOptions
78) {
79  const watchId = LocationSubscriber.registerCallback(success);
80
81  ExpoLocation.watchPositionImplAsync(watchId, options).catch((err) => {
82    LocationSubscriber.unregisterCallback(watchId);
83    error({ watchId, message: err.message, code: err.code });
84  });
85
86  return watchId;
87}
88
89// Polyfill: navigator.geolocation.clearWatch
90function clearWatch(watchId: number) {
91  LocationSubscriber.unregisterCallback(watchId);
92}
93