-
-
Notifications
You must be signed in to change notification settings - Fork 9
Geolocation API #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Geolocation API #84
Changes from 33 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
425a46e
Add docs draft
igorkamyshev f71882c
mark about options
igorkamyshev 4afcece
reporting
igorkamyshev 6b0a7e7
No setup
igorkamyshev 18948c0
Add subtitle
igorkamyshev 13f4a87
Merge branch 'master' into geolocation
igorkamyshev f882f88
Add `tractGeolocation` integration
igorkamyshev bb6e629
Merge branch 'geolocation' of github.com:igorkamyshev/withease into g…
igorkamyshev a28143f
Change API a bit
igorkamyshev aba6289
Fix typo
igorkamyshev e53bf27
Add types
igorkamyshev 9a0402e
Add watching back, improve custom providers
igorkamyshev 574c4ca
Add a note about priority
igorkamyshev 19030c0
hide Baidu example
igorkamyshev c82639f
Allow to exclude browser geolocation from providers
igorkamyshev 6af1cf6
update types
igorkamyshev 3a5d29d
Add browserProvider
igorkamyshev 7a48f6b
Fix typo
igorkamyshev a79d608
Add real units
igorkamyshev 02b964b
Add basic relations
igorkamyshev e411ae0
Add basic geolocation request
igorkamyshev ebaf2d8
Add basic geolocation watching
igorkamyshev 0366dcc
Refactoring
igorkamyshev 137b48e
Add custom request and watch
igorkamyshev 08e9937
Size limit
igorkamyshev 82993b4
Add live demo
igorkamyshev 17bd97b
Final touches
igorkamyshev 98bb6ff
Size
igorkamyshev 29f452a
Merge branch 'master' into geolocation
igorkamyshev 347f78e
Fix usage of external default providers
igorkamyshev 2ff825e
Merge branch 'geolocation' of github.com:igorkamyshev/withease into g…
igorkamyshev 76386a6
Merge branch 'master' into geolocation
igorkamyshev 409488f
Merge branch 'master' into geolocation
igorkamyshev a00daf0
Allow to override geolocation providers via Fork API
igorkamyshev fce73a4
Add correct safe calls
igorkamyshev 4fd7080
Build fixes
igorkamyshev bca44eb
Get red of race condition in old effector
igorkamyshev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@withease/web-api': minor | ||
--- | ||
|
||
Add `trackGeolocation` integration |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<title>web-api demo</title> | ||
<base href="/" /> | ||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<link rel="icon" type="image/x-icon" href="favicon.ico" /> | ||
</head> | ||
<body> | ||
<section> | ||
<h2>Geolocation</h2> | ||
|
||
<p>latitude: <span id="latitude" /></p> | ||
<p>longitude: <span id="longitude" /></p> | ||
|
||
<button id="get-location">Get Location</button> | ||
|
||
<button id="start-watching">Start Watching</button> | ||
<button id="stop-watching">Stop Watching</button> | ||
</section> | ||
<script type="module" src="/src/geolocation.ts"></script> | ||
</body> | ||
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { trackGeolocation } from '@withease/web-api'; | ||
|
||
const latitudeElement = document.querySelector('#latitude')!; | ||
const longitudeElement = document.querySelector('#longitude')!; | ||
const getLocationButton = document.querySelector('#get-location')!; | ||
const startWatchingButton = document.querySelector('#start-watching')!; | ||
const stopWatchingButton = document.querySelector('#stop-watching')!; | ||
|
||
const { $latitude, $longitude, request, watching } = trackGeolocation({}); | ||
|
||
$latitude.watch((latitude) => { | ||
console.log('latitude', latitude); | ||
latitudeElement.textContent = JSON.stringify(latitude); | ||
}); | ||
$longitude.watch((longitude) => { | ||
console.log('longitude', longitude); | ||
longitudeElement.textContent = JSON.stringify(longitude); | ||
}); | ||
|
||
getLocationButton.addEventListener('click', () => request()); | ||
startWatchingButton.addEventListener('click', () => watching.start()); | ||
stopWatchingButton.addEventListener('click', () => watching.stop()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { expect, test } from '@playwright/test'; | ||
|
||
const GEOLOCATION_PAGE = '/geolocation.html'; | ||
|
||
test.use({ | ||
geolocation: { longitude: 41.890221, latitude: 12.492348 }, | ||
permissions: ['geolocation'], | ||
}); | ||
|
||
test('request', async ({ page, context }) => { | ||
await page.goto(GEOLOCATION_PAGE); | ||
|
||
const latitudeContainer = await page.$('#latitude'); | ||
const longitudeContainer = await page.$('#longitude'); | ||
const getLocationButton = await page.$('#get-location'); | ||
|
||
// By default it should be null | ||
expect(await latitudeContainer!.textContent()).toBe('null'); | ||
expect(await longitudeContainer!.textContent()).toBe('null'); | ||
|
||
// After requesting the location, it should be updated | ||
await getLocationButton!.click(); | ||
expect(await latitudeContainer!.textContent()).toBe('12.492348'); | ||
expect(await longitudeContainer!.textContent()).toBe('41.890221'); | ||
|
||
// Change geolocation, values should NOT be updated | ||
await context.setGeolocation({ longitude: 22.492348, latitude: 32.890221 }); | ||
expect(await latitudeContainer!.textContent()).toBe('12.492348'); | ||
expect(await longitudeContainer!.textContent()).toBe('41.890221'); | ||
// Request the location again, values should be updated | ||
await getLocationButton!.click(); | ||
expect(await latitudeContainer!.textContent()).toBe('32.890221'); | ||
expect(await longitudeContainer!.textContent()).toBe('22.492348'); | ||
}); | ||
|
||
test('watch', async ({ page, context }) => { | ||
await page.goto(GEOLOCATION_PAGE); | ||
|
||
const latitudeContainer = await page.$('#latitude'); | ||
const longitudeContainer = await page.$('#longitude'); | ||
const startWatchingButton = await page.$('#start-watching'); | ||
const stopWatchingButton = await page.$('#stop-watching'); | ||
|
||
// By default it should be null | ||
expect(await latitudeContainer!.textContent()).toBe('null'); | ||
expect(await longitudeContainer!.textContent()).toBe('null'); | ||
|
||
// After watching the location, it should be updated immediately | ||
await startWatchingButton!.click(); | ||
expect(await latitudeContainer!.textContent()).toBe('12.492348'); | ||
expect(await longitudeContainer!.textContent()).toBe('41.890221'); | ||
|
||
// Change geolocation, values should be updated immediately | ||
await context.setGeolocation({ longitude: 22.492348, latitude: 32.890221 }); | ||
expect(await latitudeContainer!.textContent()).toBe('32.890221'); | ||
expect(await longitudeContainer!.textContent()).toBe('22.492348'); | ||
|
||
// Stop watching and change geolocation, values should NOT be updated | ||
await stopWatchingButton!.click(); | ||
await context.setGeolocation({ longitude: 42.492348, latitude: 52.890221 }); | ||
expect(await latitudeContainer!.textContent()).toBe('32.890221'); | ||
expect(await longitudeContainer!.textContent()).toBe('22.492348'); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<script setup> | ||
import { trackGeolocation } from '@withease/web-api'; | ||
import { useUnit } from 'effector-vue/composition'; | ||
|
||
const geo = trackGeolocation(); | ||
|
||
const { latitude, longitude, request } = useUnit({ | ||
latitude: geo.$latitude, | ||
longitude: geo.$longitude, | ||
request: geo.request, | ||
}); | ||
</script> | ||
|
||
<template> | ||
<p>latitude: {{ latitude }}</p> | ||
<p>longitude: {{ longitude }}</p> | ||
|
||
<button v-on:click="request">Request geolocation</button> | ||
</template> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
--- | ||
title: Geolocation | ||
|
||
outline: [2, 3] | ||
--- | ||
|
||
# Geolocation <Badge text="since v1.3.0" /> | ||
|
||
Allows tracking geolocation with [_Events_](https://effector.dev/en/api/effector/event/) and [_Stores_](https://effector.dev/docs/api/effector/store). | ||
|
||
::: info | ||
|
||
Uses [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/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 positive `number` representing the maximum age in milliseconds of a possible cached position that is acceptable to return. If set to `0`, it means that the device cannot use a cached position and must attempt to retrieve the real current position. If set to `Infinity` the device must return a cached position regardless of its age. | ||
- `timeout?`: a positive `number` 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 is `Infinity`. | ||
- `enableHighAccuracy?`: a `boolean` that indicates the application would like to receive the best possible results. | ||
|
||
```ts | ||
import { trackGeolocation } from '@withease/web-api'; | ||
|
||
const { $location, $latitude, $longitude, request, reporting, watching } = | ||
trackGeolocation({ | ||
maximumAge, | ||
timeout, | ||
enableHighAccuracy, | ||
}); | ||
``` | ||
|
||
Returns an object with: | ||
|
||
- `$location` - [_Store_](https://effector.dev/docs/api/effector/store) with the current location in the format `{ latitude, longitude }` | ||
- `$latitude` - [_Store_](https://effector.dev/docs/api/effector/store) with the current latitude | ||
- `$longitude` - [_Store_](https://effector.dev/docs/api/effector/store) with the current longitude | ||
- `request` - [_EventCallable_](https://effector.dev/en/api/effector/event/#eventCallable) that has to be called to get the current location | ||
- `watching` - an object with the following properties: | ||
- `start` - [_EventCallable_](https://effector.dev/en/api/effector/event/#eventCallable) that has to be called to start watching the current location | ||
- `stop` - [_EventCallable_](https://effector.dev/en/api/effector/event/#eventCallable) that has to be called to stop watching the current location | ||
- `$active` - [_Store_](https://effector.dev/docs/api/effector/store) with `true` if watching is started and `false` if watching is stopped | ||
- `reporting` - an object with the following properties: | ||
- `failed` - [_Event_](https://effector.dev/en/api/effector/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._ | ||
|
||
<script setup lang="ts"> | ||
import demoFile from './geolocation.live.vue?raw'; | ||
</script> | ||
|
||
<LiveDemo :demoFile="demoFile" /> | ||
|
||
## Additional capabilities | ||
|
||
### 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](https://lbsyun.baidu.com/index.php?title=jspopular/guide/geolocation), [Autonavi](https://lbsyun.baidu.com/index.php?title=jspopular/guide/geolocation), or [Tencent](https://lbs.qq.com/webApi/component/componentGuide/componentGeolocation). | ||
|
||
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. | ||
|
||
```ts | ||
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: | ||
|
||
```ts | ||
/* 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; | ||
}; | ||
``` | ||
|
||
::: details Baidu example | ||
|
||
For example, in case of Baidu, you can write something like this: | ||
|
||
```ts | ||
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`](https://github.com/michalchudziak/react-native-geolocation) package and do not use `navigator.geolocation` directly. You can easily achieve this by excluding `trackGeolocation.browserProvider` from the list of providers. | ||
|
||
```ts | ||
import ReactNativeGeolocation from '@react-native-community/geolocation'; | ||
|
||
const geo = trackGeolocation({ | ||
/* ... */ | ||
providers: [ | ||
trackGeolocation.browserProvider, // [!code --] | ||
ReactNativeGeolocation, // [!code ++] | ||
], | ||
}); | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ | |
"size-limit": [ | ||
{ | ||
"path": "./dist/web-api.js", | ||
"limit": "1.58 kB" | ||
"limit": "2.38 kB" | ||
} | ||
] | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.