SkirdaElectronLauncher/app/assets/js/scripts/login.js
Daniel Scalzi 5fe43ac8e9
Added basic functionality to server selection UI.
Basic selection and updating of the selected server has been added. There are a few subtle mechanics which need to be added still, such as keybind shortcuts (enter to submit, etc). In addition, functionality still needs to be added to generate the list of servers from the manifest file.

Fixed a minor issue with the login view.
Updated play button styles.
Updated dependencies.
2018-04-26 18:41:26 -04:00

251 lines
7.7 KiB
JavaScript

/**
* Script for login.ejs
*/
// Validation Regexes.
const validUsername = /^[a-zA-Z0-9_]{1,16}$/
const basicEmail = /^\S+@\S+\.\S+$/
//const validEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
// Login Elements
const loginEmailError = document.getElementById('loginEmailError')
const loginUsername = document.getElementById('loginUsername')
const loginPasswordError = document.getElementById('loginPasswordError')
const loginPassword = document.getElementById('loginPassword')
const checkmarkContainer = document.getElementById('checkmarkContainer')
const loginRememberOption = document.getElementById('loginRememberOption')
const loginButton = document.getElementById('loginButton')
const loginForm = document.getElementById('loginForm')
// Control variables.
let lu = false, lp = false
/**
* Show a login error.
*
* @param {HTMLElement} element The element on which to display the error.
* @param {string} value The error text.
*/
function showError(element, value){
element.innerHTML = value
element.style.opacity = 1
}
/**
* Shake a login error to add emphasis.
*
* @param {HTMLElement} element The element to shake.
*/
function shakeError(element){
if(element.style.opacity == 1){
element.classList.remove('shake')
void element.offsetWidth
element.classList.add('shake')
}
}
/**
* Validate that an email field is neither empty nor invalid.
*
* @param {string} value The email value.
*/
function validateEmail(value){
if(value){
if(!basicEmail.test(value) && !validUsername.test(value)){
showError(loginEmailError, '* Invalid Value')
loginDisabled(true)
lu = false
} else {
loginEmailError.style.opacity = 0
lu = true
if(lp){
loginDisabled(false)
}
}
} else {
lu = false
showError(loginEmailError, '* Required')
loginDisabled(true)
}
}
/**
* Validate that the password field is not empty.
*
* @param {string} value The password value.
*/
function validatePassword(value){
if(value){
loginPasswordError.style.opacity = 0
lp = true
if(lu){
loginDisabled(false)
}
} else {
lp = false
showError(loginPasswordError, '* Required')
loginDisabled(true)
}
}
// Emphasize errors with shake when focus is lost.
loginUsername.addEventListener('focusout', (e) => {
validateEmail(e.target.value)
shakeError(loginEmailError)
})
loginPassword.addEventListener('focusout', (e) => {
validatePassword(e.target.value)
shakeError(loginPasswordError)
})
// Validate input for each field.
loginUsername.addEventListener('input', (e) => {
validateEmail(e.target.value)
})
loginPassword.addEventListener('input', (e) => {
validatePassword(e.target.value)
})
/**
* Enable or disable the login button.
*
* @param {boolean} v True to enable, false to disable.
*/
function loginDisabled(v){
if(loginButton.disabled !== v){
loginButton.disabled = v
}
}
/**
* Enable or disable loading elements.
*
* @param {boolean} v True to enable, false to disable.
*/
function loginLoading(v){
if(v){
loginButton.setAttribute('loading', v)
loginButton.innerHTML = loginButton.innerHTML.replace('LOGIN', 'LOGGING IN')
} else {
loginButton.removeAttribute('loading')
loginButton.innerHTML = loginButton.innerHTML.replace('LOGGING IN', 'LOGIN')
}
}
/**
* Enable or disable login form.
*
* @param {boolean} v True to enable, false to disable.
*/
function formDisabled(v){
loginDisabled(v)
loginUsername.disabled = v
loginPassword.disabled = v
if(v){
checkmarkContainer.setAttribute('disabled', v)
} else {
checkmarkContainer.removeAttribute('disabled')
}
loginRememberOption.disabled = v
}
/**
* Parses an error and returns a user-friendly title and description
* for our error overlay.
*
* @param {Error | {cause: string, error: string, errorMessage: string}} err A Node.js
* error or Mojang error response.
*/
function resolveError(err){
// Mojang Response => err.cause | err.error | err.errorMessage
// Node error => err.code | err.message
if(err.cause != null && err.cause === 'UserMigratedException') {
return {
title: 'Error During Login:<br>Invalid Credentials',
desc: 'You\'ve attempted to login with a migrated account. Try again using the account email as the username.'
}
} else {
if(err.error != null){
if(err.error === 'ForbiddenOperationException'){
if(err.errorMessage != null){
if(err.errorMessage === 'Invalid credentials. Invalid username or password.'){
return {
title: 'Error During Login:<br>Invalid Credentials',
desc: 'The email or password you\'ve entered is incorrect. Please try again.'
}
} else if(err.errorMessage === 'Invalid credentials.'){
return {
title: 'Error During Login:<br>Too Many Attempts',
desc: 'There have been too many login attempts with this account recently. Please try again later.'
}
}
}
}
} else {
// Request errors (from Node).
if(err.code != null){
if(err.code === 'ENOENT'){
// No Internet.
return {
title: 'Error During Login:<br>No Internet Connection',
desc: 'You must be connected to the internet in order to login. Please connect and try again.'
}
} else if(err.code === 'ENOTFOUND'){
// Could not reach server.
return {
title: 'Error During Login:<br>Authentication Server Offline',
desc: 'Mojang\'s authentication server is currently offline or unreachable. Please wait a bit and try again. You can check the status of the server on <a href="https://help.mojang.com/">Mojang\'s help portal</a>.'
}
}
}
}
}
if(err.message != null){
// Unknown error with request.
return {
title: 'Error During Login:<br>Unknown Error',
desc: err.message
}
} else {
// Unknown Mojang error.
return {
title: err.error,
desc: err.errorMessage
}
}
}
// Disable default form behavior.
loginForm.onsubmit = () => { return false }
// Bind login button behavior.
loginButton.addEventListener('click', () => {
// Disable form.
formDisabled(true)
// Show loading stuff.
loginLoading(true)
AuthManager.addAccount(loginUsername.value, loginPassword.value).then((value) => {
loginButton.innerHTML = loginButton.innerHTML.replace('LOGGING IN', 'SUCCESS')
$('.circle-loader').toggleClass('load-complete')
$('.checkmark').toggle()
//console.log(value)
setTimeout(() => {
$('#loginContainer').fadeOut(500, () => {
$('#landingContainer').fadeIn(500)
})
}, 1000)
}).catch((err) => {
loginLoading(false)
const errF = resolveError(err)
setOverlayContent(errF.title, errF.desc, 'Try Again')
setOverlayHandler(() => {
formDisabled(false)
toggleOverlay(false)
})
toggleOverlay(true)
console.log(err)
})
})