@nativescript/geolocation
Version:
Provides API for getting and monitoring location for NativeScript app.
390 lines • 17.4 kB
JavaScript
import { Application, CoreTypes, Device, ApplicationSettings } from '@nativescript/core';
import { LocationBase, defaultGetLocationTimeout, minRangeUpdate } from './common';
export * from './common';
const locationManagers = {};
const locationListeners = {};
const permissionListeners = {};
let watchId = 0;
let attachedForErrorHandling = false;
var LocationListenerImpl = /** @class */ (function (_super) {
__extends(LocationListenerImpl, _super);
function LocationListenerImpl() {
return _super !== null && _super.apply(this, arguments) || this;
}
LocationListenerImpl.initWithLocationError = function (successCallback, error) {
var listener = LocationListenerImpl.new();
watchId++;
listener.id = watchId;
listener._onLocation = successCallback;
listener._onError = error;
return listener;
};
LocationListenerImpl.initWithPermissionError = function (permissionCallback, error) {
var listener = LocationListenerImpl.new();
watchId++;
listener.id = watchId;
listener._onPermissionChange = permissionCallback;
listener._onError = error;
return listener;
};
LocationListenerImpl.initWithPromiseCallbacks = function (resolve, reject, authorizeAlways) {
if (authorizeAlways === void 0) { authorizeAlways = false; }
var listener = LocationListenerImpl.new();
watchId++;
listener.id = watchId;
listener._resolve = resolve;
listener._reject = reject;
listener.authorizeAlways = authorizeAlways;
return listener;
};
LocationListenerImpl.prototype.locationManagerDidUpdateLocations = function (manager, locations) {
if (this._onLocation) {
for (var i = 0, count = locations.count; i < count; i++) {
var location = locationFromCLLocation(locations.objectAtIndex(i));
this._onLocation(location);
}
}
};
LocationListenerImpl.prototype.locationManagerDidFailWithError = function (manager, error) {
if (this._onError) {
this._onError(new Error(error.localizedDescription));
}
};
LocationListenerImpl.prototype.locationManagerDidChangeAuthorization = function (manager) {
this._handleAuthorizationChange(getIOSLocationManagerStatus());
};
LocationListenerImpl.prototype.locationManagerDidChangeAuthorizationStatus = function (manager, status) {
if (getVersionMaj() < 14) {
this._handleAuthorizationChange(status);
}
};
LocationListenerImpl.prototype._handleAuthorizationChange = function (status) {
// the permisssion listener doesn't resolve
if (this._onPermissionChange) {
this._onPermissionChange(status);
return;
}
if (this.authorizeAlways) {
ApplicationSettings.setBoolean('hasRequestedAlwaysAuthorization', true);
}
switch (status) {
case CLAuthorizationStatus.kCLAuthorizationStatusNotDetermined:
break;
case CLAuthorizationStatus.kCLAuthorizationStatusRestricted:
break;
case CLAuthorizationStatus.kCLAuthorizationStatusDenied:
if (this._reject) {
LocationMonitor.stopLocationMonitoring(this.id);
this._reject(new Error('Authorization Denied.'));
}
break;
case CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways:
if (this._resolve) {
LocationMonitor.stopLocationMonitoring(this.id);
this._resolve();
}
break;
case CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedWhenInUse:
if (this._resolve) {
LocationMonitor.stopLocationMonitoring(this.id);
this._resolve();
}
break;
default:
break;
}
};
LocationListenerImpl.ObjCProtocols = [CLLocationManagerDelegate]; // tslint:disable-line:variable-name
return LocationListenerImpl;
}(NSObject));
function locationFromCLLocation(clLocation) {
const location = new Location();
location.latitude = clLocation.coordinate.latitude;
location.longitude = clLocation.coordinate.longitude;
location.altitude = clLocation.altitude;
location.horizontalAccuracy = clLocation.horizontalAccuracy;
location.verticalAccuracy = clLocation.verticalAccuracy;
location.speed = clLocation.speed;
location.direction = clLocation.course;
const timeIntervalSince1970 = NSDate.dateWithTimeIntervalSinceDate(0, clLocation.timestamp).timeIntervalSince1970;
location.timestamp = new Date(timeIntervalSince1970 * 1000);
location.ios = clLocation;
return location;
}
function clLocationFromLocation(location) {
const hAccuracy = location.horizontalAccuracy ? location.horizontalAccuracy : -1;
const vAccuracy = location.verticalAccuracy ? location.verticalAccuracy : -1;
const speed = location.speed ? location.speed : -1;
const course = location.direction ? location.direction : -1;
const altitude = location.altitude ? location.altitude : -1;
const timestamp = location.timestamp ? location.timestamp : null;
const iosLocation = CLLocation.alloc().initWithCoordinateAltitudeHorizontalAccuracyVerticalAccuracyCourseSpeedTimestamp(CLLocationCoordinate2DMake(location.latitude, location.longitude), altitude, hAccuracy, vAccuracy, course, speed, timestamp);
return iosLocation;
}
function errorHandler(errData) {
while (watchId !== 0) {
clearWatch(watchId);
watchId--;
}
}
function getVersionMaj() {
return parseInt(Device.osVersion.split('.')[0]);
}
// options - desiredAccuracy, updateDistance, minimumUpdateTime, maximumAge, timeout
export function getCurrentLocation(options) {
return new Promise(function (resolve, reject) {
enableLocationRequest().then(() => {
options = options || {};
if (options.timeout === 0) {
// we should take any cached location e.g. lastKnownLocation
const lastLocation = LocationMonitor.getLastKnownLocation();
if (lastLocation) {
if (typeof options.maximumAge === 'number') {
if (lastLocation.timestamp.valueOf() + options.maximumAge > new Date().valueOf()) {
resolve(lastLocation);
}
else {
reject(new Error('Last known location too old!'));
}
}
else {
resolve(lastLocation);
}
}
else {
reject(new Error('There is no last known location!'));
}
}
else {
let timerId;
let locListener;
let initLocation;
const stopTimerAndMonitor = function (locListenerId) {
if (typeof timerId === 'number') {
clearTimeout(timerId);
timerId = null;
}
LocationMonitor.stopLocationMonitoring(locListenerId);
};
const successCallback = function (location) {
if (getVersionMaj() < 9) {
if (typeof options.maximumAge === 'number' && location.timestamp.valueOf() + options.maximumAge < new Date().valueOf()) {
// returned location is too old, but we still have some time before the timeout so maybe wait a bit?
return;
}
if (options.desiredAccuracy !== CoreTypes.Accuracy.any && !initLocation) {
// regardless of desired accuracy ios returns first location as quick as possible even if not as accurate as requested
initLocation = location;
return;
}
}
stopTimerAndMonitor(locListener.id);
resolve(location);
};
locListener = LocationListenerImpl.initWithLocationError(successCallback, reject);
try {
if (getVersionMaj() >= 9) {
LocationMonitor.requestLocation(options, locListener);
}
else {
LocationMonitor.startLocationMonitoring(options, locListener);
}
}
catch (e) {
stopTimerAndMonitor(locListener.id);
reject(e);
}
if (typeof options.timeout === 'number') {
timerId = setTimeout(function () {
LocationMonitor.stopLocationMonitoring(locListener.id);
reject(new Error('Timeout while searching for location!'));
}, options.timeout || defaultGetLocationTimeout);
}
}
}, reject);
});
}
export async function watchLocation(successCallback, errorCallback, options) {
if (!attachedForErrorHandling) {
attachedForErrorHandling = true;
Application.on(Application.uncaughtErrorEvent, errorHandler.bind(this));
}
const zonedSuccessCallback = global.zonedCallback(successCallback);
const zonedErrorCallback = global.zonedCallback(errorCallback);
const locListener = LocationListenerImpl.initWithLocationError(zonedSuccessCallback, zonedErrorCallback);
try {
const iosLocManager = getIOSLocationManager(locListener, options);
iosLocManager.startUpdatingLocation();
return locListener.id;
}
catch (e) {
LocationMonitor.stopLocationMonitoring(locListener.id);
zonedErrorCallback(e);
return null;
}
}
export function watchPermissionStatus(permissionCallback, errorCallback) {
const zonedPermissionCallback = global.zonedCallback(permissionCallback);
const zonedErrorCallback = global.zonedCallback(errorCallback);
const permListener = LocationListenerImpl.initWithPermissionError(zonedPermissionCallback, zonedErrorCallback);
try {
const iosLocManager = new CLLocationManager();
iosLocManager.delegate = permListener;
locationManagers[permListener.id] = iosLocManager;
permissionListeners[permListener.id] = permListener;
zonedPermissionCallback(iosLocManager.authorizationStatus);
return permListener.id;
}
catch (e) {
LocationMonitor.stopLocationMonitoring(permListener.id);
zonedErrorCallback(e);
return null;
}
}
export function clearWatch(_watchId) {
LocationMonitor.stopLocationMonitoring(_watchId);
}
export function enableLocationRequest(always, openSettingsIfLocationHasBeenDenied) {
return new Promise(function (resolve, reject) {
const locationIsEnabled = _isEnabled(always);
if (locationIsEnabled) {
resolve();
return;
}
else {
const status = getIOSLocationManagerStatus();
if ((status === 2 /* CLAuthorizationStatus.kCLAuthorizationStatusDenied */ && openSettingsIfLocationHasBeenDenied) || (!_systemDialogWillShow(always, status) && openSettingsIfLocationHasBeenDenied)) {
// now open the Settings so the user can toggle the Location permission
UIApplication.sharedApplication.openURL(NSURL.URLWithString(UIApplicationOpenSettingsURLString));
reject();
}
else {
const listener = LocationListenerImpl.initWithPromiseCallbacks(resolve, reject, always);
try {
const manager = getIOSLocationManager(listener, null);
if (always) {
manager.requestAlwaysAuthorization();
}
else {
manager.requestWhenInUseAuthorization();
}
}
catch (e) {
LocationMonitor.stopLocationMonitoring(listener.id);
reject(e);
}
}
}
});
}
function _systemDialogWillShow(always, status) {
// the system dialog for "always" permission will not show if we requested it previously and currently have "when use" permission
return !(status === 4 /* CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedWhenInUse */ && always && ApplicationSettings.getBoolean('hasRequestedAlwaysAuthorization', false));
}
function _isEnabled(always) {
if (CLLocationManager.locationServicesEnabled()) {
const status = getIOSLocationManagerStatus();
// CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedWhenInUse and
// CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways are options that are available in iOS 8.0+
// while CLAuthorizationStatus.kCLAuthorizationStatusAuthorized is here to support iOS 8.0-.
return ((status === 4 /* CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedWhenInUse */ && !always) ||
status === 3 /* CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways */ ||
// @ts-ignore: Types have no overlap error
status === 3 /* CLAuthorizationStatus.kCLAuthorizationStatusAuthorized */);
}
return false;
}
export function isEnabled(options) {
return new Promise(function (resolve, reject) {
const isEnabledResult = _isEnabled();
resolve(isEnabledResult);
});
}
export function getIOSLocationManagerStatus() {
return CLLocationManager.authorizationStatus();
}
export function distance(loc1, loc2) {
if (!loc1.ios) {
loc1.ios = clLocationFromLocation(loc1);
}
if (!loc2.ios) {
loc2.ios = clLocationFromLocation(loc2);
}
return loc1.ios.distanceFromLocation(loc2.ios);
}
export class LocationMonitor {
static getLastKnownLocation() {
let iosLocation;
for (let locManagerId in locationManagers) {
if (locationManagers.hasOwnProperty(locManagerId)) {
const tempLocation = locationManagers[locManagerId].location;
if (!iosLocation || tempLocation.timestamp > iosLocation.timestamp) {
iosLocation = tempLocation;
}
}
}
if (iosLocation) {
return locationFromCLLocation(iosLocation);
}
const locListener = LocationListenerImpl.initWithLocationError(null);
iosLocation = getIOSLocationManager(locListener, null).location;
if (iosLocation) {
return locationFromCLLocation(iosLocation);
}
return null;
}
static requestLocation(options, locListener) {
const iosLocManager = getIOSLocationManager(locListener, options);
iosLocManager.requestLocation();
}
static startLocationMonitoring(options, locListener) {
const iosLocManager = getIOSLocationManager(locListener, options);
iosLocManager.startUpdatingLocation();
}
static stopLocationMonitoring(iosLocManagerId) {
if (locationManagers[iosLocManagerId]) {
locationManagers[iosLocManagerId].stopUpdatingLocation();
locationManagers[iosLocManagerId].delegate = null;
delete locationManagers[iosLocManagerId];
delete locationListeners[iosLocManagerId];
delete permissionListeners[iosLocManagerId];
}
}
static createiOSLocationManager(locListener, options) {
const iosLocManager = new CLLocationManager();
iosLocManager.delegate = locListener;
iosLocManager.desiredAccuracy = options?.desiredAccuracy ?? CoreTypes.Accuracy.high;
iosLocManager.distanceFilter = options?.updateDistance ?? minRangeUpdate;
locationManagers[locListener.id] = iosLocManager;
locationListeners[locListener.id] = locListener;
if (getVersionMaj() >= 9) {
iosLocManager.allowsBackgroundLocationUpdates = options?.iosAllowsBackgroundLocationUpdates ?? false;
}
iosLocManager.pausesLocationUpdatesAutomatically = options?.iosPausesLocationUpdatesAutomatically ?? true;
return iosLocManager;
}
}
let iosLocationManager;
function getIOSLocationManager(locListener, options) {
if (!iosLocationManager) {
return LocationMonitor.createiOSLocationManager(locListener, options);
}
else {
const manager = new iosLocationManager();
manager.delegate = locListener;
manager.desiredAccuracy = options?.desiredAccuracy ?? CoreTypes.Accuracy.high;
manager.distanceFilter = options?.updateDistance ?? minRangeUpdate;
locationManagers[locListener.id] = manager;
locationListeners[locListener.id] = locListener;
return manager;
}
}
// used for tests only
export function setCustomLocationManager(manager) {
iosLocationManager = function () {
return manager;
};
}
export class Location extends LocationBase {
}
//# sourceMappingURL=index.ios.js.map