forked from MapComplete/MapComplete
Android: get login working
This commit is contained in:
parent
00c233a2eb
commit
88c76498b6
16 changed files with 199 additions and 171 deletions
2
android
2
android
|
@ -1 +1 @@
|
||||||
Subproject commit b165ec6005cb5e9fcc7c1c2bb860344287f3d281
|
Subproject commit 917fe6a0f9ef67530f281d5603432f9c8daae0c7
|
|
@ -3,6 +3,7 @@
|
||||||
<head><title>MapComplete Auth</title></head>
|
<head><title>MapComplete Auth</title></head>
|
||||||
<body>
|
<body>
|
||||||
Authorizing and redirecting, hang on...
|
Authorizing and redirecting, hang on...
|
||||||
|
<div id="token"></div>
|
||||||
<script type="module" src="./land.ts"></script>
|
<script type="module" src="./land.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
10
app/land.ts
10
app/land.ts
|
@ -1,11 +1,17 @@
|
||||||
import { OsmConnection } from "../src/Logic/Osm/OsmConnection"
|
import { OsmConnection } from "../src/Logic/Osm/OsmConnection"
|
||||||
import Constants from "../src/Models/Constants"
|
import Constants from "../src/Models/Constants"
|
||||||
|
import { Utils } from "../src/Utils"
|
||||||
|
import { UIEventSource } from "../src/Logic/UIEventSource"
|
||||||
|
import { VariableUiElement } from "../src/UI/Base/VariableUIElement"
|
||||||
|
|
||||||
console.log("Authorizing...")
|
console.log("Authorizing...")
|
||||||
const key = Constants.osmAuthConfig.url + "oauth2_state"
|
const key = Constants.osmAuthConfig.url + "oauth2_state"
|
||||||
const st =window.localStorage.getItem(key )
|
const st =window.localStorage.getItem(key )
|
||||||
console.log("Prev state is",key, st)
|
console.log("Prev state is",key, st)
|
||||||
new OsmConnection().finishLogin((_, token: string) => {
|
const tokenSrc = new UIEventSource("")
|
||||||
console.log("Login finished, redirecting to passthrough")
|
new VariableUiElement(tokenSrc).AttachTo("token")
|
||||||
|
new OsmConnection().finishLogin(async (_, token: string) => {
|
||||||
|
console.log("Login finished, redirecting to passthrough; token is "+token)
|
||||||
|
await Utils.waitFor(10)
|
||||||
window.location.href = "https://app.mapcomplete.org/passthrough.html?oauth_token="+token
|
window.location.href = "https://app.mapcomplete.org/passthrough.html?oauth_token="+token
|
||||||
})
|
})
|
||||||
|
|
|
@ -49,6 +49,7 @@ cp -r dist/assets/templates dist-full/assets
|
||||||
cp -r dist/assets/themes dist-full/assets
|
cp -r dist/assets/themes dist-full/assets
|
||||||
|
|
||||||
# mkdir dist-full/assets/generated
|
# mkdir dist-full/assets/generated
|
||||||
|
nvm use
|
||||||
|
|
||||||
# assets/icon-only.png will be used as the app icon
|
# assets/icon-only.png will be used as the app icon
|
||||||
# See https://capacitorjs.com/docs/guides/splash-screens-and-icons
|
# See https://capacitorjs.com/docs/guides/splash-screens-and-icons
|
||||||
|
@ -56,4 +57,4 @@ npx capacitor-assets generate
|
||||||
|
|
||||||
npx cap sync
|
npx cap sync
|
||||||
|
|
||||||
echo "All done! Don't forget to click 'gradly sync files' in Android Studio"
|
echo "All done! Don't forget to click 'gradle sync files' in Android Studio"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Constants from "../../Models/Constants"
|
||||||
import { Changes } from "./Changes"
|
import { Changes } from "./Changes"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
|
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
|
||||||
|
import { AndroidPolyfill } from "../Web/AndroidPolyfill"
|
||||||
|
|
||||||
export interface ChangesetTag {
|
export interface ChangesetTag {
|
||||||
key: string
|
key: string
|
||||||
|
@ -52,7 +53,7 @@ export class ChangesetHandler {
|
||||||
| { addAlias: (id0: string, id1: string) => void }
|
| { addAlias: (id0: string, id1: string) => void }
|
||||||
| undefined,
|
| undefined,
|
||||||
changes: Changes,
|
changes: Changes,
|
||||||
reportError: (e: string | Error, extramessage: string) => void
|
reportError: (e: string | Error, extramessage: string) => void,
|
||||||
) {
|
) {
|
||||||
this.osmConnection = osmConnection
|
this.osmConnection = osmConnection
|
||||||
this._reportError = reportError
|
this._reportError = reportError
|
||||||
|
@ -113,7 +114,7 @@ export class ChangesetHandler {
|
||||||
private async UploadWithNew(
|
private async UploadWithNew(
|
||||||
generateChangeXML: (csid: number, remappings: Map<string, string>) => string,
|
generateChangeXML: (csid: number, remappings: Map<string, string>) => string,
|
||||||
openChangeset: UIEventSource<number>,
|
openChangeset: UIEventSource<number>,
|
||||||
extraMetaTags: ChangesetTag[]
|
extraMetaTags: ChangesetTag[],
|
||||||
) {
|
) {
|
||||||
const csId = await this.OpenChangeset(extraMetaTags)
|
const csId = await this.OpenChangeset(extraMetaTags)
|
||||||
openChangeset.setData(csId)
|
openChangeset.setData(csId)
|
||||||
|
@ -121,7 +122,7 @@ export class ChangesetHandler {
|
||||||
console.log(
|
console.log(
|
||||||
"Opened a new changeset (openChangeset.data is undefined):",
|
"Opened a new changeset (openChangeset.data is undefined):",
|
||||||
changeset,
|
changeset,
|
||||||
extraMetaTags
|
extraMetaTags,
|
||||||
)
|
)
|
||||||
const changes = await this.UploadChange(csId, changeset)
|
const changes = await this.UploadChange(csId, changeset)
|
||||||
const hasSpecialMotivationChanges = ChangesetHandler.rewriteMetaTags(extraMetaTags, changes)
|
const hasSpecialMotivationChanges = ChangesetHandler.rewriteMetaTags(extraMetaTags, changes)
|
||||||
|
@ -144,7 +145,7 @@ export class ChangesetHandler {
|
||||||
public async UploadChangeset(
|
public async UploadChangeset(
|
||||||
generateChangeXML: (csid: number, remappings: Map<string, string>) => string,
|
generateChangeXML: (csid: number, remappings: Map<string, string>) => string,
|
||||||
extraMetaTags: ChangesetTag[],
|
extraMetaTags: ChangesetTag[],
|
||||||
openChangeset: UIEventSource<number>
|
openChangeset: UIEventSource<number>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (
|
if (
|
||||||
!extraMetaTags.some((tag) => tag.key === "comment") ||
|
!extraMetaTags.some((tag) => tag.key === "comment") ||
|
||||||
|
@ -179,13 +180,13 @@ export class ChangesetHandler {
|
||||||
try {
|
try {
|
||||||
const rewritings = await this.UploadChange(
|
const rewritings = await this.UploadChange(
|
||||||
csId,
|
csId,
|
||||||
generateChangeXML(csId, this._remappings)
|
generateChangeXML(csId, this._remappings),
|
||||||
)
|
)
|
||||||
|
|
||||||
const rewrittenTags = this.RewriteTagsOf(
|
const rewrittenTags = this.RewriteTagsOf(
|
||||||
extraMetaTags,
|
extraMetaTags,
|
||||||
rewritings,
|
rewritings,
|
||||||
oldChangesetMeta
|
oldChangesetMeta,
|
||||||
)
|
)
|
||||||
await this.UpdateTags(csId, rewrittenTags)
|
await this.UpdateTags(csId, rewrittenTags)
|
||||||
return // We are done!
|
return // We are done!
|
||||||
|
@ -196,7 +197,7 @@ export class ChangesetHandler {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
e,
|
e,
|
||||||
"While getting metadata from a changeset " + openChangeset.data
|
"While getting metadata from a changeset " + openChangeset.data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,7 +225,7 @@ export class ChangesetHandler {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Could not open/upload changeset due to ",
|
"Could not open/upload changeset due to ",
|
||||||
e,
|
e,
|
||||||
"trying again with a another fresh changeset "
|
"trying again with a another fresh changeset ",
|
||||||
)
|
)
|
||||||
openChangeset.setData(undefined)
|
openChangeset.setData(undefined)
|
||||||
|
|
||||||
|
@ -250,7 +251,7 @@ export class ChangesetHandler {
|
||||||
uid: number // User ID
|
uid: number // User ID
|
||||||
changes_count: number
|
changes_count: number
|
||||||
tags: any
|
tags: any
|
||||||
}
|
},
|
||||||
): ChangesetTag[] {
|
): ChangesetTag[] {
|
||||||
// Note: extraMetaTags is where all the tags are collected into
|
// Note: extraMetaTags is where all the tags are collected into
|
||||||
|
|
||||||
|
@ -387,7 +388,7 @@ export class ChangesetHandler {
|
||||||
tag.key !== undefined &&
|
tag.key !== undefined &&
|
||||||
tag.value !== undefined &&
|
tag.value !== undefined &&
|
||||||
tag.key !== "" &&
|
tag.key !== "" &&
|
||||||
tag.value !== ""
|
tag.value !== "",
|
||||||
)
|
)
|
||||||
const metadata = tags.map((kv) => `<tag k="${kv.key}" v="${escapeHtml(kv.value)}"/>`)
|
const metadata = tags.map((kv) => `<tag k="${kv.key}" v="${escapeHtml(kv.value)}"/>`)
|
||||||
const content = [`<osm><changeset>`, metadata, `</changeset></osm>`].join("")
|
const content = [`<osm><changeset>`, metadata, `</changeset></osm>`].join("")
|
||||||
|
@ -398,10 +399,16 @@ export class ChangesetHandler {
|
||||||
const usedGps = this.changes.state["currentUserLocation"]?.features?.data?.length > 0
|
const usedGps = this.changes.state["currentUserLocation"]?.features?.data?.length > 0
|
||||||
const hasMorePrivacy = !!this.changes.state?.featureSwitches?.featureSwitchMorePrivacy?.data
|
const hasMorePrivacy = !!this.changes.state?.featureSwitches?.featureSwitchMorePrivacy?.data
|
||||||
const setSourceAsSurvey = !hasMorePrivacy && usedGps
|
const setSourceAsSurvey = !hasMorePrivacy && usedGps
|
||||||
|
let shell = ""
|
||||||
|
let host = `${window.location.origin}${window.location.pathname}`
|
||||||
|
if (AndroidPolyfill.inAndroid.data) {
|
||||||
|
shell = " (Android)"
|
||||||
|
host = "https://mapcomplete.org/" + window.location.pathname
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
["created_by", `MapComplete ${Constants.vNumber}`],
|
["created_by", `MapComplete ${Constants.vNumber}${shell}`],
|
||||||
["locale", Locale.language.data],
|
["locale", Locale.language.data],
|
||||||
["host", `${window.location.origin}${window.location.pathname}`], // Note: deferred changes might give a different hostpath then the theme with which the changes were made
|
["host", host], // Note: deferred changes might give a different hostpath then the theme with which the changes were made
|
||||||
["source", setSourceAsSurvey ? "survey" : undefined],
|
["source", setSourceAsSurvey ? "survey" : undefined],
|
||||||
["imagery", this.changes.state["backgroundLayer"]?.data?.id],
|
["imagery", this.changes.state["backgroundLayer"]?.data?.id],
|
||||||
].map(([key, value]) => ({
|
].map(([key, value]) => ({
|
||||||
|
@ -427,7 +434,7 @@ export class ChangesetHandler {
|
||||||
const csId = await this.osmConnection.put(
|
const csId = await this.osmConnection.put(
|
||||||
"changeset/create",
|
"changeset/create",
|
||||||
[`<osm><changeset>`, metadata, `</changeset></osm>`].join(""),
|
[`<osm><changeset>`, metadata, `</changeset></osm>`].join(""),
|
||||||
{ "Content-Type": "text/xml" }
|
{ "Content-Type": "text/xml" },
|
||||||
)
|
)
|
||||||
return Number(csId)
|
return Number(csId)
|
||||||
}
|
}
|
||||||
|
@ -437,12 +444,12 @@ export class ChangesetHandler {
|
||||||
*/
|
*/
|
||||||
private async UploadChange(
|
private async UploadChange(
|
||||||
changesetId: number,
|
changesetId: number,
|
||||||
changesetXML: string
|
changesetXML: string,
|
||||||
): Promise<Map<string, string>> {
|
): Promise<Map<string, string>> {
|
||||||
const response = await this.osmConnection.post<XMLDocument>(
|
const response = await this.osmConnection.post<XMLDocument>(
|
||||||
"changeset/" + changesetId + "/upload",
|
"changeset/" + changesetId + "/upload",
|
||||||
changesetXML,
|
changesetXML,
|
||||||
{ "Content-Type": "text/xml" }
|
{ "Content-Type": "text/xml" },
|
||||||
)
|
)
|
||||||
const changes = this.parseUploadChangesetResponse(response)
|
const changes = this.parseUploadChangesetResponse(response)
|
||||||
console.log("Uploaded changeset ", changesetId)
|
console.log("Uploaded changeset ", changesetId)
|
||||||
|
|
|
@ -5,41 +5,70 @@ 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"
|
|
||||||
import { Feature, Point } from "geojson"
|
import { Feature, Point } from "geojson"
|
||||||
|
import { AndroidPolyfill } from "../Web/AndroidPolyfill"
|
||||||
|
import { QueryParameters } from "../Web/QueryParameters"
|
||||||
|
|
||||||
interface OsmUserInfo {
|
interface OsmUserInfo {
|
||||||
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 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class UserDetails {
|
"id": number,
|
||||||
public loggedIn = false
|
"display_name": string,
|
||||||
public name = "Not logged in"
|
"account_created": string,
|
||||||
public uid: number
|
"description": string,
|
||||||
public csCount = 0
|
"contributor_terms": {
|
||||||
public img?: string
|
"agreed": boolean,
|
||||||
public unreadMessages = 0
|
"pd": boolean
|
||||||
public totalMessages: number = 0
|
},
|
||||||
public home: { lon: number; lat: number }
|
"img": {
|
||||||
public backend: string
|
"href": string,
|
||||||
public account_created: string
|
},
|
||||||
public tracesCount: number = 0
|
"roles": string[],
|
||||||
public description: string
|
"changesets": {
|
||||||
public languages: string[]
|
"count": number
|
||||||
|
},
|
||||||
constructor(backend: string) {
|
"traces": {
|
||||||
this.backend = backend
|
"count": number
|
||||||
|
},
|
||||||
|
"blocks": {
|
||||||
|
"received": {
|
||||||
|
"count": number,
|
||||||
|
"active": number
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"lat": number,
|
||||||
|
"lon": number,
|
||||||
|
"zoom": number
|
||||||
|
},
|
||||||
|
"languages": string[],
|
||||||
|
"messages": {
|
||||||
|
"received": {
|
||||||
|
"count": number,
|
||||||
|
"unread": number
|
||||||
|
},
|
||||||
|
"sent": {
|
||||||
|
"count": number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default interface UserDetails {
|
||||||
|
loggedIn: boolean
|
||||||
|
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[]
|
||||||
|
|
||||||
|
}
|
||||||
export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable"
|
export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable"
|
||||||
|
|
||||||
interface CapabilityResult {
|
interface CapabilityResult {
|
||||||
|
@ -77,7 +106,10 @@ interface CapabilityResult {
|
||||||
|
|
||||||
export class OsmConnection {
|
export class OsmConnection {
|
||||||
public auth: osmAuth
|
public auth: osmAuth
|
||||||
public userDetails: UIEventSource<UserDetails>
|
/**
|
||||||
|
* Details of the currently logged-in user; undefined if not logged in
|
||||||
|
*/
|
||||||
|
public userDetails: UIEventSource<UserDetails | undefined>
|
||||||
public isLoggedIn: Store<boolean>
|
public isLoggedIn: Store<boolean>
|
||||||
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
|
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
|
||||||
"unknown",
|
"unknown",
|
||||||
|
@ -93,7 +125,6 @@ export class OsmConnection {
|
||||||
public readonly _oauth_config: AuthConfig
|
public readonly _oauth_config: AuthConfig
|
||||||
private readonly _dryRun: Store<boolean>
|
private readonly _dryRun: Store<boolean>
|
||||||
private readonly fakeUser: boolean
|
private readonly fakeUser: boolean
|
||||||
private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []
|
|
||||||
private readonly _iframeMode: boolean
|
private readonly _iframeMode: boolean
|
||||||
private readonly _singlePage: boolean
|
private readonly _singlePage: boolean
|
||||||
private isChecking = false
|
private isChecking = false
|
||||||
|
@ -129,10 +160,7 @@ export class OsmConnection {
|
||||||
this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET
|
this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userDetails = new UIEventSource<UserDetails>(
|
this.userDetails = new UIEventSource<UserDetails>(undefined, "userDetails")
|
||||||
new UserDetails(this._oauth_config.url),
|
|
||||||
"userDetails",
|
|
||||||
)
|
|
||||||
if (options.fakeUser) {
|
if (options.fakeUser) {
|
||||||
const ud = this.userDetails.data
|
const ud = this.userDetails.data
|
||||||
ud.csCount = 5678
|
ud.csCount = 5678
|
||||||
|
@ -146,25 +174,21 @@ export class OsmConnection {
|
||||||
"The 'fake-user' is a URL-parameter which allows to test features without needing an OSM account or even internet connection."
|
"The 'fake-user' is a URL-parameter which allows to test features without needing an OSM account or even internet connection."
|
||||||
this.loadingStatus.setData("logged-in")
|
this.loadingStatus.setData("logged-in")
|
||||||
}
|
}
|
||||||
this.UpdateCapabilities()
|
this.updateCapabilities()
|
||||||
|
|
||||||
this.isLoggedIn = this.userDetails.map(
|
this.isLoggedIn = this.userDetails.map(
|
||||||
(user) =>
|
(user) =>
|
||||||
user.loggedIn &&
|
!!user &&
|
||||||
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
|
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
|
||||||
[this.apiIsOnline],
|
[this.apiIsOnline],
|
||||||
)
|
)
|
||||||
this.isLoggedIn.addCallback((isLoggedIn) => {
|
|
||||||
if (this.userDetails.data.loggedIn == false && isLoggedIn == true) {
|
|
||||||
// We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do
|
|
||||||
// This means someone attempted to toggle this; so we attempt to login!
|
|
||||||
this.AttemptLogin()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false)
|
this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false)
|
||||||
|
|
||||||
this.updateAuthObject()
|
this.createAuthObject()
|
||||||
|
AndroidPolyfill.inAndroid.addCallback(() => {
|
||||||
|
this.createAuthObject()
|
||||||
|
})
|
||||||
if (!this.fakeUser) {
|
if (!this.fakeUser) {
|
||||||
this.CheckForMessagesContinuously()
|
this.CheckForMessagesContinuously()
|
||||||
}
|
}
|
||||||
|
@ -209,9 +233,6 @@ export class OsmConnection {
|
||||||
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
|
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
public OnLoggedIn(action: (userDetails: UserDetails) => void) {
|
|
||||||
this._onLoggedIn.push(action)
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogOut() {
|
public LogOut() {
|
||||||
this.auth.logout()
|
this.auth.logout()
|
||||||
|
@ -233,7 +254,7 @@ export class OsmConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AttemptLogin() {
|
public AttemptLogin() {
|
||||||
this.UpdateCapabilities()
|
this.updateCapabilities()
|
||||||
if (this.loadingStatus.data !== "logged-in") {
|
if (this.loadingStatus.data !== "logged-in") {
|
||||||
// Stay 'logged-in' if we are already logged in; this simply means we are checking for messages
|
// Stay 'logged-in' if we are already logged in; this simply means we are checking for messages
|
||||||
this.loadingStatus.setData("loading")
|
this.loadingStatus.setData("loading")
|
||||||
|
@ -245,18 +266,51 @@ export class OsmConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Trying to log in...")
|
console.log("Trying to log in...")
|
||||||
this.updateAuthObject()
|
|
||||||
|
|
||||||
LocalStorageSource.get("location_before_login").setData(
|
LocalStorageSource.get("location_before_login").setData(
|
||||||
Utils.runningFromConsole ? undefined : window.location.href,
|
Utils.runningFromConsole ? undefined : window.location.href,
|
||||||
)
|
)
|
||||||
this.auth.xhr(
|
|
||||||
{
|
this.auth.authenticate((err, result) => {
|
||||||
method: "GET",
|
if (!err) {
|
||||||
path: "/api/0.6/user/details",
|
this.loadUserInfo()
|
||||||
},
|
}
|
||||||
(err, details: XMLDocument) => {
|
})
|
||||||
if (err != null) {
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadUserInfo() {
|
||||||
|
try {
|
||||||
|
const result = await this.interact("user/details.json")
|
||||||
|
if (result === null) {
|
||||||
|
this.loadingStatus.setData("error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const data = <{
|
||||||
|
"version": "0.6",
|
||||||
|
"license": "http://opendatacommons.org/licenses/odbl/1-0/",
|
||||||
|
"user": OsmUserInfo
|
||||||
|
}>JSON.parse(result)
|
||||||
|
const user = data.user
|
||||||
|
const userdetails: UserDetails = {
|
||||||
|
uid: user.id,
|
||||||
|
name: user.display_name,
|
||||||
|
csCount: user.changesets.count,
|
||||||
|
description: user.description,
|
||||||
|
loggedIn: true,
|
||||||
|
backend: this.Backend(),
|
||||||
|
home: user.home,
|
||||||
|
languages: user.languages,
|
||||||
|
totalMessages: user.messages.received?.count ?? 0,
|
||||||
|
img: user.img?.href,
|
||||||
|
account_created: user.account_created,
|
||||||
|
tracesCount: user.traces.count,
|
||||||
|
unreadMessages: user.messages.received?.unread ?? 0,
|
||||||
|
}
|
||||||
|
console.log("Login completed, userinfo is ", userdetails)
|
||||||
|
this.userDetails.set(userdetails)
|
||||||
|
this.loadingStatus.setData("logged-in")
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
console.log("Could not login due to:", err)
|
console.log("Could not login due to:", err)
|
||||||
this.loadingStatus.setData("error")
|
this.loadingStatus.setData("error")
|
||||||
if (err.status == 401) {
|
if (err.status == 401) {
|
||||||
|
@ -268,64 +322,7 @@ export class OsmConnection {
|
||||||
console.log("Other error. Status:", err.status)
|
console.log("Other error. Status:", err.status)
|
||||||
this.apiIsOnline.setData("unreachable")
|
this.apiIsOnline.setData("unreachable")
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details == null) {
|
|
||||||
this.loadingStatus.setData("error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// details is an XML DOM of user details
|
|
||||||
const userInfo = details.getElementsByTagName("user")[0]
|
|
||||||
|
|
||||||
const data = this.userDetails.data
|
|
||||||
data.loggedIn = true
|
|
||||||
console.log("Login completed, userinfo is ", userInfo)
|
|
||||||
data.name = userInfo.getAttribute("display_name")
|
|
||||||
data.account_created = userInfo.getAttribute("account_created")
|
|
||||||
data.uid = Number(userInfo.getAttribute("id"))
|
|
||||||
data.languages = Array.from(
|
|
||||||
userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang"),
|
|
||||||
).map((l) => l.textContent)
|
|
||||||
data.csCount = Number.parseInt(
|
|
||||||
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0",
|
|
||||||
)
|
|
||||||
data.tracesCount = Number.parseInt(
|
|
||||||
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0",
|
|
||||||
)
|
|
||||||
|
|
||||||
data.img = undefined
|
|
||||||
const imgEl = userInfo.getElementsByTagName("img")
|
|
||||||
if (imgEl !== undefined && imgEl[0] !== undefined) {
|
|
||||||
data.img = imgEl[0].getAttribute("href")
|
|
||||||
}
|
|
||||||
|
|
||||||
const description = userInfo.getElementsByTagName("description")
|
|
||||||
if (description !== undefined && description[0] !== undefined) {
|
|
||||||
data.description = description[0]?.innerHTML
|
|
||||||
}
|
|
||||||
const homeEl = userInfo.getElementsByTagName("home")
|
|
||||||
if (homeEl !== undefined && homeEl[0] !== undefined) {
|
|
||||||
const lat = parseFloat(homeEl[0].getAttribute("lat"))
|
|
||||||
const lon = parseFloat(homeEl[0].getAttribute("lon"))
|
|
||||||
data.home = { lat: lat, lon: lon }
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadingStatus.setData("logged-in")
|
|
||||||
const messages = userInfo
|
|
||||||
.getElementsByTagName("messages")[0]
|
|
||||||
.getElementsByTagName("received")[0]
|
|
||||||
data.unreadMessages = parseInt(messages.getAttribute("unread"))
|
|
||||||
data.totalMessages = parseInt(messages.getAttribute("count"))
|
|
||||||
|
|
||||||
this.userDetails.ping()
|
|
||||||
for (const action of this._onLoggedIn) {
|
|
||||||
action(this.userDetails.data)
|
|
||||||
}
|
|
||||||
this._onLoggedIn = []
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -339,7 +336,7 @@ export class OsmConnection {
|
||||||
*/
|
*/
|
||||||
public async interact(
|
public async interact(
|
||||||
path: string,
|
path: string,
|
||||||
method: "GET" | "POST" | "PUT" | "DELETE",
|
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
|
||||||
header?: Record<string, string>,
|
header?: Record<string, string>,
|
||||||
content?: string,
|
content?: string,
|
||||||
allowAnonymous: boolean = false,
|
allowAnonymous: boolean = false,
|
||||||
|
@ -359,6 +356,11 @@ export class OsmConnection {
|
||||||
throw "Could not interact with OSM:" + possibleResult["error"]
|
throw "Could not interact with OSM:" + possibleResult["error"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.auth.authenticated()) {
|
||||||
|
console.trace("Not authenticated")
|
||||||
|
await Utils.waitFor(10000)
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((ok, error) => {
|
return new Promise((ok, error) => {
|
||||||
connection.xhr(
|
connection.xhr(
|
||||||
{
|
{
|
||||||
|
@ -504,7 +506,7 @@ export class OsmConnection {
|
||||||
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
|
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
|
||||||
"\"\r\nContent-Type: application/gpx+xml",
|
"\"\r\nContent-Type: application/gpx+xml",
|
||||||
}
|
}
|
||||||
user
|
|
||||||
const boundary = "987654"
|
const boundary = "987654"
|
||||||
|
|
||||||
let body = ""
|
let body = ""
|
||||||
|
@ -563,19 +565,29 @@ export class OsmConnection {
|
||||||
this.auth.authenticate(() => {
|
this.auth.authenticate(() => {
|
||||||
// Fully authed at this point
|
// Fully authed at this point
|
||||||
console.log("Authentication successful!")
|
console.log("Authentication successful!")
|
||||||
const oauth_token = window.localStorage.getItem(this._oauth_config.url + "oauth2_access_token")
|
const oauth_token = QueryParameters.GetQueryParameter("oauth_token", undefined).data ?? window.localStorage.getItem(this._oauth_config.url + "oauth2_access_token")
|
||||||
const previousLocation = LocalStorageSource.get("location_before_login")
|
const previousLocation = LocalStorageSource.get("location_before_login")
|
||||||
callback(previousLocation.data, oauth_token)
|
callback(previousLocation.data, oauth_token)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateAuthObject() {
|
private async loginAndroidPolyfill() {
|
||||||
|
const token = await AndroidPolyfill.requestLoginCodes()
|
||||||
|
console.log("Got login token!", token)
|
||||||
|
localStorage.setItem("https://www.openstreetmap.orgoauth2_access_token", token)
|
||||||
|
if (this.auth.authenticated()) {
|
||||||
|
console.log("Logged in!")
|
||||||
|
}
|
||||||
|
await this.loadUserInfo()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private createAuthObject() {
|
||||||
let redirect_uri = Utils.runningFromConsole
|
let redirect_uri = Utils.runningFromConsole
|
||||||
? "https://mapcomplete.org/land.html"
|
? "https://mapcomplete.org/land.html"
|
||||||
: window.location.protocol + "//" + window.location.host + "/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"
|
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,
|
||||||
|
@ -586,9 +598,12 @@ export class OsmConnection {
|
||||||
* 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 && !AndroidPolyfill.inAndroid.data,
|
singlepage: !this._iframeMode && !AndroidPolyfill.inAndroid.data,
|
||||||
auto: true,
|
auto: false,
|
||||||
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
|
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
|
||||||
})
|
})
|
||||||
|
if (AndroidPolyfill.inAndroid.data) {
|
||||||
|
this.loginAndroidPolyfill() // NO AWAIT!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CheckForMessagesContinuously() {
|
private CheckForMessagesContinuously() {
|
||||||
|
@ -611,9 +626,10 @@ export class OsmConnection {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Stores.Chronic(60 * 5 * 1000).addCallback(() => {
|
Stores.Chronic(60 * 5 * 1000).addCallback(() => {
|
||||||
|
// Check for new messages every 5 minutes
|
||||||
if (this.isLoggedIn.data) {
|
if (this.isLoggedIn.data) {
|
||||||
try {
|
try {
|
||||||
this.AttemptLogin()
|
this.loadUserInfo()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Could not login due to", e)
|
console.log("Could not login due to", e)
|
||||||
}
|
}
|
||||||
|
@ -621,11 +637,11 @@ export class OsmConnection {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private UpdateCapabilities(): void {
|
private updateCapabilities(): void {
|
||||||
if (this.fakeUser) {
|
if (this.fakeUser) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.FetchCapabilities().then(({ api, gpx }) => {
|
this.fetchCapabilities().then(({ api, gpx }) => {
|
||||||
this.apiIsOnline.setData(api)
|
this.apiIsOnline.setData(api)
|
||||||
this.gpxServiceIsOnline.setData(gpx)
|
this.gpxServiceIsOnline.setData(gpx)
|
||||||
})
|
})
|
||||||
|
@ -646,7 +662,8 @@ export class OsmConnection {
|
||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
private async FetchCapabilities(): Promise<{
|
/**Does not use the OSM-auth object*/
|
||||||
|
private async fetchCapabilities(): Promise<{
|
||||||
api: OsmServiceState
|
api: OsmServiceState
|
||||||
gpx: OsmServiceState
|
gpx: OsmServiceState
|
||||||
database: OsmServiceState
|
database: OsmServiceState
|
||||||
|
@ -656,7 +673,7 @@ export class OsmConnection {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await Utils.downloadJson<CapabilityResult>(
|
const result = await Utils.downloadJson<CapabilityResult>(
|
||||||
this.Backend() + "/api/0.6/capabilities.json"
|
this.Backend() + "/api/0.6/capabilities.json",
|
||||||
)
|
)
|
||||||
if (result?.api?.status === undefined) {
|
if (result?.api?.status === undefined) {
|
||||||
console.log("Something went wrong:", result)
|
console.log("Something went wrong:", result)
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class OsmPreferences {
|
||||||
this.auth = auth
|
this.auth = auth
|
||||||
this._fakeUser = fakeUser
|
this._fakeUser = fakeUser
|
||||||
this.osmConnection = osmConnection
|
this.osmConnection = osmConnection
|
||||||
osmConnection.OnLoggedIn(() => {
|
osmConnection.userDetails.addCallbackAndRunD(() => {
|
||||||
this.loadBulkPreferences()
|
this.loadBulkPreferences()
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import themeOverview from "../../assets/generated/theme_overview.json"
|
||||||
import LayerSearch from "./LayerSearch"
|
import LayerSearch from "./LayerSearch"
|
||||||
import SearchUtils from "./SearchUtils"
|
import SearchUtils from "./SearchUtils"
|
||||||
import { OsmConnection } from "../Osm/OsmConnection"
|
import { OsmConnection } from "../Osm/OsmConnection"
|
||||||
|
import { AndroidPolyfill } from "../Web/AndroidPolyfill"
|
||||||
|
|
||||||
type ThemeSearchScore = {
|
type ThemeSearchScore = {
|
||||||
theme: MinimalThemeInformation
|
theme: MinimalThemeInformation
|
||||||
|
@ -82,7 +83,7 @@ export default class ThemeSearch {
|
||||||
}
|
}
|
||||||
|
|
||||||
let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?`
|
let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?`
|
||||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
if ((location.hostname === "localhost" && !AndroidPolyfill.inAndroid.data) || location.hostname === "127.0.0.1") {
|
||||||
linkPrefix = `${path}/theme.html?layout=${layout.id}&`
|
linkPrefix = `${path}/theme.html?layout=${layout.id}&`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,9 +182,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
|
||||||
|
|
||||||
let testingDefaultValue = false
|
let testingDefaultValue = false
|
||||||
if (
|
if (
|
||||||
!Utils.runningFromConsole &&
|
!Constants.osmAuthConfig.url.startsWith("https://master.apis.dev.openstreetmap.org") && (location.hostname === "127.0.0.1") && !Utils.runningFromConsole
|
||||||
(location.hostname === "localhost" || location.hostname === "127.0.0.1") &&
|
|
||||||
!Constants.osmAuthConfig.url.startsWith("https://master.apis.dev.openstreetmap.org")
|
|
||||||
) {
|
) {
|
||||||
testingDefaultValue = true
|
testingDefaultValue = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,13 +51,11 @@ export class AndroidPolyfill {
|
||||||
this.backfillGeolocation(this.databridgePlugin)
|
this.backfillGeolocation(this.databridgePlugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async requestLoginCodes(osmConnection: OsmConnection) {
|
public static async requestLoginCodes() {
|
||||||
const result = await DatabridgePluginSingleton.request<{oauth_token: string}>({ key: "request:login" })
|
const result = await DatabridgePluginSingleton.request<{oauth_token: string}>({ key: "request:login" })
|
||||||
const token: string = result.value.oauth_token
|
const token: string = result.value.oauth_token
|
||||||
console.log("AndroidPolyfill: received code and state; trying to pass them to the oauth lib",token)
|
console.log("AndroidPolyfill: received oauth_token; trying to pass them to the oauth lib",token)
|
||||||
const auth = osmConnection.auth.bootstrapToken(token, (err, result) => {
|
return token
|
||||||
console.log("AndroidPolyFill: bootstraptoken returned", JSON.stringify({err, result}))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
const tu = Translations.t.general
|
const tu = Translations.t.general
|
||||||
const tr = Translations.t.general.morescreen
|
const tr = Translations.t.general.morescreen
|
||||||
|
|
||||||
let userLanguages = osmConnection.userDetails.map((ud) => ud.languages)
|
let userLanguages = osmConnection.userDetails.mapD((ud) => ud.languages)
|
||||||
let search: UIEventSource<string | undefined> = new UIEventSource<string>("")
|
let search: UIEventSource<string | undefined> = new UIEventSource<string>("")
|
||||||
let searchStable = search.stabilized(100)
|
let searchStable = search.stabilized(100)
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
hiddenThemes.filter(
|
hiddenThemes.filter(
|
||||||
(theme) =>
|
(theme) =>
|
||||||
knownIds.indexOf(theme.id) >= 0 ||
|
knownIds.indexOf(theme.id) >= 0 ||
|
||||||
state.osmConnection.userDetails.data.name === "Pieter Vander Vennet"
|
state.osmConnection.userDetails.data?.name === "Pieter Vander Vennet"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
josmState.stabilized(15000).addCallbackD(() => josmState.setData(undefined))
|
josmState.stabilized(15000).addCallbackD(() => josmState.setData(undefined))
|
||||||
|
|
||||||
const showButton = state.osmConnection.userDetails.map(
|
const showButton = state.osmConnection.userDetails.map(
|
||||||
(ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible
|
(ud) => ud?.csCount >= Constants.userJourney.historyLinkVisible
|
||||||
)
|
)
|
||||||
|
|
||||||
function openJosm() {
|
function openJosm() {
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
const imageInfo = await panoramax.imageInfo(image.id)
|
const imageInfo = await panoramax.imageInfo(image.id)
|
||||||
let reporter_email: string = undefined
|
let reporter_email: string = undefined
|
||||||
const userdetails = state.userRelatedState.osmConnection.userDetails
|
const userdetails = state.userRelatedState.osmConnection.userDetails
|
||||||
if (userdetails.data.loggedIn) {
|
if (!userdetails.data) {
|
||||||
reporter_email = userdetails.data.name + "@openstreetmap.org"
|
reporter_email = userdetails.data.name + "@openstreetmap.org"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ export class DeleteFlowState {
|
||||||
if (ud === undefined) {
|
if (ud === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
if (!ud.loggedIn) {
|
if (!this._osmConnection.isLoggedIn.data) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
$: tagsExplanation = tags?.asHumanString(true, false, currentProperties)
|
$: tagsExplanation = tags?.asHumanString(true, false, currentProperties)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !userDetails || $userDetails.loggedIn}
|
{#if !userDetails}
|
||||||
<div class="break-words" style="word-break: break-word">
|
<div class="break-words" style="word-break: break-word">
|
||||||
{#if tags === undefined}
|
{#if tags === undefined}
|
||||||
<slot name="no-tags"><Tr cls="subtle" t={Translations.t.general.noTagsSelected} /></slot>
|
<slot name="no-tags"><Tr cls="subtle" t={Translations.t.general.noTagsSelected} /></slot>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import SvelteUIElement from "./UI/Base/SvelteUIElement"
|
import SvelteUIElement from "./UI/Base/SvelteUIElement"
|
||||||
import Test from "./UI/Test.svelte"
|
import Test from "./UI/Test.svelte"
|
||||||
|
import { OsmConnection } from "./Logic/Osm/OsmConnection"
|
||||||
|
|
||||||
new Test({
|
new OsmConnection().interact("user/details.json").then(r => console.log(">>>", r))
|
||||||
target: document.getElementById("maindiv"),
|
|
||||||
})
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue