WIP: Some visual improvements for login #20
					 4 changed files with 98 additions and 21 deletions
				
			
		| 
						 | 
				
			
			@ -1,36 +1,67 @@
 | 
			
		|||
const {q, ElemJS, ejs} = require("./basic.js")
 | 
			
		||||
const {S_RE_DOMAIN, S_RE_IPV6, S_RE_IPV4} = require("./re.js")
 | 
			
		||||
 | 
			
		||||
const password = q("#password")
 | 
			
		||||
const homeserver = q("#homeserver")
 | 
			
		||||
 | 
			
		||||
// A regex matching a lossy MXID
 | 
			
		||||
// Groups:
 | 
			
		||||
//  1: username/localpart
 | 
			
		||||
//	MAYBE WITH
 | 
			
		||||
//  2: hostname/serverpart
 | 
			
		||||
//		3: domain
 | 
			
		||||
//		OR
 | 
			
		||||
//		4: IP address
 | 
			
		||||
// 			5: IPv4 address
 | 
			
		||||
//			OR
 | 
			
		||||
//			6: IPv6 address
 | 
			
		||||
//		MAYBE WITH
 | 
			
		||||
//		7: port number
 | 
			
		||||
const RE_LOSSY_MXID = new RegExp(`^@?([a-z0-9._=-]+?)(?::((?:(${S_RE_DOMAIN})|((${S_RE_IPV4})|(${S_RE_IPV6})))(?::(\\d+))?))?$`)
 | 
			
		||||
 | 
			
		||||
window.RE_LOSSY_MXID = RE_LOSSY_MXID
 | 
			
		||||
 | 
			
		||||
class Username extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
	constructor(homeserver) {
 | 
			
		||||
		super(q("#username"))
 | 
			
		||||
 | 
			
		||||
		this.homeserver = homeserver;
 | 
			
		||||
 | 
			
		||||
		this.on("change", this.updateServer.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isValid() {
 | 
			
		||||
		return !!this.element.value.match(/^@?[a-z0-9._=\/-]+(?::[a-zA-Z0-9.:\[\]-]+)?$/)
 | 
			
		||||
		return !!this.element.value.match(RE_LOSSY_MXID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getUsername() {
 | 
			
		||||
		return this.element.value.match(/^@?([a-z0-9._=\/-]+)/)[1]
 | 
			
		||||
		return this.element.value.match(RE_LOSSY_MXID)[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getServer() {
 | 
			
		||||
		const server = this.element.value.match(/^@?[a-z0-9._=\?-]+:([a-zA-Z0-9.:\[\]-]+)$/)
 | 
			
		||||
		if (server && server[1]) return server[1]
 | 
			
		||||
		const server = this.element.value.match(RE_LOSSY_MXID)
 | 
			
		||||
		if (server && server[2]) return server[2]
 | 
			
		||||
		else return null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	updateServer() {
 | 
			
		||||
		if (!this.isValid()) return
 | 
			
		||||
		if (this.getServer()) homeserver.value = this.getServer()
 | 
			
		||||
		if (this.getServer()) this.homeserver.suggest(this.getServer())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const username = new Username()
 | 
			
		||||
class Homeserver extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super(q("#homeserver"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	suggest(value) {
 | 
			
		||||
		this.element.placeholder = value
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getServer() {
 | 
			
		||||
		return this.element.value || this.element.placeholder;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Feedback extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
| 
						 | 
				
			
			@ -60,13 +91,14 @@ class Feedback extends ElemJS {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const feedback = new Feedback()
 | 
			
		||||
 | 
			
		||||
class Form extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
	constructor(username, feedback, homeserver) {
 | 
			
		||||
		super(q("#form"))
 | 
			
		||||
 | 
			
		||||
		this.processing = false
 | 
			
		||||
		this.username = username
 | 
			
		||||
		this.feedback = feedback
 | 
			
		||||
		this.homeserver = homeserver
 | 
			
		||||
 | 
			
		||||
		this.on("submit", this.submit.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -74,12 +106,12 @@ class Form extends ElemJS {
 | 
			
		|||
	async submit() {
 | 
			
		||||
		if (this.processing) return
 | 
			
		||||
		this.processing = true
 | 
			
		||||
		if (!username.isValid()) return this.cancel("Username is not valid.")
 | 
			
		||||
		if (!this.username.isValid()) return this.cancel("Username is not valid.")
 | 
			
		||||
 | 
			
		||||
		// Resolve homeserver address
 | 
			
		||||
		let domain
 | 
			
		||||
		try {
 | 
			
		||||
			domain = await this.findHomeserver(homeserver.value)
 | 
			
		||||
			domain = await this.findHomeserver(this.homeserver.getServer())
 | 
			
		||||
		} catch(e) {
 | 
			
		||||
			return this.cancel(e.message)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -90,7 +122,7 @@ class Form extends ElemJS {
 | 
			
		|||
			method: "POST",
 | 
			
		||||
			body: JSON.stringify({
 | 
			
		||||
				type: "m.login.password",
 | 
			
		||||
				user: username.getUsername(),
 | 
			
		||||
				user: this.username.getUsername(),
 | 
			
		||||
				password: password.value
 | 
			
		||||
			})
 | 
			
		||||
		}).then(res => res.json())
 | 
			
		||||
| 
						 | 
				
			
			@ -153,15 +185,21 @@ class Form extends ElemJS {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	status(message) {
 | 
			
		||||
		feedback.setLoading(true)
 | 
			
		||||
		feedback.message(message)
 | 
			
		||||
		this.feedback.setLoading(true)
 | 
			
		||||
		this.feedback.message(message)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cancel(message) {
 | 
			
		||||
		this.processing = false
 | 
			
		||||
		feedback.setLoading(false)
 | 
			
		||||
		feedback.message(message, true)
 | 
			
		||||
		this.feedback.setLoading(false)
 | 
			
		||||
		this.feedback.message(message, true)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const form = new Form()
 | 
			
		||||
const homeserver = new Homeserver()
 | 
			
		||||
 | 
			
		||||
const username = new Username(homeserver)
 | 
			
		||||
 | 
			
		||||
const feedback = new Feedback()
 | 
			
		||||
 | 
			
		||||
const form = new Form(username, feedback, homeserver)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								src/js/re.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/js/re.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
// A valid internet domain, according to https://stackoverflow.com/a/20046959 (cleaned)
 | 
			
		||||
const S_RE_DOMAIN = "(?:[a-zA-Z]|[a-zA-Z][a-zA-Z]|[a-zA-Z]\\d|\\d[a-zA-Z]|[a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9])\\.(?:[a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\\.[a-zA-Z]{2,3})"
 | 
			
		||||
 | 
			
		||||
// A valid ipv4 address, one that doesn't check for valid numbers (e.g. not 999) and one that does
 | 
			
		||||
// const S_RE_IPV4_NO_CHECK = "(?:(?:\\d{1,3}\\.){3}\\d{1,3})"
 | 
			
		||||
const S_RE_IPV4_HAS_CHECK = "(?:(?:25[0-5]|(?:2[0-4]|1{0,1}\\d){0,1}\\d)\\.){3}(?:25[0-5]|(?:2[0-4]|1{0,1}\\d){0,1}\\d)"
 | 
			
		||||
 | 
			
		||||
const S_RE_IPV6_SEG = "[a-fA-F\\d]{1,4}"
 | 
			
		||||
// Yes, this is an ipv6 address.
 | 
			
		||||
const S_RE_IPV6 = `
 | 
			
		||||
(?:
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){7}(?:${S_RE_IPV6_SEG}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){6}(?:${S_RE_IPV4_HAS_CHECK}|:${S_RE_IPV6_SEG}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){5}(?::${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,2}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){4}(?:(?::${S_RE_IPV6_SEG}){0,1}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,3}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){3}(?:(?::${S_RE_IPV6_SEG}){0,2}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,4}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){2}(?:(?::${S_RE_IPV6_SEG}){0,3}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,5}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){1}(?:(?::${S_RE_IPV6_SEG}){0,4}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,6}|:)|
 | 
			
		||||
(?::(?:(?::${S_RE_IPV6_SEG}){0,5}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,7}|:))
 | 
			
		||||
)(?:%[0-9a-zA-Z]{1,})?`
 | 
			
		||||
  .replace(/\s*\/\/.*$/gm, '')
 | 
			
		||||
  .replace(/\n/g, '')
 | 
			
		||||
  .trim();
 | 
			
		||||
 | 
			
		||||
const RE_DOMAIN_EXACT = new RegExp(`^${S_RE_DOMAIN}$`)
 | 
			
		||||
const RE_IPV4_EXACT = new RegExp(`^${S_RE_IPV4_HAS_CHECK}$`)
 | 
			
		||||
const RE_IPV6_EXACT = new RegExp(`^${S_RE_IPV6}$`)
 | 
			
		||||
const RE_IP_ADDR_EXACT = new RegExp(`^${S_RE_IPV6}|${S_RE_IPV4_HAS_CHECK}$`)
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  S_RE_DOMAIN,
 | 
			
		||||
  S_RE_IPV6,
 | 
			
		||||
  S_RE_IPV4: S_RE_IPV4_HAS_CHECK,
 | 
			
		||||
  RE_DOMAIN_EXACT,
 | 
			
		||||
  RE_IPV4_EXACT,
 | 
			
		||||
  RE_IPV6_EXACT,
 | 
			
		||||
  RE_IP_ADDR_EXACT
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ html
 | 
			
		|||
          .data-input
 | 
			
		||||
            .form-input-container
 | 
			
		||||
              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
 | 
			
		||||
              input(type="text" name="username" autocomplete="username" placeholder="@username:server.com" pattern="^@?[a-z0-9._=/-]+(?::[a-zA-Z0-9.:\\[\\]-]+)?$" required)#username
 | 
			
		||||
 | 
			
		||||
            .form-input-container
 | 
			
		||||
              label(for="password") Password
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +23,7 @@ html
 | 
			
		|||
 | 
			
		||||
            .form-input-container
 | 
			
		||||
              label(for="homeserver") Homeserver
 | 
			
		||||
              input(type="text" name="homeserver" value="matrix.org" placeholder="matrix.org" required)#homeserver
 | 
			
		||||
              input(type="text" name="homeserver" placeholder="matrix.org")#homeserver
 | 
			
		||||
 | 
			
		||||
          #feedback
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,6 +78,7 @@ button, input[type="submit"]
 | 
			
		|||
  padding: 7px
 | 
			
		||||
 | 
			
		||||
  &:hover
 | 
			
		||||
    cursor: pointer
 | 
			
		||||
    background-color: c.$milder
 | 
			
		||||
 | 
			
		||||
label
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue