const {q, ElemJS, ejs} = require("./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 domain try { domain = await this.findHomeserver(homeserver.value) } catch(e) { return this.cancel(e.message) } // Request access token this.status("Logging in...") const root = await fetch(`${domain}/_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", domain) localStorage.setItem("access_token", root.access_token) location.assign("../") } async findHomeserver(address, maxDepth = 5) { //Protects from servers sending us on a redirect loop maxDepth-- if (maxDepth <= 0) throw new Error(`Failed to look up homeserver, maximum search depth reached`) //Normalise the address if (!address.match(/^https?:\/\//)) { console.warn(`${address} doesn't specify the protocol, assuming https`) address = "https://" + address } address = address.replace(/\/*$/, "") this.status(`Looking up homeserver... trying ${address}`) // Check if we found the actual matrix server try { const versionsReq = await fetch(`${address}/_matrix/client/versions`) if (versionsReq.ok) { const versions = await versionsReq.json() if (Array.isArray(versions.versions)) return address } } catch(e) {} // Find the next matrix server in the chain const root = await fetch(`${address}/.well-known/matrix/client`).then(res => res.json()).catch(e => { console.error(e) throw new Error(`Failed to look up server ${address}`) }) let nextAddress = root["m.homeserver"].base_url nextAddress = nextAddress.replace(/\/*$/, "") if (address === nextAddress) { throw new Error(`Failed to look up server ${address}, /.well-known/matrix/client found a redirect loop`); } return this.findHomeserver(nextAddress, maxDepth) } status(message) { feedback.setLoading(true) feedback.message(message) } cancel(message) { this.processing = false feedback.setLoading(false) feedback.message(message, true) } } const form = new Form()