| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | import { osmAuth } from "osm-auth" | 
					
						
							|  |  |  | import { Store, Stores, UIEventSource } from "../UIEventSource" | 
					
						
							|  |  |  | import { OsmPreferences } from "./OsmPreferences" | 
					
						
							|  |  |  | import { Utils } from "../../Utils" | 
					
						
							|  |  |  | import { LocalStorageSource } from "../Web/LocalStorageSource" | 
					
						
							|  |  |  | import { AuthConfig } from "./AuthConfig" | 
					
						
							|  |  |  | import Constants from "../../Models/Constants" | 
					
						
							| 
									
										
										
										
											2024-12-13 14:43:53 +01:00
										 |  |  | import { Feature, Point } from "geojson" | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  | import { AndroidPolyfill } from "../Web/AndroidPolyfill" | 
					
						
							|  |  |  | import { QueryParameters } from "../Web/QueryParameters" | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | interface OsmUserInfo { | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |     id: number | 
					
						
							|  |  |  |     display_name: string | 
					
						
							|  |  |  |     account_created: string | 
					
						
							|  |  |  |     description: string | 
					
						
							|  |  |  |     contributor_terms: { | 
					
						
							|  |  |  |         agreed: boolean | 
					
						
							|  |  |  |         pd: boolean | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |     img?: { | 
					
						
							|  |  |  |         href: string | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |     roles: string[] | 
					
						
							|  |  |  |     changesets: { | 
					
						
							|  |  |  |         count: number | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |     traces: { | 
					
						
							|  |  |  |         count: number | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |     blocks: { | 
					
						
							|  |  |  |         received: { | 
					
						
							|  |  |  |             count: number | 
					
						
							|  |  |  |             active: number | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |     home?: { | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         lat: number | 
					
						
							|  |  |  |         lon: number | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  |         zoom: number | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |     languages: string[] | 
					
						
							|  |  |  |     messages: { | 
					
						
							|  |  |  |         received: { | 
					
						
							|  |  |  |             count: number | 
					
						
							|  |  |  |             unread: number | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         sent: { | 
					
						
							|  |  |  |             count: number | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         id: number | 
					
						
							|  |  |  |         display_name: string | 
					
						
							|  |  |  |         account_created: string | 
					
						
							|  |  |  |         description: string | 
					
						
							|  |  |  |         contributor_terms: { agreed: boolean } | 
					
						
							|  |  |  |         roles: [] | 
					
						
							|  |  |  |         changesets: { count: number } | 
					
						
							|  |  |  |         traces: { count: number } | 
					
						
							|  |  |  |         blocks: { received: { count: number; active: number } } | 
					
						
							|  |  |  |         img?: { href: string } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         home: { lat: number; lon: number } | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  |         languages?: string[] | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         messages: { received: { count: number; unread: number }; sent: { count: number } } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-01 21:36:39 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  | export default interface UserDetails { | 
					
						
							|  |  |  |     name: string | 
					
						
							|  |  |  |     uid: number | 
					
						
							|  |  |  |     csCount: number | 
					
						
							|  |  |  |     img?: string | 
					
						
							|  |  |  |     unreadMessages: number | 
					
						
							|  |  |  |     totalMessages: number | 
					
						
							|  |  |  |     home?: { lon: number; lat: number } | 
					
						
							|  |  |  |     backend: string | 
					
						
							|  |  |  |     account_created: string | 
					
						
							|  |  |  |     tracesCount: number | 
					
						
							|  |  |  |     description?: string | 
					
						
							|  |  |  |     languages: string[] | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-01-06 03:30:18 +01:00
										 |  |  | export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-15 22:39:22 +01:00
										 |  |  | interface CapabilityResult { | 
					
						
							| 
									
										
										
										
											2024-12-17 04:23:24 +01:00
										 |  |  |     version: "0.6" | string | 
					
						
							|  |  |  |     generator: "OpenStreetMap server" | string | 
					
						
							|  |  |  |     copyright: "OpenStreetMap and contributors" | string | 
					
						
							|  |  |  |     attribution: "http://www.openstreetmap.org/copyright" | string | 
					
						
							|  |  |  |     license: "http://opendatacommons.org/licenses/odbl/1-0/" | string | 
					
						
							|  |  |  |     api: { | 
					
						
							|  |  |  |         version: { minimum: "0.6"; maximum: "0.6" } | 
					
						
							|  |  |  |         area: { maximum: 0.25 | number } | 
					
						
							|  |  |  |         note_area: { maximum: 25 | number } | 
					
						
							|  |  |  |         tracepoints: { per_page: 5000 | number } | 
					
						
							|  |  |  |         waynodes: { maximum: 2000 | number } | 
					
						
							|  |  |  |         relationmembers: { maximum: 32000 | number } | 
					
						
							|  |  |  |         changesets: { | 
					
						
							|  |  |  |             maximum_elements: 10000 | number | 
					
						
							|  |  |  |             default_query_limit: 100 | number | 
					
						
							|  |  |  |             maximum_query_limit: 100 | number | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         notes: { default_query_limit: 100 | number; maximum_query_limit: 10000 | number } | 
					
						
							|  |  |  |         timeout: { seconds: 300 | number } | 
					
						
							|  |  |  |         status: { | 
					
						
							|  |  |  |             database: OsmServiceState | 
					
						
							|  |  |  |             api: OsmServiceState | 
					
						
							|  |  |  |             gpx: OsmServiceState | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     policy: { | 
					
						
							|  |  |  |         imagery: { | 
					
						
							|  |  |  |             blacklist: { regex: string }[] | 
					
						
							| 
									
										
										
										
											2024-12-15 22:39:22 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | export class OsmConnection { | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     public auth: osmAuth | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Details of the currently logged-in user; undefined if not logged in | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public userDetails: UIEventSource<UserDetails | undefined> | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     public isLoggedIn: Store<boolean> | 
					
						
							|  |  |  |     public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         "unknown" | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  |     public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         "unknown" | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         "not-attempted" | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  |     public preferencesHandler: OsmPreferences | 
					
						
							|  |  |  |     public readonly _oauth_config: AuthConfig | 
					
						
							|  |  |  |     private readonly _dryRun: Store<boolean> | 
					
						
							|  |  |  |     private readonly fakeUser: boolean | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     private readonly _iframeMode: boolean | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     private readonly _singlePage: boolean | 
					
						
							|  |  |  |     private isChecking = false | 
					
						
							| 
									
										
										
										
											2024-04-01 02:40:21 +02:00
										 |  |  |     private readonly _doCheckRegularly | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor(options?: { | 
					
						
							|  |  |  |         dryRun?: Store<boolean> | 
					
						
							|  |  |  |         fakeUser?: false | boolean | 
					
						
							|  |  |  |         oauth_token?: UIEventSource<string> | 
					
						
							|  |  |  |         // Used to keep multiple changesets open and to write to the correct changeset
 | 
					
						
							|  |  |  |         singlePage?: boolean | 
					
						
							| 
									
										
										
										
											2025-01-22 02:39:28 +01:00
										 |  |  |         attemptLogin?: boolean | 
					
						
							| 
									
										
										
										
											2024-04-01 02:40:21 +02:00
										 |  |  |         /** | 
					
						
							|  |  |  |          * If true: automatically check if we're still online every 5 minutes + fetch messages | 
					
						
							|  |  |  |          */ | 
					
						
							|  |  |  |         checkOnlineRegularly?: true | boolean | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     }) { | 
					
						
							|  |  |  |         options ??= {} | 
					
						
							|  |  |  |         this.fakeUser = options?.fakeUser ?? false | 
					
						
							|  |  |  |         this._singlePage = options?.singlePage ?? true | 
					
						
							|  |  |  |         this._oauth_config = Constants.osmAuthConfig | 
					
						
							| 
									
										
										
										
											2024-04-01 02:40:21 +02:00
										 |  |  |         this._doCheckRegularly = options?.checkOnlineRegularly ?? true | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         console.debug("Using backend", this._oauth_config.url) | 
					
						
							|  |  |  |         this._iframeMode = Utils.runningFromConsole ? false : window !== window.top | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Check if there are settings available in environment variables, and if so, use those
 | 
					
						
							|  |  |  |         if ( | 
					
						
							|  |  |  |             import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && | 
					
						
							|  |  |  |             import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined | 
					
						
							|  |  |  |         ) { | 
					
						
							|  |  |  |             console.debug("Using environment variables for oauth config") | 
					
						
							| 
									
										
										
										
											2023-10-03 20:09:19 +02:00
										 |  |  |             this._oauth_config.oauth_client_id = import.meta.env.VITE_OSM_OAUTH_CLIENT_ID | 
					
						
							|  |  |  |             this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         this.userDetails = new UIEventSource<UserDetails>(undefined, "userDetails") | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         if (options.fakeUser) { | 
					
						
							|  |  |  |             const ud = this.userDetails.data | 
					
						
							|  |  |  |             ud.csCount = 5678 | 
					
						
							| 
									
										
										
										
											2023-10-30 13:45:44 +01:00
										 |  |  |             ud.uid = 42 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             ud.unreadMessages = 0 | 
					
						
							|  |  |  |             ud.name = "Fake user" | 
					
						
							|  |  |  |             ud.totalMessages = 42 | 
					
						
							| 
									
										
										
										
											2023-11-19 01:05:15 +01:00
										 |  |  |             ud.languages = ["en"] | 
					
						
							| 
									
										
										
										
											2024-08-14 13:53:56 +02:00
										 |  |  |             ud.description = | 
					
						
							|  |  |  |                 "The 'fake-user' is a URL-parameter which allows to test features without needing an OSM account or even internet connection." | 
					
						
							| 
									
										
										
										
											2024-03-11 00:01:44 +01:00
										 |  |  |             this.loadingStatus.setData("logged-in") | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         this.updateCapabilities() | 
					
						
							| 
									
										
										
										
											2024-03-11 00:01:44 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         this.isLoggedIn = this.userDetails.map( | 
					
						
							|  |  |  |             (user) => | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |                 !!user && | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |                 (this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"), | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             [this.apiIsOnline] | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-22 02:39:28 +01:00
										 |  |  |         this.updateAuthObject(false) | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         AndroidPolyfill.inAndroid.addCallback(() => { | 
					
						
							| 
									
										
										
										
											2025-01-22 02:43:40 +01:00
										 |  |  |             this.updateAuthObject(false) | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         if (!this.fakeUser) { | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |             this.CheckForMessagesContinuously() | 
					
						
							| 
									
										
										
										
											2024-04-01 02:55:48 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 00:01:44 +01:00
										 |  |  |         this.preferencesHandler = new OsmPreferences(this.auth, this, this.fakeUser) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (options.oauth_token?.data !== undefined) { | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |             this.auth.bootstrapToken(options.oauth_token.data, (err, result) => { | 
					
						
							|  |  |  |                 console.log("Bootstrap token called back", err, result) | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |                 this.AttemptLogin() | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             options.oauth_token.setData(undefined) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |         if ( | 
					
						
							|  |  |  |             !Utils.runningFromConsole && | 
					
						
							|  |  |  |             this.auth.authenticated() && | 
					
						
							|  |  |  |             options.attemptLogin !== false | 
					
						
							|  |  |  |         ) { | 
					
						
							| 
									
										
										
										
											2023-10-24 00:35:42 +02:00
										 |  |  |             this.AttemptLogin() | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             console.log("Not authenticated") | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-08-26 15:36:04 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-10-17 14:39:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     public GetPreference<T extends string = string>( | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         key: string, | 
					
						
							|  |  |  |         defaultValue: string = undefined, | 
					
						
							|  |  |  |         options?: { | 
					
						
							|  |  |  |             prefix?: string | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     ): UIEventSource<T | undefined> { | 
					
						
							| 
									
										
										
										
											2024-10-17 14:39:42 +02:00
										 |  |  |         const prefix = options?.prefix ?? "mapcomplete-" | 
					
						
							| 
									
										
										
										
											2024-09-16 23:36:42 +02:00
										 |  |  |         return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-10-17 14:39:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 23:36:42 +02:00
										 |  |  |     public getPreference<T extends string = string>( | 
					
						
							|  |  |  |         key: string, | 
					
						
							|  |  |  |         defaultValue: string = undefined, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         prefix: string = "mapcomplete-" | 
					
						
							| 
									
										
										
										
											2024-09-16 23:36:42 +02:00
										 |  |  |     ): UIEventSource<T | undefined> { | 
					
						
							|  |  |  |         return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix) | 
					
						
							| 
									
										
										
										
											2020-08-26 15:36:04 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-10-17 14:39:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     public LogOut() { | 
					
						
							|  |  |  |         this.auth.logout() | 
					
						
							| 
									
										
										
										
											2025-02-05 01:15:00 +01:00
										 |  |  |         this.userDetails.setData(undefined) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         console.log("Logged out") | 
					
						
							|  |  |  |         this.loadingStatus.setData("not-attempted") | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The backend host, without path or trailing '/' | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * new OsmConnection().Backend() // => "https://www.openstreetmap.org"
 | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public Backend(): string { | 
					
						
							|  |  |  |         return this._oauth_config.url | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 01:15:00 +01:00
										 |  |  |     public async AttemptLogin() { | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         this.updateCapabilities() | 
					
						
							| 
									
										
										
										
											2023-10-17 01:36:22 +02:00
										 |  |  |         if (this.loadingStatus.data !== "logged-in") { | 
					
						
							|  |  |  |             // Stay 'logged-in' if we are already logged in; this simply means we are checking for messages
 | 
					
						
							|  |  |  |             this.loadingStatus.setData("loading") | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         if (this.fakeUser) { | 
					
						
							|  |  |  |             this.loadingStatus.setData("logged-in") | 
					
						
							|  |  |  |             console.log("AttemptLogin called, but ignored as fakeUser is set") | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-01-22 02:39:28 +01:00
										 |  |  |         this.updateAuthObject(true) | 
					
						
							| 
									
										
										
										
											2024-10-17 02:10:25 +02:00
										 |  |  |         LocalStorageSource.get("location_before_login").setData( | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             Utils.runningFromConsole ? undefined : window.location.href | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 13:20:51 +01:00
										 |  |  |         this.auth.authenticate((err) => { | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |             if (!err) { | 
					
						
							|  |  |  |                 this.loadUserInfo() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |     private async loadUserInfo() { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             const result = await this.interact("user/details.json") | 
					
						
							|  |  |  |             if (result === null) { | 
					
						
							|  |  |  |                 this.loadingStatus.setData("error") | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             const data = < | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     version: "0.6" | 
					
						
							|  |  |  |                     license: "http://opendatacommons.org/licenses/odbl/1-0/" | 
					
						
							|  |  |  |                     user: OsmUserInfo | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             >JSON.parse(result) | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |             const user = data.user | 
					
						
							|  |  |  |             const userdetails: UserDetails = { | 
					
						
							|  |  |  |                 uid: user.id, | 
					
						
							|  |  |  |                 name: user.display_name, | 
					
						
							|  |  |  |                 csCount: user.changesets.count, | 
					
						
							|  |  |  |                 description: user.description, | 
					
						
							|  |  |  |                 backend: this.Backend(), | 
					
						
							|  |  |  |                 home: user.home, | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  |                 languages: user.languages ?? [], | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |                 totalMessages: user.messages.received?.count ?? 0, | 
					
						
							|  |  |  |                 img: user.img?.href, | 
					
						
							|  |  |  |                 account_created: user.account_created, | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  |                 tracesCount: user.traces?.count ?? 0, | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |                 unreadMessages: user.messages.received?.unread ?? 0, | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             this.userDetails.set(userdetails) | 
					
						
							|  |  |  |             this.loadingStatus.setData("logged-in") | 
					
						
							|  |  |  |         } catch (err) { | 
					
						
							|  |  |  |             console.log("Could not login due to:", err) | 
					
						
							|  |  |  |             this.loadingStatus.setData("error") | 
					
						
							|  |  |  |             if (err.status == 401) { | 
					
						
							|  |  |  |                 console.log("Clearing tokens...") | 
					
						
							|  |  |  |                 // Not authorized - our token probably got revoked
 | 
					
						
							|  |  |  |                 this.auth.logout() | 
					
						
							|  |  |  |                 this.LogOut() | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2025-02-05 11:41:27 +01:00
										 |  |  |                 console.log("Other error. Status:", err["status"]) | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |                 this.apiIsOnline.setData("unreachable") | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Interact with the API. | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2023-10-24 00:35:42 +02:00
										 |  |  |      * @param path the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' | 
					
						
							|  |  |  |      * @param method | 
					
						
							|  |  |  |      * @param header | 
					
						
							|  |  |  |      * @param content | 
					
						
							|  |  |  |      * @param allowAnonymous if set, will use the anonymous-connection if the main connection is not authenticated | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public async interact( | 
					
						
							|  |  |  |         path: string, | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         method: "GET" | "POST" | "PUT" | "DELETE" = "GET", | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |         header?: Record<string, string>, | 
					
						
							| 
									
										
										
										
											2023-10-24 00:35:42 +02:00
										 |  |  |         content?: string, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         allowAnonymous: boolean = false | 
					
						
							| 
									
										
										
										
											2023-10-24 00:35:42 +02:00
										 |  |  |     ): Promise<string> { | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |         const connection: osmAuth = this.auth | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |         if (allowAnonymous && !this.auth.authenticated()) { | 
					
						
							|  |  |  |             const possibleResult = await Utils.downloadAdvanced( | 
					
						
							|  |  |  |                 `${this.Backend()}/api/0.6/${path}`, | 
					
						
							|  |  |  |                 header, | 
					
						
							|  |  |  |                 method, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 content | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |             ) | 
					
						
							|  |  |  |             if (possibleResult["content"]) { | 
					
						
							| 
									
										
										
										
											2023-10-24 00:35:42 +02:00
										 |  |  |                 return possibleResult["content"] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             console.error(possibleResult) | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |             throw "Could not interact with OSM:" + possibleResult["error"] | 
					
						
							| 
									
										
										
										
											2023-10-24 00:35:42 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         if (!this.auth.authenticated()) { | 
					
						
							|  |  |  |             console.trace("Not authenticated") | 
					
						
							|  |  |  |             await Utils.waitFor(10000) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         return new Promise((ok, error) => { | 
					
						
							| 
									
										
										
										
											2023-10-24 00:35:42 +02:00
										 |  |  |             connection.xhr( | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |                 { | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                     method, | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |                     headers: header, | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                     content, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                     path: `/api/0.6/${path}`, | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 function (err, response) { | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                     if (err !== null) { | 
					
						
							|  |  |  |                         error(err) | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         ok(response) | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             ) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-17 18:42:39 +02:00
										 |  |  |     public async post<T = string>( | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         path: string, | 
					
						
							|  |  |  |         content?: string, | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |         header?: Record<string, string>, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         allowAnonymous: boolean = false | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     ): Promise<T> { | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |         return <T>await this.interact(path, "POST", header, content, allowAnonymous) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     public async put<T extends string>( | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         path: string, | 
					
						
							|  |  |  |         content?: string, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         header?: Record<string, string> | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     ): Promise<T> { | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |         return <T>await this.interact(path, "PUT", header, content) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |     public async get( | 
					
						
							|  |  |  |         path: string, | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |         header?: Record<string, string>, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         allowAnonymous: boolean = false | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |     ): Promise<string> { | 
					
						
							|  |  |  |         return await this.interact(path, "GET", header, undefined, allowAnonymous) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     public closeNote(id: number | string, text?: string): Promise<string> { | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         let textSuffix = "" | 
					
						
							|  |  |  |         if ((text ?? "") !== "") { | 
					
						
							|  |  |  |             textSuffix = "?text=" + encodeURIComponent(text) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (this._dryRun.data) { | 
					
						
							|  |  |  |             console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text) | 
					
						
							|  |  |  |             return new Promise((ok) => { | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |                 ok("") | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2022-01-08 04:22:50 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         return this.post(`notes/${id}/close${textSuffix}`) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-08 04:22:50 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     public reopenNote(id: number | string, text?: string): Promise<string> { | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         if (this._dryRun.data) { | 
					
						
							|  |  |  |             console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text) | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |             return new Promise((resolve) => { | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |                 resolve("") | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2022-01-14 01:41:19 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         let textSuffix = "" | 
					
						
							|  |  |  |         if ((text ?? "") !== "") { | 
					
						
							|  |  |  |             textSuffix = "?text=" + encodeURIComponent(text) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return this.post(`notes/${id}/reopen${textSuffix}`) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { | 
					
						
							|  |  |  |         if (this._dryRun.data) { | 
					
						
							|  |  |  |             console.warn("Dryrun enabled - not actually opening note with text ", text) | 
					
						
							|  |  |  |             return new Promise<{ id: number }>((ok) => { | 
					
						
							|  |  |  |                 window.setTimeout( | 
					
						
							|  |  |  |                     () => ok({ id: Math.floor(Math.random() * 1000) }), | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                     Math.random() * 5000 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                 ) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // Lat and lon must be strings for the API to accept it
 | 
					
						
							|  |  |  |         const content = `lat=${lat}&lon=${lon}&text=${encodeURIComponent(text)}` | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |         const response = await this.post( | 
					
						
							|  |  |  |             "notes.json", | 
					
						
							|  |  |  |             content, | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |             }, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             true | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         const parsed = JSON.parse(response) | 
					
						
							| 
									
										
										
										
											2023-10-24 00:35:42 +02:00
										 |  |  |         console.log("Got result:", parsed) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         const id = parsed.properties | 
					
						
							|  |  |  |         console.log("OPENED NOTE", id) | 
					
						
							|  |  |  |         return id | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-13 14:43:53 +01:00
										 |  |  |     public async getNote(id: number): Promise<Feature<Point>> { | 
					
						
							| 
									
										
										
										
											2024-12-17 04:23:24 +01:00
										 |  |  |         return JSON.parse(await this.get("notes/" + id + ".json")) | 
					
						
							| 
									
										
										
										
											2024-12-13 14:43:53 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 02:20:57 +01:00
										 |  |  |     public static GpxTrackVisibility = ["private", "public", "trackable", "identifiable"] as const | 
					
						
							| 
									
										
										
										
											2024-04-01 02:55:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     public async uploadGpxTrack( | 
					
						
							|  |  |  |         gpx: string, | 
					
						
							|  |  |  |         options: { | 
					
						
							|  |  |  |             description: string | 
					
						
							| 
									
										
										
										
											2024-01-11 02:20:57 +01:00
										 |  |  |             visibility: (typeof OsmConnection.GpxTrackVisibility)[number] | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             filename?: string | 
					
						
							|  |  |  |             /** | 
					
						
							|  |  |  |              * Some words to give some properties; | 
					
						
							|  |  |  |              * | 
					
						
							|  |  |  |              * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. | 
					
						
							|  |  |  |              */ | 
					
						
							|  |  |  |             labels: string[] | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     ): Promise<{ id: number }> { | 
					
						
							|  |  |  |         if (this._dryRun.data) { | 
					
						
							|  |  |  |             console.warn("Dryrun enabled - not actually uploading GPX ", gpx) | 
					
						
							| 
									
										
										
										
											2024-01-24 23:45:20 +01:00
										 |  |  |             return new Promise<{ id: number }>((ok) => { | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                 window.setTimeout( | 
					
						
							|  |  |  |                     () => ok({ id: Math.floor(Math.random() * 1000) }), | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                     Math.random() * 5000 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                 ) | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2022-02-16 00:56:48 +01:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         const contents = { | 
					
						
							|  |  |  |             file: gpx, | 
					
						
							| 
									
										
										
										
											2024-01-11 02:20:57 +01:00
										 |  |  |             description: options.description, | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             tags: options.labels?.join(",") ?? "", | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             visibility: options.visibility, | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-02-16 00:56:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 02:20:57 +01:00
										 |  |  |         if (!contents.description) { | 
					
						
							|  |  |  |             throw "The description of a GPS-trace cannot be the empty string, undefined or null" | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         const extras = { | 
					
						
							|  |  |  |             file: | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 '; filename="' + | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                 (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 '"\r\nContent-Type: application/gpx+xml', | 
					
						
							| 
									
										
										
										
											2022-02-16 00:56:48 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         const boundary = "987654" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let body = "" | 
					
						
							|  |  |  |         for (const key in contents) { | 
					
						
							|  |  |  |             body += "--" + boundary + "\r\n" | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             body += 'Content-Disposition: form-data; name="' + key + '"' | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             if (extras[key] !== undefined) { | 
					
						
							|  |  |  |                 body += extras[key] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             body += "\r\n\r\n" | 
					
						
							|  |  |  |             body += contents[key] + "\r\n" | 
					
						
							| 
									
										
										
										
											2022-02-16 00:56:48 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         body += "--" + boundary + "--\r\n" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const response = await this.post("gpx/create", body, { | 
					
						
							|  |  |  |             "Content-Type": "multipart/form-data; boundary=" + boundary, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             "Content-Length": "" + body.length, | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  |         const parsed = JSON.parse(response) | 
					
						
							|  |  |  |         console.log("Uploaded GPX track", parsed) | 
					
						
							|  |  |  |         return { id: parsed } | 
					
						
							| 
									
										
										
										
											2020-07-30 16:34:06 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-01-06 03:30:18 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |     public addCommentToNote(id: number | string, text: string): Promise<void> { | 
					
						
							|  |  |  |         if (this._dryRun.data) { | 
					
						
							|  |  |  |             console.warn("Dryrun enabled - not actually adding comment ", text, "to  note ", id) | 
					
						
							| 
									
										
										
										
											2024-07-09 10:54:41 +02:00
										 |  |  |             return Utils.waitFor(1000) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         if ((text ?? "") === "") { | 
					
						
							|  |  |  |             throw "Invalid text!" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return new Promise((ok, error) => { | 
					
						
							|  |  |  |             this.auth.xhr( | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     method: "POST", | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                     path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 function (err) { | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |                     if (err !== null) { | 
					
						
							|  |  |  |                         error(err) | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         ok() | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             ) | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-09-25 02:55:43 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * To be called by land.html | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2025-02-05 13:20:51 +01:00
										 |  |  |     public finishLogin(callback: (previousURL: string) => void) { | 
					
						
							| 
									
										
										
										
											2024-12-19 13:49:14 +01:00
										 |  |  |         this.auth.authenticate(() => { | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             // Fully authed at this point
 | 
					
						
							|  |  |  |             console.log("Authentication successful!") | 
					
						
							| 
									
										
										
										
											2024-10-17 02:10:25 +02:00
										 |  |  |             const previousLocation = LocalStorageSource.get("location_before_login") | 
					
						
							| 
									
										
										
										
											2025-02-05 13:20:51 +01:00
										 |  |  |             callback(previousLocation.data) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-01-06 03:30:18 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 13:20:51 +01:00
										 |  |  |     public getToken(): string { | 
					
						
							|  |  |  |         // https://www.openstreetmap.orgoauth2_access_token
 | 
					
						
							|  |  |  |         let prefix = this.Backend() | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         while (prefix.endsWith("/")) { | 
					
						
							|  |  |  |             prefix = prefix.substring(0, prefix.length - 2) | 
					
						
							| 
									
										
										
										
											2025-02-05 13:20:51 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         return ( | 
					
						
							|  |  |  |             QueryParameters.GetQueryParameter(prefix + "oauth_token", undefined).data ?? | 
					
						
							|  |  |  |             window.localStorage.getItem(this._oauth_config.url + "oauth2_access_token") | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2025-02-05 13:20:51 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |     private async loginAndroidPolyfill() { | 
					
						
							| 
									
										
										
										
											2025-02-05 14:24:52 +01:00
										 |  |  |         const key = "https://www.openstreetmap.orgoauth2_access_token" | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         if (localStorage.getItem(key)) { | 
					
						
							| 
									
										
										
										
											2025-02-05 14:24:52 +01:00
										 |  |  |             // We are probably already logged in
 | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-13 22:07:45 +01:00
										 |  |  |         const tokenPromise = AndroidPolyfill.requestLoginCodes() | 
					
						
							|  |  |  |         console.trace("Opening login page") | 
					
						
							|  |  |  |         await AndroidPolyfill.openLoginPage() | 
					
						
							|  |  |  |         const token = await tokenPromise | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         console.log("Got login token!", token) | 
					
						
							| 
									
										
										
										
											2025-02-05 14:24:52 +01:00
										 |  |  |         localStorage.setItem(key, token) | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         if (this.auth.authenticated()) { | 
					
						
							|  |  |  |             console.log("Logged in!") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         await this.loadUserInfo() | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-01-22 02:39:28 +01:00
										 |  |  |     private updateAuthObject(autoLogin: boolean) { | 
					
						
							| 
									
										
										
										
											2024-12-12 00:46:24 +01:00
										 |  |  |         let redirect_uri = Utils.runningFromConsole | 
					
						
							|  |  |  |             ? "https://mapcomplete.org/land.html" | 
					
						
							|  |  |  |             : window.location.protocol + "//" + window.location.host + "/land.html" | 
					
						
							| 
									
										
										
										
											2024-12-19 13:49:14 +01:00
										 |  |  |         if (AndroidPolyfill.inAndroid.data) { | 
					
						
							| 
									
										
										
										
											2024-12-12 00:46:24 +01:00
										 |  |  |             redirect_uri = "https://app.mapcomplete.org/land.html" | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         this.auth = new osmAuth({ | 
					
						
							|  |  |  |             client_id: this._oauth_config.oauth_client_id, | 
					
						
							|  |  |  |             url: this._oauth_config.url, | 
					
						
							| 
									
										
										
										
											2024-12-01 22:24:56 +01:00
										 |  |  |             scope: "read_prefs write_prefs write_api write_gpx write_notes", | 
					
						
							| 
									
										
										
										
											2024-12-12 00:46:24 +01:00
										 |  |  |             redirect_uri, | 
					
						
							| 
									
										
										
										
											2024-10-17 14:39:42 +02:00
										 |  |  |             /* We use 'singlePage' as much as possible, it is the most stable - including in PWA. | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |              * However, this breaks in iframes so we open a popup in that case | 
					
						
							|  |  |  |              */ | 
					
						
							| 
									
										
										
										
											2024-12-12 00:46:24 +01:00
										 |  |  |             singlepage: !this._iframeMode && !AndroidPolyfill.inAndroid.data, | 
					
						
							| 
									
										
										
										
											2025-01-22 02:39:28 +01:00
										 |  |  |             auto: autoLogin, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             apiUrl: this._oauth_config.api_url ?? this._oauth_config.url, | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |         if (AndroidPolyfill.inAndroid.data) { | 
					
						
							|  |  |  |             this.loginAndroidPolyfill() // NO AWAIT!
 | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-25 02:55:43 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     private CheckForMessagesContinuously() { | 
					
						
							|  |  |  |         if (this.isChecking) { | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |         Stores.Chronic(3 * 1000).addCallback(() => { | 
					
						
							|  |  |  |             if (!(this.apiIsOnline.data === "unreachable" || this.apiIsOnline.data === "offline")) { | 
					
						
							| 
									
										
										
										
											2024-04-01 02:55:48 +02:00
										 |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-01-21 20:47:57 +01:00
										 |  |  |             if (!this.isLoggedIn.data) { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-04-01 02:55:48 +02:00
										 |  |  |             try { | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |                 this.AttemptLogin() | 
					
						
							| 
									
										
										
										
											2024-04-01 02:55:48 +02:00
										 |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 console.log("Could not login due to", e) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         this.isChecking = true | 
					
						
							|  |  |  |         if (!this._doCheckRegularly) { | 
					
						
							| 
									
										
										
										
											2024-04-01 02:40:21 +02:00
										 |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |         Stores.Chronic(60 * 5 * 1000).addCallback(() => { | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |             // Check for new messages every 5 minutes
 | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |             if (this.isLoggedIn.data) { | 
					
						
							| 
									
										
										
										
											2024-04-01 02:55:48 +02:00
										 |  |  |                 try { | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |                     this.loadUserInfo() | 
					
						
							| 
									
										
										
										
											2024-04-01 02:55:48 +02:00
										 |  |  |                 } catch (e) { | 
					
						
							|  |  |  |                     console.log("Could not login due to", e) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-09-25 02:55:43 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |     private updateCapabilities(): void { | 
					
						
							| 
									
										
										
										
											2023-12-16 01:29:42 +01:00
										 |  |  |         if (this.fakeUser) { | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |         this.fetchCapabilities() | 
					
						
							|  |  |  |             .then(({ api, gpx }) => { | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |                 this.apiIsOnline.setData(api) | 
					
						
							|  |  |  |                 this.gpxServiceIsOnline.setData(gpx) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .catch((err) => { | 
					
						
							|  |  |  |                 console.log("Could not reach the api:", err) | 
					
						
							|  |  |  |                 this.apiIsOnline.set("unreachable") | 
					
						
							|  |  |  |                 this.gpxServiceIsOnline.set("unreachable") | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2023-09-25 02:55:43 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-06 18:58:19 +02:00
										 |  |  |     private readonly _userInfoCache: Record<number, OsmUserInfo> = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public async getInformationAboutUser(id: number): Promise<OsmUserInfo> { | 
					
						
							| 
									
										
										
										
											2023-11-02 04:35:32 +01:00
										 |  |  |         if (id === undefined) { | 
					
						
							|  |  |  |             return undefined | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (this._userInfoCache[id]) { | 
					
						
							|  |  |  |             return this._userInfoCache[id] | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const info = await this.get("user/" + id + ".json", { accepts: "application/json" }, true) | 
					
						
							|  |  |  |         const parsed = JSON.parse(info)["user"] | 
					
						
							|  |  |  |         this._userInfoCache[id] = parsed | 
					
						
							|  |  |  |         return parsed | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-04-01 02:55:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-31 19:55:08 +01:00
										 |  |  |     /**Does not use the OSM-auth object*/ | 
					
						
							|  |  |  |     private async fetchCapabilities(): Promise<{ | 
					
						
							| 
									
										
										
										
											2024-12-17 04:23:24 +01:00
										 |  |  |         api: OsmServiceState | 
					
						
							|  |  |  |         gpx: OsmServiceState | 
					
						
							|  |  |  |         database: OsmServiceState | 
					
						
							|  |  |  |     }> { | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         if (Utils.runningFromConsole) { | 
					
						
							| 
									
										
										
										
											2024-12-17 04:23:24 +01:00
										 |  |  |             return { api: "online", gpx: "online", database: "online" } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-12-17 04:23:24 +01:00
										 |  |  |         try { | 
					
						
							|  |  |  |             const result = await Utils.downloadJson<CapabilityResult>( | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 this.Backend() + "/api/0.6/capabilities.json" | 
					
						
							| 
									
										
										
										
											2024-12-17 04:23:24 +01:00
										 |  |  |             ) | 
					
						
							|  |  |  |             if (result?.api?.status === undefined) { | 
					
						
							|  |  |  |                 console.log("Something went wrong:", result) | 
					
						
							|  |  |  |                 return { api: "unreachable", gpx: "unreachable", database: "unreachable" } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return result.api.status | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							| 
									
										
										
										
											2024-12-15 22:39:22 +01:00
										 |  |  |             console.error("Could not fetch capabilities") | 
					
						
							| 
									
										
										
										
											2024-12-17 04:23:24 +01:00
										 |  |  |             return { api: "offline", gpx: "offline", database: "online" } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-01-06 03:30:18 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | } |