login.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * Script for login.ejs
  3. */
  4. // Validation Regexes.
  5. const validUsername = /^[a-zA-Z0-9_]{1,16}$/
  6. const basicEmail = /^\S+@\S+\.\S+$/
  7. //const validEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
  8. // Login Elements
  9. const loginCancelContainer = document.getElementById('loginCancelContainer')
  10. const loginCancelButton = document.getElementById('loginCancelButton')
  11. const loginEmailError = document.getElementById('loginEmailError')
  12. const loginUsername = document.getElementById('loginUsername')
  13. const loginPasswordError = document.getElementById('loginPasswordError')
  14. const loginPassword = document.getElementById('loginPassword')
  15. const checkmarkContainer = document.getElementById('checkmarkContainer')
  16. const loginRememberOption = document.getElementById('loginRememberOption')
  17. const loginButton = document.getElementById('loginButton')
  18. const loginForm = document.getElementById('loginForm')
  19. // Control variables.
  20. let lu = false, lp = false
  21. /**
  22. * Show a login error.
  23. *
  24. * @param {HTMLElement} element The element on which to display the error.
  25. * @param {string} value The error text.
  26. */
  27. function showError(element, value){
  28. element.innerHTML = value
  29. element.style.opacity = 1
  30. }
  31. /**
  32. * Shake a login error to add emphasis.
  33. *
  34. * @param {HTMLElement} element The element to shake.
  35. */
  36. function shakeError(element){
  37. if(element.style.opacity == 1){
  38. element.classList.remove('shake')
  39. void element.offsetWidth
  40. element.classList.add('shake')
  41. }
  42. }
  43. /**
  44. * Validate that an email field is neither empty nor invalid.
  45. *
  46. * @param {string} value The email value.
  47. */
  48. function validateEmail(value){
  49. if(value){
  50. if(!basicEmail.test(value) && !validUsername.test(value)){
  51. showError(loginEmailError, '* Invalid Value')
  52. loginDisabled(true)
  53. lu = false
  54. } else {
  55. loginEmailError.style.opacity = 0
  56. lu = true
  57. if(lp){
  58. loginDisabled(false)
  59. }
  60. }
  61. } else {
  62. lu = false
  63. showError(loginEmailError, '* Required')
  64. loginDisabled(true)
  65. }
  66. }
  67. /**
  68. * Validate that the password field is not empty.
  69. *
  70. * @param {string} value The password value.
  71. */
  72. function validatePassword(value){
  73. if(value){
  74. loginPasswordError.style.opacity = 0
  75. lp = true
  76. if(lu){
  77. loginDisabled(false)
  78. }
  79. } else {
  80. lp = false
  81. showError(loginPasswordError, '* Required')
  82. loginDisabled(true)
  83. }
  84. }
  85. // Emphasize errors with shake when focus is lost.
  86. loginUsername.addEventListener('focusout', (e) => {
  87. validateEmail(e.target.value)
  88. shakeError(loginEmailError)
  89. })
  90. loginPassword.addEventListener('focusout', (e) => {
  91. validatePassword(e.target.value)
  92. shakeError(loginPasswordError)
  93. })
  94. // Validate input for each field.
  95. loginUsername.addEventListener('input', (e) => {
  96. validateEmail(e.target.value)
  97. })
  98. loginPassword.addEventListener('input', (e) => {
  99. validatePassword(e.target.value)
  100. })
  101. /**
  102. * Enable or disable the login button.
  103. *
  104. * @param {boolean} v True to enable, false to disable.
  105. */
  106. function loginDisabled(v){
  107. if(loginButton.disabled !== v){
  108. loginButton.disabled = v
  109. }
  110. }
  111. /**
  112. * Enable or disable loading elements.
  113. *
  114. * @param {boolean} v True to enable, false to disable.
  115. */
  116. function loginLoading(v){
  117. if(v){
  118. loginButton.setAttribute('loading', v)
  119. loginButton.innerHTML = loginButton.innerHTML.replace('LOGIN', 'LOGGING IN')
  120. } else {
  121. loginButton.removeAttribute('loading')
  122. loginButton.innerHTML = loginButton.innerHTML.replace('LOGGING IN', 'LOGIN')
  123. }
  124. }
  125. /**
  126. * Enable or disable login form.
  127. *
  128. * @param {boolean} v True to enable, false to disable.
  129. */
  130. function formDisabled(v){
  131. loginDisabled(v)
  132. loginCancelButton.disabled = v
  133. loginUsername.disabled = v
  134. loginPassword.disabled = v
  135. if(v){
  136. checkmarkContainer.setAttribute('disabled', v)
  137. } else {
  138. checkmarkContainer.removeAttribute('disabled')
  139. }
  140. loginRememberOption.disabled = v
  141. }
  142. /**
  143. * Parses an error and returns a user-friendly title and description
  144. * for our error overlay.
  145. *
  146. * @param {Error | {cause: string, error: string, errorMessage: string}} err A Node.js
  147. * error or Mojang error response.
  148. */
  149. function resolveError(err){
  150. // Mojang Response => err.cause | err.error | err.errorMessage
  151. // Node error => err.code | err.message
  152. if(err.cause != null && err.cause === 'UserMigratedException') {
  153. return {
  154. title: 'Error During Login:<br>Invalid Credentials',
  155. desc: 'You\'ve attempted to login with a migrated account. Try again using the account email as the username.'
  156. }
  157. } else {
  158. if(err.error != null){
  159. if(err.error === 'ForbiddenOperationException'){
  160. if(err.errorMessage != null){
  161. if(err.errorMessage === 'Invalid credentials. Invalid username or password.'){
  162. return {
  163. title: 'Error During Login:<br>Invalid Credentials',
  164. desc: 'The email or password you\'ve entered is incorrect. Please try again.'
  165. }
  166. } else if(err.errorMessage === 'Invalid credentials.'){
  167. return {
  168. title: 'Error During Login:<br>Too Many Attempts',
  169. desc: 'There have been too many login attempts with this account recently. Please try again later.'
  170. }
  171. }
  172. }
  173. }
  174. } else {
  175. // Request errors (from Node).
  176. if(err.code != null){
  177. if(err.code === 'ENOENT'){
  178. // No Internet.
  179. return {
  180. title: 'Error During Login:<br>No Internet Connection',
  181. desc: 'You must be connected to the internet in order to login. Please connect and try again.'
  182. }
  183. } else if(err.code === 'ENOTFOUND'){
  184. // Could not reach server.
  185. return {
  186. title: 'Error During Login:<br>Authentication Server Offline',
  187. 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>.'
  188. }
  189. }
  190. }
  191. }
  192. }
  193. if(err.message != null){
  194. // Unknown error with request.
  195. return {
  196. title: 'Error During Login:<br>Unknown Error',
  197. desc: err.message
  198. }
  199. } else {
  200. // Unknown Mojang error.
  201. return {
  202. title: err.error,
  203. desc: err.errorMessage
  204. }
  205. }
  206. }
  207. let loginViewOnSuccess = VIEWS.landing
  208. let loginViewOnCancel = VIEWS.settings
  209. function loginCancelEnabled(val){
  210. if(val){
  211. $(loginCancelContainer).show()
  212. } else {
  213. $(loginCancelContainer).hide()
  214. }
  215. }
  216. loginCancelButton.onclick = (e) => {
  217. switchView(getCurrentView(), loginViewOnCancel, 500, 500, () => {
  218. loginCancelEnabled(false)
  219. })
  220. }
  221. // Disable default form behavior.
  222. loginForm.onsubmit = () => { return false }
  223. // Bind login button behavior.
  224. loginButton.addEventListener('click', () => {
  225. // Disable form.
  226. formDisabled(true)
  227. // Show loading stuff.
  228. loginLoading(true)
  229. AuthManager.addAccount(loginUsername.value, loginPassword.value).then((value) => {
  230. updateSelectedAccount(value)
  231. loginButton.innerHTML = loginButton.innerHTML.replace('LOGGING IN', 'SUCCESS')
  232. $('.circle-loader').toggleClass('load-complete')
  233. $('.checkmark').toggle()
  234. //console.log(value)
  235. setTimeout(() => {
  236. switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => {
  237. // Temporary workaround
  238. if(loginViewOnSuccess === VIEWS.settings){
  239. prepareSettings()
  240. }
  241. loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
  242. loginCancelEnabled(false) // Reset this for good measure.
  243. loginUsername.value = ''
  244. loginPassword.value = ''
  245. $('.circle-loader').toggleClass('load-complete')
  246. $('.checkmark').toggle()
  247. loginLoading(false)
  248. loginButton.innerHTML = loginButton.innerHTML.replace('SUCCESS', 'LOGIN')
  249. formDisabled(false)
  250. })
  251. }, 1000)
  252. }).catch((err) => {
  253. loginLoading(false)
  254. const errF = resolveError(err)
  255. setOverlayContent(errF.title, errF.desc, 'Try Again')
  256. setOverlayHandler(() => {
  257. formDisabled(false)
  258. toggleOverlay(false)
  259. })
  260. toggleOverlay(true)
  261. console.log(err)
  262. })
  263. })