import {q, ElemJS, ejs} from $to_relative "/js/basic.js" const password = q("#password") const homeserver = q("#homeserver") class Username extends ElemJS { constructor() { super(q("#username")) this.on("change", this.updateServer.bind(this)) } isValid() { return !!this.element.value.match(/^@?[a-z0-9._=\/-]+(?::[a-zA-Z0-9.:\[\]-]+)?$/) } getUsername() { return this.element.value.match(/^@?([a-z0-9._=\/-]+)/)[1] } getServer() { const server = this.element.value.match(/^@?[a-z0-9._=\?-]+:([a-zA-Z0-9.:\[\]-]+)$/) if (server && server[1]) return server[1] else return null } updateServer() { if (!this.isValid()) return if (this.getServer()) homeserver.value = this.getServer() } } const username = new Username() class Feedback extends ElemJS { constructor() { super(q("#feedback")) this.loading = false this.loadingIcon = ejs("span").class("loading-icon") this.messageSpan = ejs("span") this.child(this.messageSpan) } setLoading(state) { if (this.loading && !state) { this.loadingIcon.remove() } else if (!this.loading && state) { this.childAt(0, this.loadingIcon) } this.loading = state } message(content, isError) { this.removeClass("form-feedback") this.removeClass("form-error") if (content) this.class("form-feedback") if(isError) this.class("form-error") this.messageSpan.text(content) } } const feedback = new Feedback() class Form extends ElemJS { constructor() { super(q("#form")) this.processing = false this.on("submit", this.submit.bind(this)) } async submit() { if (this.processing) return this.processing = true if (!username.isValid()) return this.cancel("Username is not valid.") // Resolve homeserver address let currentAddress = homeserver.value let ok = false while (!ok) { if (!currentAddress.match(/^https?:\/\//)) { console.warn(`${currentAddress} doesn't specify the protocol, assuming https`) currentAddress = "https://" + currentAddress } currentAddress = currentAddress.replace(/\/*$/, "") this.status(`Looking up homeserver... trying ${currentAddress}`) try { // check if we found the actual matrix server try { const versions = await fetch(`${currentAddress}/_matrix/client/versions`).then(res => res.json()) if (Array.isArray(versions.versions)) { ok = true break } } catch (e) {} // find the next matrix server in the chain const root = await fetch(`${currentAddress}/.well-known/matrix/client`).then(res => res.json()) let nextAddress = root["m.homeserver"].base_url nextAddress = nextAddress.replace(/\/*$/, "") if (currentAddress === nextAddress) { ok = true } currentAddress = nextAddress } catch (e) { return this.cancel(`Failed to look up server ${currentAddress}`) } } // Request access token this.status("Logging in...") const root = await fetch(`${currentAddress}/_matrix/client/r0/login`, { method: "POST", body: JSON.stringify({ type: "m.login.password", user: username.getUsername(), password: password.value }) }).then(res => res.json()) if (!root.access_token) { if (root.error) { this.cancel(`Server said: ${root.error}`) } else { this.cancel("Login mysteriously failed.") console.error(root) } return } localStorage.setItem("mx_user_id", root.user_id) localStorage.setItem("domain", currentAddress) localStorage.setItem("access_token", root.access_token) location.assign("./") } status(message) { feedback.setLoading(true) feedback.message(message) } cancel(message) { this.processing = false feedback.setLoading(false) feedback.message(message, true) } } const form = new Form()