1$(function() { 2 3 let xhr, captchaWidgetElement, signInInitiatorName; 4 // Captcha state shared by the proactive (mandatory sign-up) and reactive (429.0107) flows â one 5 // grecaptcha widget whose callback branches on captchaMode (DEV-13809). 6 let captchaMode, captchaRetryRegistration, captchaResponseText; 7 const fileName = $('.jsParams').find('.jsFileName').val(); 8 const isLoggedIn = Config.isUserLoggedIn; 9 const pageBody = $('body'); 10 11 12// --------------------- SIGN IN bottom sheet --------------------- 13 const signInButton = $('.jsShowSignIn', pageBody); 14 const signInBS = $('.jsSignInBottomSheet', pageBody); 15 const signInBack = $('.jsBottomSheetBack', signInBS); 16 const signInForm = $('.jsSignInForm', signInBS); 17 let returnTo = $('.jsReturnTo', signInBS).val(); 18 const mandatorySignupCaptcha = $('.jsMandatorySignupCaptcha', signInBS).val() === 'true'; 19 const analyticsPageName = $('.jsAnalyticsPageName').val(); 20 let postLoginAction = localStorage.getItem('post-login-action'); 21 const postLoginActionItemId = localStorage.getItem('post-login-action-item-id'); 22 23// structure 24 25 const facebookSignInButton = $('.jsFBLoginButton', signInBS); 26 const googleSignInButton = $('.jsGPLoginButton', signInBS); 27 const showSecondaryScreen = $('.jsGoEmailLogic', signInBS); 28 const continueSignInButton = $('.jsContinueSignInButton', signInBS); 29 const remindPasswordButton = $('.jsRemindPassword', signInBS); 30 const emailPasswordBlock = $('.jsEmailPasswordBlock', signInBS); 31 const signInLogin = $('.jsInputLogin', signInBS); 32 const signInPassword = $('.jsInputPassword', signInBS); 33 const signInUserName = $('.jsInputUsername', signInBS); 34 const showHidePassword = $('.jsShowPassword', signInBS); 35 const privacyBlock = $('.jsPrivacyBlock', signInBS); 36 37// errors 38 const allErrors = $('.jsError', signInBS); 39 const errorInvalidEmail = $(".jsEmailNotValid", signInBS); 40 const errorEmptyEmail = $(".jsEmailRequired", signInBS); 41 const errorEmptyPassword = $(".jsPasswordRequired", signInBS); 42 const errorEmptyName = $(".jsFirstNameRequired", signInBS); 43 const errorConcernRequired = $(".jsGDPRAgreeRequired", signInBS); 44 const errorFromServer = $(".jsErrorFromServer", signInBS); 45 const errorInvalidPassword = $(".jsInvalidPassword", signInBS); 46 47// hints 48 const remindPasswordHint = $('.jsRemindPasswordHint', signInBS); 49 const remindPasswordEmail = $('.jsEmailForInstructions', signInBS); 50 51// params for login from www.4s.io 52 const SIGNIN_PARAMS_PREFIX = "signInParams="; 53 const postLoginHash = window.location.hash !== '' ? window.location.hash : window.location.search; 54 const postLoginHashIndex = postLoginHash.indexOf(SIGNIN_PARAMS_PREFIX); 55 56 57 signInButton.click(function () { 58 if (isLoggedIn) { 59 window.location.href = `${window.location.origin}/account/home.jsp`; 60 return; 61 } 62 window.showSignInBottomSheet({screenNumber: 1, initiator: 'header'}); 63 }); 64 65// screen 1 - main login buttons (via social or via email) 66// screen 2 - enter email 67// screen 3 - enter user name 68// screen 4 - enter password 69// screen 5 - privacy checkboxes 70// screen 6 - verify email 71// screen 7 - validate captcha 72 73 window.showSignInBottomSheet = function (options) { 74 let { screenNumber, initiator, postAuthenticateAction, postAuthenticateItemId, linkReturnTo } = options; 75 76 postAuthenticateItemId = typeof postAuthenticateItemId === 'object' ? JSON.stringify(postAuthenticateItemId) : postAuthenticateItemId; 77 78 if (pageBody.hasClass("site4sIo")) { 79 const siteOptions = { 80 screenNumber, 81 initiator, 82 postAuthenticateAction, 83 postAuthenticateItemId: postAuthenticateAction === "add_folder_to_library" ? JSON.parse(postAuthenticateItemId) : postAuthenticateItemId, 84 linkReturnTo 85 } 86 87 const url = window.location.href.replace(`${window.location.origin}/`, Config.mainSiteUrlHttpsPath); 88 89 if (siteOptions && siteOptions.linkReturnTo) { 90 siteOptions.linkReturnTo = siteOptions.linkReturnTo.replace(`${window.location.origin}/`, Config.mainSiteUrlHttpsPath); 91 } 92 93 const stringJSONParams = JSON.stringify(siteOptions); 94 const paramsURLEncoded = encodeURIComponent(encodeURIComponent(stringJSONParams).replace(/'/g, "%27")); 95 96 window.location.href = url + (siteOptions ? (url.indexOf("?") !== -1 && "&" || "?") + SIGNIN_PARAMS_PREFIX + paramsURLEncoded : ""); 97 98 return; 99 } 100 101 localStorage.setItem('post-login-signin-initiator-element', initiator); 102 103 if (postAuthenticateAction) { 104 localStorage.setItem('post-login-action', postAuthenticateAction); 105 } 106 107 if (postAuthenticateItemId) { 108 localStorage.setItem('post-login-action-item-id', postAuthenticateItemId); 109 } 110 111 setCurrentScreen(screenNumber || 1);
112 signInInitiatorName = initiator ? initiator : 'header'; 113 114 window.showBottomSheet('signIn'); 115 pageBody.attr('data-signin-initiator', signInInitiatorName); 116 117 if (pageBody.hasClass('type-music') && !linkReturnTo) { 118 linkReturnTo = window.location.href; 119 } 120 121 trackAnalyticsEventClicks('Login_Popup', 'login_popup_view'); 122 123 setReturnTo(googleSignInButton, linkReturnTo); 124 setReturnTo(facebookSignInButton, linkReturnTo); 125 returnTo = linkReturnTo || $('.jsReturnTo', signInBS).val(); 126 } 127 128 129 130 131// --------------------- post login actions analytics ------------------- 132 if (waitConfirmAdultContent && waitConfirmAdultContent(Config.isUserLoggedIn ? $('#jsCurUserId').val(): false)) { 133 return; 134 } 135 136 if (!pageBody.hasClass('jsSocialLogin')) { 137 localStorage.removeItem('post-login-action'); 138 localStorage.removeItem('post-login-action-item-id'); 139 } 140 141 if (isLoggedIn && postLoginAction) { 142 setTimeout(function() { 143 Events.fireEvent(postLoginAction, postLoginActionItemId); 144 postLoginAction = false; 145 }, 200); 146 } 147 148// check sign-in prompt params in url 149 if (postLoginHashIndex !== -1) { 150 if (isLoggedIn) { // check for redirect after social sign-in 151 window.history.pushState('', document.title, (window.location.href).replace(postLoginHash.substring(postLoginHashIndex-1), '')); 152 return; 153 } 154 155 const paramsURLEncoded = postLoginHash.substring(postLoginHashIndex + SIGNIN_PARAMS_PREFIX.length, postLoginHash.length); 156 157 if (paramsURLEncoded) { 158 const stringJSONParams = decodeURIComponent(decodeURIComponent(paramsURLEncoded).replace("%27", "'")); 159 160 try { 161 const postLoginParams = JSON.parse(stringJSONParams); 162 163 window.showSignInBottomSheet(postLoginParams); 164 } catch (e){ 165 Utils.log(`Something wrong happened during execute hash-callback, ${e}`); 166 } 167 } 168 } 169 170 171 $('.jsCloseBottomSheet', signInBS).click(function () { 172 localStorage.removeItem('post-login-action'); 173 localStorage.removeItem('post-login-action-item-id'); 174 175 trackAnalyticsEventClicks('Login_Popup', 'login_popup_close'); 176 }) 177 178 function setReturnTo(el, linkReturnTo) { 179 if (!linkReturnTo) { 180 //el.attr('data-href', encodeURIComponent(el.attr('data-def'))); 181 el.attr('data-href', encodeUrl(el.attr('data-def'))); 182 183 return; 184 } 185 const href = new URL(el.attr('data-def')); 186 href.searchParams.set('fp', encodeURIComponent(linkReturnTo)); 187 el.attr('data-href', href); 188 } 189 190 function encodeUrl(url) { 191 const urlObj = new URL(url); 192 const params = new URLSearchParams(urlObj.search); 193 194 for (let [key, value] of params.entries()) { 195 params.set(key, encodeURIComponent(value)); 196 } 197 198 return `${urlObj.origin}${urlObj.pathname}?${params.toString()}`; 199 } 200 201 function getCurrentScreen() { 202 return signInBS.attr('data-screen'); 203 } 204 205 function setCurrentScreen(val) { 206 signInBS.attr('data-screen', val); 207 $('input:visible', emailPasswordBlock).focus(); 208 } 209 210 googleSignInButton.click(function () { 211 trackAnalyticsEventClicks('Login_Popup', 'login_popup_google'); 212 window.location.href = googleSignInButton.attr('data-href'); 213 }); 214 215 facebookSignInButton.click(function () { 216 trackAnalyticsEventClicks('Login_Popup', 'login_popup_facebook'); 217 window.location.href = facebookSignInButton.attr('data-href'); 218 }); 219 220 showSecondaryScreen.click(function () { 221 setCurrentScreen(2); 222 signInLogin.focus(); 223 224 trackAnalyticsEventClicks('Login_Popup', 'login_popup_email'); 225 }); 226 227 signInBack.click(function () { 228 if (getCurrentScreen() === '4' && signInBS.hasClass('jsUserExists')) { 229 setCurrentScreen(2); 230 } else { 231 setCurrentScreen(1 * getCurrentScreen() - 1); 232 } 233 234 allErrors.hide(); 235 }); 236 237 continueSignInButton.click(function () { 238 if (!validateTextInputs()) return; 239 240 trackAnalyticsContinueClickEvent(getCurrentScreen()); 241 242 if (getCurrentScreen() === '2') { 243 checkGoLoginOrRegistration(); 244 return; 245 } 246 247 if (getCurrentScreen() === '4' && signInBS.hasClass('jsUserExists')) { 248 validateUserLogin(); 249 return; 250 } 251 252 if (getCurrentScreen() === '5') { 253 // Mandatory captcha as the final registration step (DEV-13809): show the captcha instead of 254 // creating the account directly; its callback runs doSignIn(true, token). Property off â old flow. 255 if (mandatorySignupCaptcha) { 256 showSignupCaptcha(); 257 } else { 258 doSignIn(true); 259 } 260 return; 261 } 262 263 setCurrentScreen(1 * getCurrentScreen() + 1); 264 }); 265 266 $('input', emailPasswordBlock).on('keydown change', function (e) { 267 allErrors.hide(); 268 269 if (e.keyCode === 13) { // Enter key 270 continueSignInButton.click(); 271 } 272 }); 273 274 function checkGoLoginOrRegistration() { 275 if (xhr) xhr.abort(); 276 277 xhr = $.ajax({ 278 type: 'GET', 279 url: '/web/rest/usersWeb/validate', 280 data: { 281 email: signInLogin.val() 282 }, 283 success: function () { 284 console.log('unregistered user, proceed registration'); 285 signInBS.removeClass('jsUserExists'); 286 signInLogin.attr('name', 'email'); 287 setCurrentScreen(3); 288 }, 289 error: function () { 290 console.log('user exists, go to login'); 291 signInBS.addClass('jsUserExists'); 292 signInLogin.attr('name', 'login'); 293 setCurrentScreen(4); 294 } 295 }); 296 } 297 298 function validateTextInputs() { 299 if (getCurrentScreen() === '2' && !signInLogin[0].checkValidity()) { 300 if (signInLogin.val().trim() === '') { 301 errorEmptyEmail.show(); 302 } else { 303 errorInvalidEmail.show(); 304 } 305 return false;
306 } else if (getCurrentScreen() === '4' && !signInPassword[0].checkValidity()) { 307 errorEmptyPassword.show(); 308 return false; 309 } else if (getCurrentScreen() === '3' && !signInUserName[0].checkValidity()) { 310 errorEmptyName.show(); 311 return false; 312 } else if (getCurrentScreen() === '5' && !validatePrivacyCheckboxes()) { 313 errorConcernRequired.show(); 314 return false; 315 } else { 316 allErrors.hide(); 317 return true; 318 } 319 } 320 321 function validatePrivacyCheckboxes() { 322 const inputsCount = $('input[type="checkbox"]', privacyBlock).length; 323 const checkedCount = $(':checked', privacyBlock).length; 324 325 return inputsCount === checkedCount; 326 } 327 328 showHidePassword.click(function () { 329 $(this).toggleClass('is-visible'); 330 signInPassword.attr('type', function (index, attr) { 331 return attr === 'password' ? 'text' : 'password'; 332 }) 333 }); 334 335 function doSignIn(newRegistration, captchaToken) { 336 signInBS.addClass('isLoading'); 337 338 let postData = signInForm.serialize() + "&addHeaderForSpecial=true"; 339 if (captchaToken) postData += "&captchaResponse=" + encodeURIComponent(captchaToken); 340 341 $.ajax({ 342 type: 'POST', 343 url: newRegistration ? '/web/rest/usersWeb/signUp' : '/web/login', 344 data: postData, 345 success: function (resp, state, xhr) { 346 347 //redirect for addHeaderForSpecial 348 const xLocation = xhr && xhr.getResponseHeader('x-location'); 349 if (xLocation) { 350 window.location.href = xLocation; 351 return; 352 } 353 354 signInBS.removeClass('isLoading'); 355 window.hideAllBottomSheets(); 356 localStorage.setItem('email-login-in-process', newRegistration ? 'signup' : 'email'); 357 358 if (returnTo === "") { 359 window.location.href = `${window.location.origin}/account/home.jsp`; 360 return; 361 } 362 363 if (postLoginHashIndex !== -1) { // only for www.4s.io case 364 window.history.pushState('', document.title, (window.location.href).replace(postLoginHash.substring(postLoginHashIndex-1), '')); 365 returnTo = returnTo.replace(postLoginHash.substring(postLoginHashIndex-1), ''); 366 } 367 368 if (window.location.hash) { 369 window.location.reload(); 370 } else { 371 window.location.href = decodeURIComponent(returnTo); 372 } 373 374 }, 375 error: function (data) { 376 signInBS.removeClass('isLoading'); 377 378 trackAnalyticsEventLogins('Login_Fail', newRegistration ? 'signup_fail' : 'email_fail'); 379 380 const parsedJSON = JSON.parse(data.responseText); 381 const errorCode = parsedJSON.code ? parsedJSON.code.split('.')[1] : ''; 382 if (errorCode === '0107') { 383 // Reactive rate-limit captcha (unchanged mechanism); retry the SAME endpoint after solve (AC #6). 384 const msg1 = fileName && fileName.length ? $('.jsMsgToDownload').val() : $('.jsMsgForFree').val(); 385 const msg2 = fileName && fileName.length ? fileName : $('.jsMsgFreeSpace').val(); 386 showCaptchaScreen(data.responseText, msg1, msg2, newRegistration); 387 } else if (errorCode === '0325') { 388 // ERROR_INVALID_CAPTCHA from signUp â re-show the mandatory captcha for a fresh token AND 389 // surface the server message ("Captcha is invalidâ¦") so it isn't a silent loop (DEV-13809 QA 390 // Major 3). Entered form data is preserved (it lives on the earlier sign-up screens). 391 showSignupCaptcha(parsedJSON.message); 392 } else { 393 // A non-captcha server error raised after the mandatory sign-up captcha (we're on screen 7, 394 // which has no error area) â return to the last form step so the message is visible. Form 395 // data is preserved. Reactive flow hides the sheet before retrying, so it doesn't apply. 396 if (captchaMode === 'signup') setCurrentScreen(5); 397 showServerError(data); 398 } 399 } 400 }); 401 } 402 403 function validateUserLogin() { 404 signInBS.addClass('isLoading'); 405 406 $.ajax({ 407 type: "get", 408 url: Config.mainSiteUrlHttpsPath + "web/login/validate", 409 data: { 410 login: signInLogin.val(), 411 password: signInPassword.val() 412 }, 413 dataType: "jsonp", 414 success: function (data) { 415 if (!data.success) { 416 errorInvalidPassword.show(); 417 signInBS.removeClass('isLoading'); 418 } else { 419 doSignIn(false); 420 } 421 }, 422 error: function (data) { 423 errorFromServer.text(data.responseText).show(); 424 signInBS.removeClass('isLoading'); 425 } 426 }); 427 } 428 429 remindPasswordButton.click(function () { 430 trackAnalyticsEventClicks('Login_Popup', 'login_page_password_forgot'); 431 432 $.ajax({ 433 type: 'POST', 434 url: '/remindPassword.jsp', 435 data: signInForm.serialize(), 436 success: function () { 437 allErrors.hide(); 438 remindPasswordHint.show(); 439 remindPasswordEmail.text(signInLogin.val()); 440 remindPasswordButton.hide(); 441 }, 442 error: function (data) { 443 console.log(data); 444 } 445 }); 446 }) 447 448 signInBS.click(function () { 449 if (remindPasswordHint.is(':visible')) { 450 remindPasswordHint.hide(); 451 remindPasswordButton.removeAttr('style'); 452 } 453 }) 454 455 function showServerError(data) { 456 if (data.responseText && data.responseText.length) { 457 const parsedJSON = JSON.parse(data.responseText); 458 const msg = (!parsedJSON.message) ? data.statusText : parsedJSON.message; 459 const errorCode = parsedJSON.code.substring(4); 460 if (errorCode === '0228') { 461 msg.replace('$[p1]', signInLogin.val()) 462 } 463 errorFromServer.text(msg).show(); 464 } 465 } 466 467 // Renders (or resets) the shared reCAPTCHA widget on the captcha screen and shows it. Its callback branches on captchaMode: 468 // 'signup' : proactive mandatory captcha â token goes straight to signUp (DEV-13809). 469 // 'reactive': existing 429.0107 flow â validate limitKey via /web/rest/api/reCaptcha, then retry the ORIGINATING endpoint. Mechanism unchanged. 470 471 function renderOrResetCaptcha() { 472 // Scope to the sign-in BS + unique id: .jsContentCaptchaBlock / #reCaptchaBlock are shared with 473 // the comments captcha, so an unscoped lookup could render into another screen (DEV-13809 QA Minor 4). 474 const wrapElement = $(".jsContentCaptchaBlock", signInBS); 475 476 setCurrentScreen(7); 477 $('.jsCaptchaError', signInBS).hide().empty(); 478 479 if (typeof captchaWidgetElement != 'undefined') { 480 grecaptcha.reset(captchaWidgetElement); 481 } else { 482 captchaWidgetElement = grecaptcha.render(wrapElement.find('#signInReCaptchaBlock')[0], { 483 'sitekey': Config.captchaSiteKey, 484 'theme': 'light', 485 'size': Config.isMobile ? 'compact' : 'normal', 486 'callback': onCaptchaSolved 487 }); 488 } 489 } 490 491 function onCaptchaSolved() { 492 // Read from THIS widget (DEV-13809 QA Minor 4): pages can host multiple reCAPTCHA widgets 493 // (comments), so a hardcoded #g-recaptcha-response may pick another widget's token. 494 const token = grecaptcha.getResponse(captchaWidgetElement); 495 496 if (captchaMode === 'signup') { 497 doSignIn(true, token); 498 return; 499 } 500 501 // reactive 429.0107 â clear the rate limit, then retry whichever endpoint hit it. 502 $.post('/web/rest/api/reCaptcha', 503 { 504 limitKey: Utils.parseResponseText(captchaResponseText, 'limitKey'), 505 response: token 506 }, 507 function (data) { 508 if (data && data.valid) { 509 window.hideAllBottomSheets(); 510 doSignIn(captchaRetryRegistration); 511 } 512 } 513 ); 514 } 515 516 // Proactive mandatory captcha shown before the account is created. errorMsg (optional) surfaces a 517 // server rejection (ERROR_INVALID_CAPTCHA) instead of a silent re-render (DEV-13809 QA Major 3). 518 function showSignupCaptcha(errorMsg) { 519 captchaMode = 'signup'; 520 renderOrResetCaptcha(); 521 if (errorMsg) $('.jsCaptchaError', signInBS).text(errorMsg).show(); 522 } 523 524 // Reactive captcha shown when the backend returns 429.0107 (rate limit). 525 function showCaptchaScreen(responseText, msg1, msg2, newRegistration) { 526 captchaMode = 'reactive'; 527 captchaResponseText = responseText; 528 captchaRetryRegistration = newRegistration; 529 renderOrResetCaptcha(); 530 } 531 532 $('.jsResendVerification').click(function () { 533 const self = $(this); 534 535 $.ajax({ 536 type: 'get', 537 dataType: 'jsonp', 538 url: '/account/sendEmailVerification.jsp', 539 success: function (data) { 540 if (data.ok) { 541 self.hide(); 542 signInBS.find('.jsConfirmationResent').show(); 543 } else { 544 signInBS.find('.jsSendingError').text(data.errorMessage); 545 } 546 } 547 }); 548 }); 549 550 551 552 553 554 555 556 557 558 // --------------------- sign-in analytics --------------------- 559 560 function trackAnalyticsEventClicks(eventName, action) { 561 gtag('event', eventName, { 562 'action': action, 563 'source': `${eventName.toLowerCase()}_${analyticsPageName}
563_${signInInitiatorName}`, 564 }); 565 } 566 567 function trackAnalyticsEventLogins(eventName, action) { 568 gtag('event', eventName, { 569 'login': action, 570 'source': `${eventName.toLowerCase()}_${analyticsPageName}_${signInInitiatorName}`, 571 }); 572 } 573 574 function trackAnalyticsContinueClickEvent(screenNumber) { 575 let eventName = ''; 576 577 switch (screenNumber) { 578 case '2': 579 eventName = 'login_popup_email_continue' 580 break; 581 case '3': 582 eventName = 'login_popup_name_view_continue' 583 break; 584 case '4': 585 eventName = 'login_popup_password_continue' 586 break; 587 case '5': 588 eventName = 'login_popup_policy_continue' 589 break; 590 } 591 592 if (eventName !== '') { 593 trackAnalyticsEventClicks('Login_Popup', eventName); 594 } 595 } 596 597 const facebookLoginFlag = localStorage.getItem('facebook-login-in-process'); 598 const googleLoginFlag = localStorage.getItem('google-login-in-process'); 599 const emailLoginFlag = localStorage.getItem('email-login-in-process'); 600 601 if (emailLoginFlag || googleLoginFlag || facebookLoginFlag) { 602 signInInitiatorName = localStorage.getItem('post-login-signin-initiator-element'); 603 604 const eventName = googleLoginFlag ? 'google' : (facebookLoginFlag ? 'facebook' : emailLoginFlag); 605 606 trackAnalyticsEventLogins('Login', eventName); 607 608 localStorage.removeItem('email-login-in-process') 609 localStorage.removeItem('facebook-login-in-process') 610 localStorage.removeItem('google-login-in-process'); 611 localStorage.removeItem('post-login-signin-initiator-element'); 612 } 613})
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.