Android: get polyfills for geolocation working, some steps for logging in

This commit is contained in:
Pieter Vander Vennet 2024-12-12 00:46:24 +01:00
parent 76e9381650
commit 14e5f1efb3
5 changed files with 52 additions and 28 deletions

View file

@ -4,6 +4,7 @@ We are using capacitor. This is a tool which packages some files into an Android
## Developing ## Developing
0. `nvm use` to make sure your using the correct android version
1. Build all the necessary files. 1. Build all the necessary files.
a. If no layer/theme changes were made, `npm run build` is sufficient a. If no layer/theme changes were made, `npm run build` is sufficient
b. Otherwise, run `npm run prepare-deploy`. b. Otherwise, run `npm run prepare-deploy`.

View file

@ -5,6 +5,7 @@ import { Utils } from "../../Utils"
import { LocalStorageSource } from "../Web/LocalStorageSource" import { LocalStorageSource } from "../Web/LocalStorageSource"
import { AuthConfig } from "./AuthConfig" import { AuthConfig } from "./AuthConfig"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { AndroidPolyfill } from "../Web/AndroidPolyfill"
interface OsmUserInfo { interface OsmUserInfo {
id: number id: number
@ -520,6 +521,7 @@ export class OsmConnection {
* To be called by land.html * To be called by land.html
*/ */
public finishLogin(callback: (previousURL: string) => void) { public finishLogin(callback: (previousURL: string) => void) {
console.log(">>> authenticating")
this.auth.authenticate(function () { this.auth.authenticate(function () {
// Fully authed at this point // Fully authed at this point
console.log("Authentication successful!") console.log("Authentication successful!")
@ -529,17 +531,22 @@ export class OsmConnection {
} }
private updateAuthObject() { private updateAuthObject() {
let redirect_uri = Utils.runningFromConsole
? "https://mapcomplete.org/land.html"
: window.location.protocol + "//" + window.location.host + "/land.html"
if(AndroidPolyfill.inAndroid.data){
redirect_uri = "https://app.mapcomplete.org/land.html"
AndroidPolyfill.requestLoginCodes(this)
}
this.auth = new osmAuth({ this.auth = new osmAuth({
client_id: this._oauth_config.oauth_client_id, client_id: this._oauth_config.oauth_client_id,
url: this._oauth_config.url, url: this._oauth_config.url,
scope: "read_prefs write_prefs write_api write_gpx write_notes", scope: "read_prefs write_prefs write_api write_gpx write_notes",
redirect_uri: Utils.runningFromConsole redirect_uri,
? "https://mapcomplete.org/land.html"
: window.location.protocol + "//" + window.location.host + "/land.html",
/* We use 'singlePage' as much as possible, it is the most stable - including in PWA. /* We use 'singlePage' as much as possible, it is the most stable - including in PWA.
* However, this breaks in iframes so we open a popup in that case * However, this breaks in iframes so we open a popup in that case
*/ */
singlepage: !this._iframeMode, singlepage: !this._iframeMode && !AndroidPolyfill.inAndroid.data,
auto: true, auto: true,
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url, apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
}) })

View file

@ -3,6 +3,7 @@ import { LocalStorageSource } from "../Web/LocalStorageSource"
import { QueryParameters } from "../Web/QueryParameters" import { QueryParameters } from "../Web/QueryParameters"
import { Translation } from "../../UI/i18n/Translation" import { Translation } from "../../UI/i18n/Translation"
import Translations from "../../UI/i18n/Translations" import Translations from "../../UI/i18n/Translations"
import { AndroidPolyfill } from "../Web/AndroidPolyfill"
export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "denied" export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "denied"
@ -157,12 +158,20 @@ export class GeoLocationState {
this.permission.setData("denied") this.permission.setData("denied")
return return
} }
if (this.permission.data !== "prompt" && this.permission.data !== "requested") { 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 // 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" // Hence that we continue the flow if it is "requested"
return return
} }
if(AndroidPolyfill.inAndroid.data){
this.permission.setData("requested")
AndroidPolyfill.geolocationPermission.addCallbackAndRunD(state => this.permission.set(state))
this.startWatching()
return
}
if (GeoLocationState.isSafari()) { if (GeoLocationState.isSafari()) {
/* /*
This is probably safari This is probably safari

View file

@ -3,16 +3,17 @@
* If this is successful, it will patch some webAPIs * If this is successful, it will patch some webAPIs
*/ */
import { registerPlugin } from "@capacitor/core" import { registerPlugin } from "@capacitor/core"
import { UIEventSource } from "../UIEventSource" import { Store, UIEventSource } from "../UIEventSource"
import { OsmConnection } from "../Osm/OsmConnection"
export interface DatabridgePlugin { export interface DatabridgePlugin {
request(options: { key: string }): Promise<{ value: string }>; request(options: { key: string }): Promise<{ value: string | object }>;
} }
const DatabridgePluginSingleton = registerPlugin<DatabridgePlugin>("Databridge", { const DatabridgePluginSingleton = registerPlugin<DatabridgePlugin>("Databridge", {
web: () => { web: () => {
return <DatabridgePlugin>{ return <DatabridgePlugin>{
async request(options: { key: string }): Promise<{ value: string }> { async request(options: { key: string }): Promise<{ value: string | object }> {
return { value: "web" } return { value: "web" }
}, },
} }
@ -21,6 +22,10 @@ const DatabridgePluginSingleton = registerPlugin<DatabridgePlugin>("Databridge",
export class AndroidPolyfill { export class AndroidPolyfill {
private readonly databridgePlugin: DatabridgePlugin = DatabridgePluginSingleton private readonly databridgePlugin: DatabridgePlugin = DatabridgePluginSingleton
private static readonly _inAndroid: UIEventSource<boolean> = new UIEventSource<boolean>(false)
public static readonly inAndroid: Store<boolean> = AndroidPolyfill._inAndroid
private static readonly _geolocationPermission: UIEventSource<"granted" | "denied" | "prompt"> = new UIEventSource("prompt")
public static readonly geolocationPermission: Store<"granted" | "denied" | "prompt"> = this._geolocationPermission
/** /**
* Registers 'navigator.' * Registers 'navigator.'
@ -28,26 +33,10 @@ export class AndroidPolyfill {
*/ */
private backfillGeolocation(databridgePlugin: DatabridgePlugin) { private backfillGeolocation(databridgePlugin: DatabridgePlugin) {
const origQueryFunc = navigator?.permissions?.query const origQueryFunc = navigator?.permissions?.query
navigator.permissions.query = async (descr: PermissionDescriptor) => { const src = UIEventSource.FromPromise(databridgePlugin.request({ key: "location:request-permission" }))
if (descr.name === "geolocation") { src.addCallbackAndRunD(permission => {
console.log("Got a geolocation permission request") AndroidPolyfill._geolocationPermission.set(<"granted" | "denied">permission.value)
const src = UIEventSource.FromPromise(databridgePlugin.request({ key: "location:request-permission" })) })
return <PermissionStatus>{
state: undefined,
addEventListener(key: "change", f: (value: "granted" | "denied") => void) {
src.addCallbackAndRunD(v => {
const content = <"granted" | "denied">v.value
f(content)
return true
})
},
}
}
if (origQueryFunc) {
return await origQueryFunc(descr)
}
}
} }
public async init() { public async init() {
@ -57,8 +46,20 @@ export class AndroidPolyfill {
console.log("Not initing Android polyfill as not in a shell; web detected") console.log("Not initing Android polyfill as not in a shell; web detected")
return return
} }
AndroidPolyfill._inAndroid.set(true)
console.log("Detected shell:", shell.value) console.log("Detected shell:", shell.value)
this.backfillGeolocation(this.databridgePlugin) this.backfillGeolocation(this.databridgePlugin)
} }
public static async requestLoginCodes(osmConnection: OsmConnection) {
const result = await DatabridgePluginSingleton.request({ key: "request:login" })
const code: string = result["code"]
const state: string = result["state"]
console.log("AndroidPolyfill: received code and state; trying to pass them to the oauth lib")
window.location.search = "?code=" + code + "&state=" + state
osmConnection.finishLogin((oldLocation) => {
console.log("Login should be completed, oldLocation is", oldLocation)
})
}
} }

View file

@ -52,6 +52,7 @@
import PanoramaxLink from "./PanoramaxLink.svelte" import PanoramaxLink from "./PanoramaxLink.svelte"
import { UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle" import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle"
import { AndroidPolyfill } from "../../Logic/Web/AndroidPolyfill"
export let state: ThemeViewState export let state: ThemeViewState
let userdetails = state.osmConnection.userDetails let userdetails = state.osmConnection.userDetails
@ -78,6 +79,7 @@
}) })
} }
}) })
let isAndroid = AndroidPolyfill.inAndroid
</script> </script>
<div <div
@ -255,7 +257,8 @@
</If> </If>
<a class="sidebar-button flex" href="geo:{$location.lat},{$location.lon}"> <a class="sidebar-button flex" href="geo:{$location.lat},{$location.lon}">
<ShareIcon /><Tr t={t.openHereDifferentApp} /> <ShareIcon />
<Tr t={t.openHereDifferentApp} />
</a> </a>
</SidebarUnit> </SidebarUnit>
@ -343,6 +346,9 @@
<div class="subtle self-end"> <div class="subtle self-end">
{Constants.vNumber} {Constants.vNumber}
{#if $isAndroid}
Android
{/if}
</div> </div>
</SidebarUnit> </SidebarUnit>
</div> </div>