/** * 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 loginCancelContainer = document.getElementById('loginCancelContainer') const loginCancelButton = document.getElementById('loginCancelButton') 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') const loginMSButton = document.getElementById('loginMSButton') // Control variables. let lu = false, lp = false const loggerLogin = LoggerUtil('%c[Login]', 'color: #000668; font-weight: bold') /** * 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, Lang.queryJS('login.error.invalidValue')) loginDisabled(true) lu = false } else { loginEmailError.style.opacity = 0 lu = true if(lp){ loginDisabled(false) } } } else { lu = false showError(loginEmailError, Lang.queryJS('login.error.requiredValue')) 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, Lang.queryJS('login.error.invalidValue')) 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(Lang.queryJS('login.login'), Lang.queryJS('login.loggingIn')) } else { loginButton.removeAttribute('loading') loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.login')) } } /** * Enable or disable login form. * * @param {boolean} v True to enable, false to disable. */ function formDisabled(v){ loginDisabled(v) loginCancelButton.disabled = 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: Lang.queryJS('login.error.userMigrated.title'), desc: Lang.queryJS('login.error.userMigrated.desc') } } else { if(err.error != null){ if(err.error === 'ForbiddenOperationException'){ if(err.errorMessage != null){ if(err.errorMessage === 'Invalid credentials. Invalid username or password.'){ return { title: Lang.queryJS('login.error.invalidCredentials.title'), desc: Lang.queryJS('login.error.invalidCredentials.desc') } } else if(err.errorMessage === 'Invalid credentials.'){ return { title: Lang.queryJS('login.error.rateLimit.title'), desc: Lang.queryJS('login.error.rateLimit.desc') } } } } } else { // Request errors (from Node). if(err.code != null){ if(err.code === 'ENOENT'){ // No Internet. return { title: Lang.queryJS('login.error.noInternet.title'), desc: Lang.queryJS('login.error.noInternet.desc') } } else if(err.code === 'ENOTFOUND'){ // Could not reach server. return { title: Lang.queryJS('login.error.authDown.title'), desc: Lang.queryJS('login.error.authDown.desc') } } } } } if(err.message != null){ if(err.message === 'NotPaidAccount'){ return { title: Lang.queryJS('login.error.notPaid.title'), desc: Lang.queryJS('login.error.notPaid.desc') } } else { // Unknown error with request. return { title: Lang.queryJS('login.error.unknown.title'), desc: err.message } } } else { // Unknown Mojang error. return { title: err.error, desc: err.errorMessage } } } let loginViewOnSuccess = VIEWS.landing let loginViewOnCancel = VIEWS.settings let loginViewCancelHandler function loginCancelEnabled(val){ if(val){ $(loginCancelContainer).show() } else { $(loginCancelContainer).hide() } } loginCancelButton.onclick = (e) => { switchView(getCurrentView(), loginViewOnCancel, 500, 500, () => { loginUsername.value = '' loginPassword.value = '' loginCancelEnabled(false) if(loginViewCancelHandler != null){ loginViewCancelHandler() loginViewCancelHandler = null } }) } // 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) => { updateSelectedAccount(value) loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success')) $('.circle-loader').toggleClass('load-complete') $('.checkmark').toggle() setTimeout(() => { switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => { // Temporary workaround if(loginViewOnSuccess === VIEWS.settings){ prepareSettings() } loginViewOnSuccess = VIEWS.landing // Reset this for good measure. loginCancelEnabled(false) // Reset this for good measure. loginViewCancelHandler = null // Reset this for good measure. loginUsername.value = '' loginPassword.value = '' $('.circle-loader').toggleClass('load-complete') $('.checkmark').toggle() loginLoading(false) loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login')) formDisabled(false) }) }, 1000) }).catch((err) => { loginLoading(false) const errF = resolveError(err) setOverlayContent(errF.title, errF.desc, Lang.queryJS('login.tryAgain')) setOverlayHandler(() => { formDisabled(false) toggleOverlay(false) }) toggleOverlay(true) loggerLogin.log('Error while logging in.', err) }) }) loginMSButton.addEventListener('click', (event) => { loginMSButton.disabled = true ipcRenderer.send('openMSALoginWindow', 'open') }) ipcRenderer.on('MSALoginWindowReply', (event, ...args) => { if (args[0] === 'error') { setOverlayContent('ERROR', 'There is already a login window open!', 'OK') setOverlayHandler(() => { toggleOverlay(false) }) toggleOverlay(true) return } const queryMap = args[0] if (queryMap.has('error')) { let error = queryMap.get('error') let errorDesc = queryMap.get('error_description') if(error === 'access_denied'){ error = 'ERRPR' errorDesc = 'To use the NexusLauncher, you must agree to the required permissions! Otherwise you can\'t use this launcher with Microsoft accounts.

Despite agreeing to the permissions you don\'t give us the possibility to do anything with your account, because all data will always be sent back to you (the launcher) IMMEDIATELY and WITHOUT WAY.' } setOverlayContent(error, errorDesc, 'OK') setOverlayHandler(() => { loginMSButton.disabled = false toggleOverlay(false) }) toggleOverlay(true) return } // Disable form. formDisabled(true) // Show loading stuff. loginLoading(true) const authCode = queryMap.get('code') AuthManager.addMSAccount(authCode).then(account => { updateSelectedAccount(account) loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success')) $('.circle-loader').toggleClass('load-complete') $('.checkmark').toggle() setTimeout(() => { switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => { // Temporary workaround if (loginViewOnSuccess === VIEWS.settings) { prepareSettings() } loginViewOnSuccess = VIEWS.landing // Reset this for good measure. loginCancelEnabled(false) // Reset this for good measure. loginViewCancelHandler = null // Reset this for good measure. loginUsername.value = '' loginPassword.value = '' $('.circle-loader').toggleClass('load-complete') $('.checkmark').toggle() loginLoading(false) loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login')) formDisabled(false) }) }, 1000) }).catch(error => { loginMSButton.disabled = false loginLoading(false) setOverlayContent('ERROR', error.message ? error.message : 'An error occurred while logging in with Microsoft! For more detailed information please check the log. You can open it with CTRL + SHIFT + I.', Lang.queryJS('login.tryAgain')) setOverlayHandler(() => { formDisabled(false) toggleOverlay(false) }) toggleOverlay(true) loggerLogin.error(error) }) })