Geolocation since v1.3.0
Allows tracking geolocation with Events and Stores.
INFO
Uses Geolocation API under the hood
Usage
All you need to do is to create an integration by calling trackGeolocation
with an integration options:
maximumAge?
: a positivenumber
representing the maximum age in milliseconds of a possible cached position that is acceptable to return. If set to0
, it means that the device cannot use a cached position and must attempt to retrieve the real current position. If set toInfinity
the device must return a cached position regardless of its age.timeout?
: a positivenumber
representing the maximum length of time (in milliseconds) the device is allowed to take in order to return a position. The maximum value for this attribute isInfinity
.enableHighAccuracy?
: aboolean
that indicates the application would like to receive the best possible results.
import { trackGeolocation } from '@withease/web-api';
const { $location, $latitude, $longitude, request, reporting, watching } =
trackGeolocation({
maximumAge,
timeout,
enableHighAccuracy,
});
Returns an object with:
$location
- Store with the current location in the format{ latitude, longitude }
$latitude
- Store with the current latitude$longitude
- Store with the current longituderequest
- EventCallable that has to be called to get the current locationwatching
- an object with the following properties:start
- EventCallable that has to be called to start watching the current locationstop
- EventCallable that has to be called to stop watching the current location$active
- Store withtrue
if watching is started andfalse
if watching is stopped
reporting
- an object with the following properties:failed
- Event that fires when the location request fails
Live demo
Let us show you a live demo of how it works. The following demo displays $latitude
and $longitude
values. Click "Request geolocation" button to retrieve it.
Additional capabilities
While creating an integration, you can override the default geolocation provider with your custom one. It can be done by passing an array of providers to the trackGeolocation
function.
import { trackGeolocation } from '@withease/web-api';
const geo = trackGeolocation({
/* ... */
// by default providers field contains trackGeolocation.browserProvider
// which represents the browser built-in Geolocation API
providers: [trackGeolocation.browserProvider],
});
The logic is quite straightforward: integration will call providers one by one until one of them returns the location. The first provider that returns the location will be used.
Regional restrictions
In some countries and regions, the use of geolocation can be restricted. If you are aiming to provide a service in such locations, you use some local providers to get the location of the user. For example, in China, you can use Baidu, Autonavi, or Tencent.
Geolocation integration of @withease/web-api
allows to use any provider additionally to the default one provided by the browser. To do so, you need to pass an providers
option to the trackGeolocation
function.
import { trackGeolocation } from '@withease/web-api';
const geo = trackGeolocation({
/* ... */
providers: [
/* default browser Geolocation API */
trackGeolocation.browserProvider,
/* your custom providers */
],
});
Any provider should conform to the following contract:
/* This type mimics https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPosition */
type CustomGeolocationPosition = {
timestamp: number;
coords: {
latitude: number;
longitude: number;
accuracy?: number;
altitude?: number;
altitudeAccuracy?: number;
heading?: number;
speed?: number;
};
};
/* This type mimics https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError */
type CustomGeolocationError = {
/*
* You have to map your error codes to the Geolocation API error codes
* In case of unknown error, you are free to skip this field
*/
code?: 'PERMISSION_DENIED' | 'POSITION_UNAVAILABLE' | 'TIMEOUT';
/*
* You can provide a custom message for the error
*/
message?: string;
/*
* You can provide a raw error object from your provider
*/
raw?: unknown;
};
type CustomProvider = (
/* All options would be passed from trackGeolocation call */ {
maximumAge,
timeout,
enableHighAccuracy,
}
) => {
/* This function can throw CustomGeolocationError in case of error */
getCurrentPosition: () => Promise<CustomGeolocationPosition>;
/*
* This function should call successCallback with the position or errorCallback with the error.
* Function should return an Unsubscribe function, which should stop watching the position.
*/
watchPosition: (
successCallback: (position: CustomGeolocationPosition) => void,
errorCallback: (error: CustomGeolocationError) => void
) => Unsubscribe;
};
Baidu example
For example, in case of Baidu, you can write something like this:
function baiduProvider({ maximumAge, timeout, enableHighAccuracy }) {
// Create a Baidu geolocation instance outside of the getCurrentPosition function
// to avoid creating a new instance every time the function is called
const geolocation = new BMap.Geolocation();
const getCurrentPosition = ({ maximumAge, timeout, enableHighAccuracy }) => {
// getCurrentPosition function should return a Promise
return new Promise((resolve, reject) => {
geolocation.getCurrentPosition(function (r) {
if (this.getStatus() === BMAP_STATUS_SUCCESS) {
// in case of success, resolve with the result
resolve({
timestamp: Date.now(),
coords: { latitude: r.point.lat, longitude: r.point.lng },
});
} else {
// map Baidu error codes to the Geolocation API error codes
let code;
switch (this.getStatus()) {
case BMAP_STATUS_PERMISSION_DENIED:
code = 'PERMISSION_DENIED';
break;
case BMAP_STATUS_SERVICE_UNAVAILABLE:
code = 'POSITION_UNAVAILABLE';
break;
case BMAP_STATUS_TIMEOUT:
code = 'TIMEOUT';
break;
}
// reject with the error object
reject({ code, raw: this.getStatus() });
}
});
});
};
/*
* Bailu does not support watching the position
* so, we have to write an imitation of the watchPosition function
*/
const watchPosition = (successCallback, errorCallback) => {
const timerId = setInterval(async () => {
try {
const position = await getCurrentPosition();
successCallback(position);
} catch (error) {
errorCallback(error);
}
}, 1_000);
return () => clearInterval(timerId);
};
return {
getCurrentPosition,
watchPosition,
};
}
const geo = trackGeolocation({
/* ... */
providers: [
/* default browser Geolocation API */
trackGeolocation.browserProvider,
/* Baidu provider */
baiduProvider,
],
});
Array of providers
would be used in the order they are passed to the trackGeolocation
function. The first provider that returns the coordinates would be used.
React Native
In case of React Native, it is recommended to use the @react-native-community/geolocation
package and do not use navigator.geolocation
directly. You can easily achieve this by excluding trackGeolocation.browserProvider
from the list of providers.
import ReactNativeGeolocation from '@react-native-community/geolocation';
const geo = trackGeolocation({
/* ... */
providers: [
trackGeolocation.browserProvider,
ReactNativeGeolocation,
],
});
Testing
You can pass a Store to providers
option to get a way to mock the geolocation provider during testing via Fork API.
import { createStore, fork } from 'effector';
import { trackGeolocation } from '@withease/web-api';
// Create a store with the default provider
const $geolocationProviders = createStore([trackGeolocation.browserProvider]);
// Create an integration with the store
const geo = trackGeolocation({
/* ... */
providers: $geolocationProviders,
});
// during testing, you can replace the provider with your mock
const scope = fork({ values: [[$geolocationProviders, myFakeProvider]] });
That is it, any calculations on the created Scope will use the myFakeProvider
instead of the default one.