1import { Subscription } from 'expo-modules-core'; 2 3import ExpoLocation from './ExpoLocation'; 4import { LocationCallback, LocationHeadingCallback } from './Location.types'; 5import { LocationEventEmitter } from './LocationEventEmitter'; 6 7type EventObject = { 8 watchId: number; 9 [key: string]: any; 10}; 11 12let nextWatchId = 0; 13 14class Subscriber<CallbackType extends LocationCallback | LocationHeadingCallback> { 15 private eventName: string; 16 private eventDataField: string; 17 private callbacks: { [id: string]: CallbackType } = {}; 18 private eventSubscription: Subscription | null = null; 19 20 constructor(eventName: string, eventDataField: string) { 21 this.eventName = eventName; 22 this.eventDataField = eventDataField; 23 } 24 25 maybeInitializeSubscription() { 26 if (this.eventSubscription) { 27 return; 28 } 29 this.eventSubscription = LocationEventEmitter.addListener( 30 this.eventName, 31 (event: EventObject) => this.trigger(event) 32 ); 33 } 34 35 /** 36 * Registers given callback under new id which is then returned. 37 */ 38 registerCallback(callback: CallbackType): number { 39 this.maybeInitializeSubscription(); 40 const id = ++nextWatchId; 41 this.callbacks[id] = callback; 42 return id; 43 } 44 45 /** 46 * Unregisters a callback with given id and revokes the subscription if possible. 47 */ 48 unregisterCallback(id: number): void { 49 // Do nothing if we have already unregistered the callback. 50 if (!this.callbacks[id]) { 51 return; 52 } 53 54 delete this.callbacks[id]; 55 ExpoLocation.removeWatchAsync(id); 56 57 if (Object.keys(this.callbacks).length === 0 && this.eventSubscription) { 58 LocationEventEmitter.removeSubscription(this.eventSubscription); 59 this.eventSubscription = null; 60 } 61 } 62 63 trigger(event: EventObject): void { 64 const watchId = event.watchId; 65 const callback = this.callbacks[watchId]; 66 67 if (callback) { 68 callback(event[this.eventDataField]); 69 } else { 70 ExpoLocation.removeWatchAsync(watchId); 71 } 72 } 73} 74 75export const LocationSubscriber = new Subscriber<LocationCallback>( 76 'Expo.locationChanged', 77 'location' 78); 79export const HeadingSubscriber = new Subscriber<LocationHeadingCallback>( 80 'Expo.headingChanged', 81 'heading' 82); 83 84/** 85 * @private Necessary for some unit tests. 86 */ 87export function _getCurrentWatchId(): number { 88 return nextWatchId; 89} 90