diff --git a/.gitignore b/.gitignore index 2dd4cffb60..7f45392a34 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ android/ dist-full/ public/assets/icons/*.webp uploaded_images.json +/app/dist/ diff --git a/Docs/ServerConfig/hetzner/Caddyfile b/Docs/ServerConfig/hetzner/Caddyfile index 631efadd46..2fca43ec59 100644 --- a/Docs/ServerConfig/hetzner/Caddyfile +++ b/Docs/ServerConfig/hetzner/Caddyfile @@ -6,6 +6,14 @@ hosted.mapcomplete.org { } } +app.mapcomplete.org { + root * app/ + file_server + header { + +Permissions-Policy "interest-cohort=()" + } +} + countrycoder.mapcomplete.org { root * tiles/ file_server diff --git a/app/passthrough.html b/app/passthrough.html new file mode 100644 index 0000000000..457877eb28 --- /dev/null +++ b/app/passthrough.html @@ -0,0 +1,14 @@ + + + + + MapComplete Login + + + +Hey! + +Open this page with the app. (Press the menu, "open in app") + + + diff --git a/package.json b/package.json index 40aafdbdc3..bae50114ba 100644 --- a/package.json +++ b/package.json @@ -83,18 +83,19 @@ "strt": "vite --host | sed 's/localhost:/127.0.0.1:/g'", "build": "./scripts/build.sh", "build:single": "./scripts/single_build.sh", + "build:vite:app-landing": "export NODE_OPTIONS=\"--max-old-space-size=12192\" && vite build --sourcemap --config app/app.vite.config.js", "build:dbscript": "vite-node ./scripts/osm2pgsql/generateBuildDbScript.ts", "prepare-deploy": "npm run generate:service-worker && ./scripts/prepare-build.sh && npm run build", "watch:css": "tailwindcss -i src/index.css -o public/css/index-tailwind-output.css --watch", "generate:css": "tailwindcss -i src/index.css -o public/css/index-tailwind-output.css", "generate:doctests": "doctest-ts-improved . --ignore .*.spec.ts --ignore .*ConfigJson.ts", "test:run-only": "vitest --run test", - "test": " export NODE_OPTIONS=\"--max-old-space-size=8192\" && npm run clean:tests && (npm run generate:doctests 2>&1 | grep -v \"No doctests found in\") && npm run test:run-only && npm run clean:tests", + "test": "NODE_OPTIONS=\"--max-old-space-size=8192\" && npm run clean:tests && (npm run generate:doctests 2>&1 | grep -v \"No doctests found in\") && npm run test:run-only && npm run clean:tests", "generate:polygon-features": "vite-node scripts/downloadFile.ts -- https://raw.githubusercontent.com/tyrasd/osm-polygon-features/master/polygon-features.json assets/polygon-features.json", "generate:images": "vite-node scripts/generateIncludedImages.ts", "generate:translations": "vite-node scripts/generateTranslations.ts", "reset:translations": "vite-node scripts/generateTranslations.ts -- --ignore-weblate", - "generate:layouts": "vite-node scripts/generateLayouts.ts", + "generate:layouts": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayouts.ts", "generate:docs": "rm -rf Docs/Themes/* && rm -rf Docs/Layers/* && rm -rf Docs/TagInfo && mkdir Docs/TagInfo && export NODE_OPTIONS=\"--max-old-space-size=16000\" && vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts", "generate:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts", "generate:mapcomplete-changes-theme": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --generate-change-map", diff --git a/scripts/prepareAndroid.sh b/scripts/prepareAndroid.sh index 5207b2898d..50b72b1cab 100755 --- a/scripts/prepareAndroid.sh +++ b/scripts/prepareAndroid.sh @@ -2,6 +2,14 @@ # Copy all necessary files from the 'dist' directory into dist full # To be executed from the `MapComplete` repo root +nvm use +if [[ ! -f bookcases.html ]] +then + npm run generate:layeroverview + npm run generate:layouts +fi + +npm run build echo ''' import type { CapacitorConfig } from "@capacitor/cli"; @@ -47,3 +55,5 @@ cp -r dist/assets/themes dist-full/assets npx capacitor-assets generate npx cap sync + +echo "All done! Don't forget to click 'gradly sync files' in Android Studio" diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index ba48e5cf5a..70451e1df1 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -46,14 +46,14 @@ export class OsmConnection { public userDetails: UIEventSource public isLoggedIn: Store public gpxServiceIsOnline: UIEventSource = new UIEventSource( - "unknown" + "unknown", ) public apiIsOnline: UIEventSource = new UIEventSource( - "unknown" + "unknown", ) public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( - "not-attempted" + "not-attempted", ) public preferencesHandler: OsmPreferences public readonly _oauth_config: AuthConfig @@ -97,7 +97,7 @@ export class OsmConnection { this.userDetails = new UIEventSource( new UserDetails(this._oauth_config.url), - "userDetails" + "userDetails", ) if (options.fakeUser) { const ud = this.userDetails.data @@ -118,7 +118,7 @@ export class OsmConnection { (user) => user.loggedIn && (this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"), - [this.apiIsOnline] + [this.apiIsOnline], ) this.isLoggedIn.addCallback((isLoggedIn) => { if (this.userDetails.data.loggedIn == false && isLoggedIn == true) { @@ -161,7 +161,7 @@ export class OsmConnection { defaultValue: string = undefined, options?: { prefix?: string - } + }, ): UIEventSource { const prefix = options?.prefix ?? "mapcomplete-" return >this.preferencesHandler.getPreference(key, defaultValue, prefix) @@ -170,7 +170,7 @@ export class OsmConnection { public getPreference( key: string, defaultValue: string = undefined, - prefix: string = "mapcomplete-" + prefix: string = "mapcomplete-", ): UIEventSource { return >this.preferencesHandler.getPreference(key, defaultValue, prefix) } @@ -214,7 +214,7 @@ export class OsmConnection { this.updateAuthObject() LocalStorageSource.get("location_before_login").setData( - Utils.runningFromConsole ? undefined : window.location.href + Utils.runningFromConsole ? undefined : window.location.href, ) this.auth.xhr( { @@ -252,13 +252,13 @@ export class OsmConnection { data.account_created = userInfo.getAttribute("account_created") data.uid = Number(userInfo.getAttribute("id")) data.languages = Array.from( - userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang") + userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang"), ).map((l) => l.textContent) data.csCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0" + userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0", ) data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0" + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0", ) data.img = undefined @@ -290,7 +290,7 @@ export class OsmConnection { action(this.userDetails.data) } this._onLoggedIn = [] - } + }, ) } @@ -308,7 +308,7 @@ export class OsmConnection { method: "GET" | "POST" | "PUT" | "DELETE", header?: Record, content?: string, - allowAnonymous: boolean = false + allowAnonymous: boolean = false, ): Promise { const connection: osmAuth = this.auth if (allowAnonymous && !this.auth.authenticated()) { @@ -316,7 +316,7 @@ export class OsmConnection { `${this.Backend()}/api/0.6/${path}`, header, method, - content + content, ) if (possibleResult["content"]) { return possibleResult["content"] @@ -333,13 +333,13 @@ export class OsmConnection { content, path: `/api/0.6/${path}`, }, - function (err, response) { + function(err, response) { if (err !== null) { error(err) } else { ok(response) } - } + }, ) }) } @@ -348,7 +348,7 @@ export class OsmConnection { path: string, content?: string, header?: Record, - allowAnonymous: boolean = false + allowAnonymous: boolean = false, ): Promise { return await this.interact(path, "POST", header, content, allowAnonymous) } @@ -356,7 +356,7 @@ export class OsmConnection { public async put( path: string, content?: string, - header?: Record + header?: Record, ): Promise { return await this.interact(path, "PUT", header, content) } @@ -364,7 +364,7 @@ export class OsmConnection { public async get( path: string, header?: Record, - allowAnonymous: boolean = false + allowAnonymous: boolean = false, ): Promise { return await this.interact(path, "GET", header, undefined, allowAnonymous) } @@ -403,7 +403,7 @@ export class OsmConnection { return new Promise<{ id: number }>((ok) => { window.setTimeout( () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 + Math.random() * 5000, ) }) } @@ -415,7 +415,7 @@ export class OsmConnection { { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", }, - true + true, ) const parsed = JSON.parse(response) console.log("Got result:", parsed) @@ -438,14 +438,14 @@ export class OsmConnection { * 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[] - } + }, ): Promise<{ id: number }> { if (this._dryRun.data) { console.warn("Dryrun enabled - not actually uploading GPX ", gpx) return new Promise<{ id: number }>((ok) => { window.setTimeout( () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 + Math.random() * 5000, ) }) } @@ -462,9 +462,9 @@ export class OsmConnection { } const extras = { file: - '; filename="' + + "; filename=\"" + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + - '"\r\nContent-Type: application/gpx+xml', + "\"\r\nContent-Type: application/gpx+xml", } const boundary = "987654" @@ -472,7 +472,7 @@ export class OsmConnection { let body = "" for (const key in contents) { body += "--" + boundary + "\r\n" - body += 'Content-Disposition: form-data; name="' + key + '"' + body += "Content-Disposition: form-data; name=\"" + key + "\"" if (extras[key] !== undefined) { body += extras[key] } @@ -506,13 +506,13 @@ export class OsmConnection { path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, }, - function (err) { + function(err) { if (err !== null) { error(err) } else { ok() } - } + }, ) }) } @@ -520,13 +520,14 @@ export class OsmConnection { /** * To be called by land.html */ - public finishLogin(callback: (previousURL: string) => void) { + public finishLogin(callback: (previousURL: string, oauth_token: string) => void) { console.log(">>> authenticating") - this.auth.authenticate(function () { + this.auth.authenticate(() => { // Fully authed at this point console.log("Authentication successful!") + const oauth_token = window.localStorage.getItem(this._oauth_config.url + "oauth2_access_token") const previousLocation = LocalStorageSource.get("location_before_login") - callback(previousLocation.data) + callback(previousLocation.data, oauth_token) }) } @@ -534,7 +535,7 @@ export class OsmConnection { let redirect_uri = Utils.runningFromConsole ? "https://mapcomplete.org/land.html" : window.location.protocol + "//" + window.location.host + "/land.html" - if(AndroidPolyfill.inAndroid.data){ + if (AndroidPolyfill.inAndroid.data) { redirect_uri = "https://app.mapcomplete.org/land.html" AndroidPolyfill.requestLoginCodes(this) } diff --git a/src/Logic/Web/AndroidPolyfill.ts b/src/Logic/Web/AndroidPolyfill.ts index 16663637b4..81f3c70117 100644 --- a/src/Logic/Web/AndroidPolyfill.ts +++ b/src/Logic/Web/AndroidPolyfill.ts @@ -7,7 +7,7 @@ import { Store, UIEventSource } from "../UIEventSource" import { OsmConnection } from "../Osm/OsmConnection" export interface DatabridgePlugin { - request(options: { key: string }): Promise<{ value: string | object }>; + request(options: { key: string }): Promise<{ value: T }>; } const DatabridgePluginSingleton = registerPlugin("Databridge", { @@ -52,14 +52,12 @@ export class AndroidPolyfill { } 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) + const result = await DatabridgePluginSingleton.request<{oauth_token: string}>({ key: "request:login" }) + const token: string = result.value.oauth_token + console.log("AndroidPolyfill: received code and state; trying to pass them to the oauth lib",token) + const auth = osmConnection.auth.bootstrapToken(token, (err, result) => { + console.log("AndroidPolyFill: bootstraptoken returned", JSON.stringify({err, result})) }) } - } + diff --git a/src/UI/InspectorGUI.svelte b/src/UI/InspectorGUI.svelte index 8d2c99afc2..3ed4352a6e 100644 --- a/src/UI/InspectorGUI.svelte +++ b/src/UI/InspectorGUI.svelte @@ -39,7 +39,7 @@ let maplibremap: MapLibreAdaptor = new MapLibreAdaptor(map, { zoom, - location: new UIEventSource<{ lon: number; lat: number }>({ lat: lat.data, lon: lon.data }) + location: new UIEventSource<{ lon: number; lat: number }>({ lat: lat.data, lon: lon.data }), }) maplibremap.location.stabilized(500).addCallbackAndRunD(l => { lat.set(l.lat) @@ -47,18 +47,18 @@ }) let allLayers = HistoryUtils.personalTheme.layers - let layersNoFixme = allLayers.filter(l => l.id !== "fixme") -let fixme = allLayers.find(l => l.id === "fixme") + let layersNoFixme = allLayers.filter(l => l.id !== "fixme") + let fixme = allLayers.find(l => l.id === "fixme") let featuresStore = new UIEventSource([]) let features = new StaticFeatureSource(featuresStore) - ShowDataLayer.showMultipleLayers(map, features, [...layersNoFixme, fixme] , { + ShowDataLayer.showMultipleLayers(map, features, [...layersNoFixme, fixme], { zoomToFeatures: true, onClick: (f: Feature) => { selectedElement.set(undefined) Utils.waitFor(200).then(() => { selectedElement.set(f) }) - } + }, }) let osmConnection = new OsmConnection() @@ -71,7 +71,7 @@ let fixme = allLayers.find(l => l.id === "fixme") async function load() { const user = username.data - if(user.indexOf(";")<0){ + if (user.indexOf(";") < 0) { const inspectedData = inspectedContributors.data const previousEntry = inspectedData.find(e => e.name === user) @@ -81,7 +81,7 @@ let fixme = allLayers.find(l => l.id === "fixme") inspectedData.push({ label: undefined, visitedTime: new Date().toISOString(), - name: user + name: user, }) } inspectedContributors.ping() @@ -114,44 +114,44 @@ let fixme = allLayers.find(l => l.id === "fixme") let mode: "map" | "table" | "aggregate" | "images" = "map" let showPreviouslyVisited = new UIEventSource(true) -const t = Translations.t.inspector + const t = Translations.t.inspector
- +

- +

load()} /> {#if loadingData} {:else} {/if} - +
@@ -215,5 +215,5 @@ const t = Translations.t.inspector
Earlier inspected constributors
{ username.set(e.detail); load();showPreviouslyVisited.set(false) - }} /> + }} /> diff --git a/src/land.ts b/src/land.ts index e77a3497b4..bbcfca8ee7 100644 --- a/src/land.ts +++ b/src/land.ts @@ -1,7 +1,12 @@ import { OsmConnection } from "./Logic/Osm/OsmConnection" +import Constants from "./Models/Constants" console.log("Authorizing...") +const key = Constants.osmAuthConfig.url + "oauth2_state" +const st =window.localStorage.getItem(key ) +console.log("Prev state is",key, st) new OsmConnection().finishLogin((previousURL) => { + const fallback = window.location.protocol + "//" + window.location.host + "/index.html" previousURL ??= fallback if (previousURL.indexOf("/land") > 0) {