| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  | import { UIEventSource } from "../UIEventSource" | 
					
						
							|  |  |  | import { LocalStorageSource } from "../Web/LocalStorageSource" | 
					
						
							| 
									
										
										
										
											2023-02-10 01:35:49 +01:00
										 |  |  | import { QueryParameters } from "../Web/QueryParameters" | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | type GeolocationState = "prompt" | "requested" | "granted" | "denied" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface GeoLocationPointProperties extends GeolocationCoordinates { | 
					
						
							|  |  |  |     id: "gps" | 
					
						
							|  |  |  |     "user:location": "yes" | 
					
						
							|  |  |  |     date: string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * An abstract representation of the current state of the geolocation. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class GeoLocationState { | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * What do we know about the current state of having access to the GPS? | 
					
						
							|  |  |  |      * If 'prompt', then we just started and didn't request access yet | 
					
						
							|  |  |  |      * 'requested' means the user tapped the 'locate me' button at least once | 
					
						
							|  |  |  |      * 'granted' means that it is granted | 
					
						
							|  |  |  |      * 'denied' means that we don't have access | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public readonly permission: UIEventSource<GeolocationState> = new UIEventSource("prompt") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-09 03:12:21 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Important to determine e.g. if we move automatically on fix or not | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |     public readonly requestMoment: UIEventSource<Date | undefined> = new UIEventSource(undefined) | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * If true: the map will center (and re-center) to this location | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public readonly isLocked: UIEventSource<boolean> = new UIEventSource<boolean>(false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public readonly currentGPSLocation: UIEventSource<GeolocationCoordinates | undefined> = | 
					
						
							|  |  |  |         new UIEventSource<GeolocationCoordinates | undefined>(undefined) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * A small flag on localstorage. If the user previously granted the geolocation, it will be set. | 
					
						
							|  |  |  |      * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. | 
					
						
							|  |  |  |      * If the user denies the geolocation this time, we unset this flag | 
					
						
							|  |  |  |      * @private | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = <any>( | 
					
						
							|  |  |  |         LocalStorageSource.Get("geolocation-permissions") | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Used to detect a permission retraction | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private readonly _grantedThisSession: UIEventSource<boolean> = new UIEventSource<boolean>(false) | 
					
						
							|  |  |  |     constructor() { | 
					
						
							|  |  |  |         const self = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.permission.addCallbackAndRunD(async (state) => { | 
					
						
							|  |  |  |             if (state === "granted") { | 
					
						
							|  |  |  |                 self._previousLocationGrant.setData("true") | 
					
						
							|  |  |  |                 self._grantedThisSession.setData(true) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (state === "prompt" && self._grantedThisSession.data) { | 
					
						
							|  |  |  |                 // This is _really_ weird: we had a grant earlier, but it's 'prompt' now?
 | 
					
						
							|  |  |  |                 // This means that the rights have been revoked again!
 | 
					
						
							|  |  |  |                 //   self.permission.setData("denied")
 | 
					
						
							|  |  |  |                 self._previousLocationGrant.setData("false") | 
					
						
							|  |  |  |                 self.permission.setData("denied") | 
					
						
							| 
									
										
										
										
											2022-12-23 15:52:22 +01:00
										 |  |  |                 self.currentGPSLocation.setData(undefined) | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |                 console.warn("Detected a downgrade in permissions!") | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (state === "denied") { | 
					
						
							|  |  |  |                 self._previousLocationGrant.setData("false") | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-23 15:52:22 +01:00
										 |  |  |         console.log("Previous location grant:", this._previousLocationGrant.data) | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         if (this._previousLocationGrant.data === "true") { | 
					
						
							|  |  |  |             // A previous visit successfully granted permission. Chance is high that we are allowed to use it again!
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them
 | 
					
						
							|  |  |  |             this._previousLocationGrant.setData("false") | 
					
						
							|  |  |  |             console.log("Requesting access to GPS as this was previously granted") | 
					
						
							| 
									
										
										
										
											2023-02-10 01:35:49 +01:00
										 |  |  |             const latLonGivenViaUrl = | 
					
						
							|  |  |  |                 QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") | 
					
						
							|  |  |  |             if (!latLonGivenViaUrl) { | 
					
						
							|  |  |  |                 this.requestMoment.setData(new Date()) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |             this.requestPermission() | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-12-23 15:52:22 +01:00
										 |  |  |         window["geolocation_state"] = this | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Installs the listener for updates | 
					
						
							|  |  |  |      * @private | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private async startWatching() { | 
					
						
							|  |  |  |         const self = this | 
					
						
							|  |  |  |         navigator.geolocation.watchPosition( | 
					
						
							|  |  |  |             function (position) { | 
					
						
							|  |  |  |                 self.currentGPSLocation.setData(position.coords) | 
					
						
							| 
									
										
										
										
											2022-12-23 15:52:22 +01:00
										 |  |  |                 self._previousLocationGrant.setData("true") | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |             }, | 
					
						
							|  |  |  |             function () { | 
					
						
							|  |  |  |                 console.warn("Could not get location with navigator.geolocation") | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 enableHighAccuracy: true, | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Requests the user to allow access to their position. | 
					
						
							|  |  |  |      * When granted, will be written to the 'geolocationState'. | 
					
						
							|  |  |  |      * This class will start watching | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public requestPermission() { | 
					
						
							|  |  |  |         if (typeof navigator === "undefined") { | 
					
						
							|  |  |  |             // Not compatible with this browser
 | 
					
						
							|  |  |  |             this.permission.setData("denied") | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-12-23 16:11:05 +01:00
										 |  |  |         if (this.permission.data !== "prompt" && this.permission.data !== "requested") { | 
					
						
							|  |  |  |             // If the user denies the first prompt, revokes the deny and then tries again, we have to run the flow as well
 | 
					
						
							|  |  |  |             // Hence that we continue the flow if it is "requested"
 | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-02-10 01:35:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         this.permission.setData("requested") | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             navigator?.permissions | 
					
						
							|  |  |  |                 ?.query({ name: "geolocation" }) | 
					
						
							|  |  |  |                 .then((status) => { | 
					
						
							| 
									
										
										
										
											2022-12-23 15:52:22 +01:00
										 |  |  |                     console.log("Status update: received geolocation permission is ", status.state) | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |                     this.permission.setData(status.state) | 
					
						
							|  |  |  |                     const self = this | 
					
						
							|  |  |  |                     status.onchange = function () { | 
					
						
							|  |  |  |                         self.permission.setData(status.state) | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-12-23 15:52:22 +01:00
										 |  |  |                     this.permission.setData("requested") | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |                     // We _must_ call 'startWatching', as that is the actual trigger for the popup...
 | 
					
						
							|  |  |  |                     self.startWatching() | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .catch((e) => console.error("Could not get geopermission", e)) | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							|  |  |  |             console.error("Could not get permission:", e) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |