From 265d774b4f149f0fafe6f6e77b6e384967710c98 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 21 Oct 2020 19:33:36 +1300 Subject: [PATCH 1/2] Some login functionality --- spec.js | 5 ++ src/js/login.js | 114 ++++++++++++++++++++++++++++++++++++++++++++ src/login.pug | 13 ++--- src/sass/login.sass | 31 +++++++----- 4 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 src/js/login.js diff --git a/spec.js b/spec.js index 600af14..a488e5e 100644 --- a/spec.js +++ b/spec.js @@ -84,6 +84,11 @@ module.exports = [ source: "/js/functions.js", target: "/static/functions.js", }, + { + type: "js", + source: "/js/login.js", + target: "/static/login.js", + }, { type: "file", source: "/assets/fonts/whitney-500.woff", diff --git a/src/js/login.js b/src/js/login.js new file mode 100644 index 0000000..d3baf3a --- /dev/null +++ b/src/js/login.js @@ -0,0 +1,114 @@ +import {q, ElemJS} 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 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?:\/\//)) currentAddress = "https://" + currentAddress + currentAddress = currentAddress.replace(/\/*$/, "") + this.status(`Looking up homeserver... trying ${currentAddress}`) + try { + // check if we found the actual matrix server + const exists = await fetch(`${currentAddress}/_matrix/client/r0`).then(res => res.json()) + if (exists.errcode === "M_UNRECOGNIZED") { + ok = true + break + } + // 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) { + //TODO: display the message + } + + cancel(message) { + this.processing = false + //TODO: display the message + } +} + +const form = new Form() diff --git a/src/login.pug b/src/login.pug index 13acefd..4e86f06 100644 --- a/src/login.pug +++ b/src/login.pug @@ -2,19 +2,20 @@ doctype html html head meta(charset="utf-8") - link(rel="stylesheet" type="text/css" href=getStatic("/sass/login.sass")) title Carbon meta(name="viewport" content="width=device-width, initial-scale=1") + link(rel="stylesheet" type="text/css" href=getStatic("/sass/login.sass")) + script(type="module" src=getStatic("/js/login.js")) body main.main .center-login-container h1 Welcome to the Matrix! - form.login-form + form.login-form(method="post" onsubmit="return false")#form .data-input .form-input-container - label(for="login") Username - input(type="text" name="login" autocomplete="username" required)#login + label(for="username") Username + input(type="text" name="username" autocomplete="username" placeholder="@username:server.tld" pattern="^@?[a-z0-9._=/-]+(?::[a-zA-Z0-9.:\\[\\]-]+)?$" required)#username .form-input-container label(for="password") Password @@ -22,7 +23,7 @@ html .form-input-container label(for="homeserver") Homeserver - input(type="text" name="homeserver" value="matrix.org" required)#homeserver + input(type="text" name="homeserver" value="matrix.org" placeholder="matrix.org" required)#homeserver .form-input-container - input(type="submit" value="Login") + input(type="submit" value="Log in")#submit diff --git a/src/sass/login.sass b/src/sass/login.sass index 92f443f..77144bc 100644 --- a/src/sass/login.sass +++ b/src/sass/login.sass @@ -10,10 +10,10 @@ flex-flow: column justify-content: center align-items: center - width: min(100vw, 30rem) + width: min(100vw, 450px) padding: max(1rem,3vw) 2rem - height: 27rem - box-shadow: -2px 2px 10px c.$darkest + margin: 8px + box-shadow: 0px 2px 10px c.$darkest background-color: c.$darker border-radius: 5px @@ -34,23 +34,30 @@ flex-direction: column margin: 1em 0 -input +input, button + font-family: inherit + font-size: 17px + background-color: c.$mild + color: #eee width: 100% - height: 2.4rem border-radius: 5px box-sizing: border-box - transition: background-color 1s, border-color 1s - padding: 0px 1ch + transition: background-color 0.15s ease-out, border-color 0.15s ease-out + padding: 4px 9px border: 0px input[type="text"],input[type="password"] border: 3px solid transparent margin: 0.4em 0px - &:hover - border-color: c.$mild -input[type="submit"]:hover - background-color: c.$mild + &:hover, &:focus + border-color: c.$milder + +button, input[type="submit"] + padding: 7px + + &:hover + background-color: c.$milder label - font-size: 1.2em + font-size: 18px From 735ca360c8dc24dd14597f12db5e50c846a5afd8 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 21 Oct 2020 20:23:51 +1300 Subject: [PATCH 2/2] Login form feedback --- src/js/login.js | 50 ++++++++++++++++++++++++++++++++++++------- src/js/room-picker.js | 8 ++++--- src/login.pug | 3 +++ src/sass/login.sass | 18 ++++++++++++++++ 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/js/login.js b/src/js/login.js index d3baf3a..de32932 100644 --- a/src/js/login.js +++ b/src/js/login.js @@ -1,4 +1,4 @@ -import {q, ElemJS} from $to_relative "/js/basic.js" +import {q, ElemJS, ejs} from $to_relative "/js/basic.js" const password = q("#password") const homeserver = q("#homeserver") @@ -32,6 +32,36 @@ class Username extends ElemJS { 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) { + if (content) { + this.class("form-feedback") + } else { + this.removeClass("form-feedback") + } + this.messageSpan.text(content) + } +} + +const feedback = new Feedback() + class Form extends ElemJS { constructor() { super(q("#form")) @@ -55,11 +85,13 @@ class Form extends ElemJS { this.status(`Looking up homeserver... trying ${currentAddress}`) try { // check if we found the actual matrix server - const exists = await fetch(`${currentAddress}/_matrix/client/r0`).then(res => res.json()) - if (exists.errcode === "M_UNRECOGNIZED") { - ok = true - break - } + 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 @@ -102,12 +134,14 @@ class Form extends ElemJS { } status(message) { - //TODO: display the message + feedback.setLoading(true) + feedback.message(message) } cancel(message) { this.processing = false - //TODO: display the message + feedback.setLoading(false) + feedback.message(message) } } diff --git a/src/js/room-picker.js b/src/js/room-picker.js index 8e696de..9fbbece 100644 --- a/src/js/room-picker.js +++ b/src/js/room-picker.js @@ -108,10 +108,12 @@ class Room extends ElemJS { getIcon() { const avatar = this.data.state.events.find(e => e.type === "m.room.avatar") if (avatar) { - return resolveMxc(avatar.content.url || avatar.content.avatar_url, 32, "crop") - } else { - return null + const url = avatar.content.url || avatar.content.avatar_url + if (url) { + return resolveMxc(url, 32, "crop") + } } + return null } isDirect() { diff --git a/src/login.pug b/src/login.pug index 4e86f06..85d4485 100644 --- a/src/login.pug +++ b/src/login.pug @@ -25,5 +25,8 @@ html label(for="homeserver") Homeserver input(type="text" name="homeserver" value="matrix.org" placeholder="matrix.org" required)#homeserver + #feedback + .form-input-container input(type="submit" value="Log in")#submit + diff --git a/src/sass/login.sass b/src/sass/login.sass index 77144bc..e4820ee 100644 --- a/src/sass/login.sass +++ b/src/sass/login.sass @@ -34,6 +34,24 @@ flex-direction: column margin: 1em 0 +.form-feedback + width: 100% + margin: -0.5em 0 -0.8em + +@keyframes spin + 0% + transform: rotate(0deg) + 100% + transform: rotate(180deg) + +.loading-icon + display: inline-block + background-color: #ccc + width: 12px + height: 12px + margin-right: 6px + animation: spin 0.7s infinite + input, button font-family: inherit font-size: 17px