1const PUBLIC_KEY_STRING = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0O1PYOyN9+RIZSVjLscut0v+NQ7t7tF4uX0u0HT82D0xcRtZn3OZItL9pnna0FGG+BW5DdMHZaX/7gix7j4Gh/VjzcIKucjJx7NVcb+6KDwOHNna9EW1s5xi+g0ZTLsHbgtpzuQWSpSCg/oHn34Kp9f2qo4SAXrOc21hXNhltXQIDAQAB' 2 3let uniq = ids => [...new Set(ids)] 4 5let invalidDomains = ['@gmail.', '@yahoo.', '@hotmail.', '@live.', '@aol.', '@outlook.'] 6 7 8function setCookie(cName, cValue, expMinutes) { 9 const minute = 60 * 1000 10 let date = new Date() 11 date.setTime(date.getTime() + expMinutes * minute) 12 const expires = 'expires=' + date.toUTCString() 13 document.cookie = cName + '=' + cValue + '; ' + expires + '; path=/' 14} 15 16function getCookieValue(name) { 17 const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')) 18 return match ? match[2] : '' 19} 20 21let marketoToSegmentMap = { 22 FirstName: 'first_name', 23 LastName: 'last_name', 24 pi__comments__c: 'message', 25 Phone: 'phone', 26 Company: 'company', 27 Email: 'email' 28} 29 30let marketoToSegmentBackendMap = { 31 FirstName: 'firstName', 32 LastName: 'lastName', 33 pi__comments__c: 'message', 34 Phone: 'phone', 35 Company: 'company', 36 Email: 'email', 37 Obility_ID__c: 'obilityId', 38 Contact_Form_Use_Case__c_contact: 'useCase', 39} 40 41async function fetchDomains() { 42 try { 43 const response = await fetch('/scripts/domains.txt') 44 const text = await response.text() 45 invalidDomains = text.split('\n') 46 } catch (error) { 47 await window.SENTRY.captureMessage(`Failed to fetch file with domains: ${error}`) 48 } 49} 50 51 52const kickboxValidation = async (email) => { 53 try { 54 const response = await fetch('/api/form/verify-email/', { 55 method: 'POST', 56 headers: { 57 'Content-Type': 'application/json' 58 }, 59 body: JSON.stringify({ email }) 60 }) 61 62 const data = await response.json() 63 64 return data.isEmailValid 65 66 } catch (error) { 67 await window.SENTRY.captureMessage(`Unable to validate via kickbox: ${error}`) 68 console.error('Error verifying email:', error) 69 return false 70 } 71 72} 73 74const isBusinessEmail = email => { 75 for (let i = 0; i < invalidDomains.length; i++) { 76 const domain = invalidDomains[i] 77 if (email.indexOf(domain) !== -1) { 78 return false 79 } 80 } 81 return true 82} 83 84const isEmailCorrect = email => { 85 return /^[\w]+[\w.+]+@([\w-]+\.)+[\w-]{2,63}$/.test(email) 86} 87 88const businessEmailValidation = (mktoForm, id) => { 89 const email = document.getElementById(id + 'Email') 90 email.addEventListener('input', function (event) { 91 if (!isBusinessEmail($(`#${id}Email`).val())) { 92 mktoForm.submitable(false) 93 email.setCustomValidity('Must be Business Email Address') 94 email.reportValidity() 95 } else if (!isEmailCorrect($(`#${id}Email`).val())) { 96 mktoForm.submitable(false) 97 email.setCustomValidity('Invalid Email Address') 98 email.reportValidity() 99 } else { 100 mktoForm.submitable(true) 101 email.setCustomValidity('') 102 } 103 }) 104} 105 106 107const emailValidation = (mktoForm, id) => { 108 const email = document.getElementById(id + 'Email') 109 email.addEventListener('input', function (event) { 110 if (!isEmailCorrect($(`#${id}Email`).val())) { 111 mktoForm.submitable(false) 112 email.setCustomValidity('Invalid Email Address') 113 email.reportValidity() 114 } else { 115 mktoForm.submitable(true) 116 email.setCustomValidity('') 117 } 118 }) 119} 120 121const getUtmQueryData = () => { 122 const queryString = window?.location?.search || '' 123 const urlParams = new URLSearchParams(queryString) 124 125 const getParam = (name) => urlParams.get(name) || getCookieValue(name) || '' 126 127 return { 128 name: getParam('utm_campaign'), 129 source: getParam('utm_source'), 130 term: getParam('utm_term'), 131 content: getParam('utm_content'), 132 medium: getParam('utm_medium'), 133 gclid: getParam('gclid'), 134 } 135} 136 137const createTrackingData = async(formData, contextData) => { 138 let formKeys = Object.keys(formData) 139 let parsedData = { 140 contextData 141 } 142 143 formKeys.map(key => { 144 let segmentKey = marketoToSegmentBackendMap[key] ? marketoToSegmentBackendMap[key] : key 145 parsedData[segmentKey] = formData[key] 146 }) 147 148 if (parsedData['formVid']) { 149 parsedData['formVid'] = Number(parsedData['formVid']) 150 } 151 152 parsedData['PN_Page_Before_Contact'] = '' 153 parsedData['geo'] = await fetchGeo() 154 parsedData['sessionId'] = typeof amplitude !== 'undefined' ? amplitude.getSessionId() : '' 155 156 return parsedData 157} 158 159const sendTrackingData = async values => { 160 let formKeys = Object.keys(values) 161 let segmentData = {} 162 163 formKeys.map(key => { 164 let segmentKey = marketoToSegmentMap[key] ? marketoToSegmentMap[key] : key 165 segmentData[segmentKey] = values[key] 166 167 if (key === 'Email') {
168 setCookie('UserFormEmail', values[key], 10) // Set cookie for GTM Data Layer on thank you page 169 } 170 }) 171 172 if (segmentData['formVid']) { 173 segmentData['formVid'] = Number(segmentData['formVid']) 174 } 175 176 segmentData['PN_Page_Before_Contact'] = '' 177 segmentData['geo'] = await fetchGeo() 178 179 if (typeof analytics !== 'undefined') { 180 analytics.track('contact_us', segmentData, { 181 integrations: { 182 'Amplitude': { 183 'session_id': typeof amplitude !== 'undefined' ? amplitude.getSessionId() : '' 184 } 185 } 186 }) 187 } 188} 189 190async function fetchGeo() { 191 if (typeof canUseTargetingCookies !== 'function' || !canUseTargetingCookies()) { 192 return '' 193 } 194 let geoData = {} 195 try { 196 geoData = JSON.parse(decodeURI(localStorage.getItem('geo'))) 197 } catch(error) { 198 await window.SENTRY.captureMessage(`Unable to parse geo: ${error}`) 199 } 200 if (!geoData) { 201 geoData = await fetch('/geo/').then(response => response.json()) 202 localStorage.setItem('geo', encodeURI(JSON.stringify(geoData))) 203 } 204 return geoData 205} 206 207function importPublicKey(publicKeyString) { 208 const publicKeyData = Uint8Array.from(atob(publicKeyString), c => c.charCodeAt(0)) 209 210 return window.crypto.subtle.importKey( 211 'spki', 212 publicKeyData, 213 { 214 name: 'RSA-OAEP', 215 hash: 'SHA-256', 216 }, 217 true, 218 ['encrypt'] 219 ) 220} 221 222async function encryptData(publicKey, data) { 223 const encodedData = new TextEncoder().encode(data) 224 225 const symmetricKey = await window.crypto.subtle.generateKey( 226 { 227 name: 'AES-GCM', 228 length: 128, 229 }, 230 true, 231 ['encrypt', 'decrypt'] 232 ) 233 234 const iv = window.crypto.getRandomValues(new Uint8Array(12)) 235 const encryptedData = await window.crypto.subtle.encrypt( 236 { 237 name: 'AES-GCM', 238 iv: iv, 239 }, 240 symmetricKey, 241 encodedData 242 ) 243 244 const exportedKey = await window.crypto.subtle.exportKey('raw', symmetricKey) 245 const encryptedKey = await window.crypto.subtle.encrypt( 246 { 247 name: 'RSA-OAEP', 248 }, 249 publicKey, 250 exportedKey 251 ) 252 253 const encryptedKeyBytes = new Uint8Array(encryptedKey) 254 const encryptedDataBytes = new Uint8Array(encryptedData) 255 const result = new Uint8Array(encryptedKeyBytes.length + iv.length + encryptedDataBytes.length) 256 result.set(encryptedKeyBytes, 0) 257 result.set(iv, encryptedKeyBytes.length) 258 result.set(encryptedDataBytes, encryptedKeyBytes.length + iv.length) 259 260 return result 261} 262 263function uint8ArrayToBase64(uint8Array) { 264 return new Promise((resolve, reject) => { 265 const blob = new Blob([uint8Array], { type: 'application/octet-stream' }) 266 const reader = new FileReader() 267 268 reader.onload = () => { 269 const dataUrl = reader.result 270 const base64String = dataUrl.split(',')[1] 271 resolve(base64String) 272 } 273 274 reader.onerror = (error) => { 275 reject(error) 276 } 277 278 reader.readAsDataURL(blob) 279 }) 280} 281 282const initMarketo = () => { 283 fetchDomains() 284 285 const ids = [] 286 const LDBookItV2HiddenFieldName = 'LD_BookIt_Log_ID__c' 287 288 //TODO: support old legacy system (remove in the future) 289 if (document.getElementById('form_2100')) { 290 ids.push(2100) 291 } 292 293 $('.marketo').each(function () { 294 ids.push($(this).data('id')) 295 }) 296 297 uniq(ids).map(id => { 298 MktoForms2.loadForm('//www.pubnub.com', '330-XQK-637', id) 299 }) 300 301 MktoForms2.whenReady(function (mktoForm) { 302 const form = mktoForm.getFormElem() 303 const id = $(form).data('id') 304 const submit = $(`#form_${id}`).find('.component-form__submit') 305 306 if ($('.mktoNotYou').length > 0) { 307 $('.mktoNotYou')[0].click() 308 } 309 310 const redirectUrl = $(submit).data('thank-you-page-url') != '' 311 ? $(submit).data('thank-you-page-url') 312 : location.href 313 314 const downloadFile = $(submit).data('download-file') 315 316 if ($(submit).data('enable-business-email-validation')) { 317 businessEmailValidation(mktoForm, id) 318 } else { 319 emailValidation(mktoForm, id) 320 } 321 322 if ($(submit).data('enable-chili-piper') && typeof LDBookItV2 !== 'undefined') { 323 LDBookItV2.initialize('00Dd0000000e46pEAA', 'Demo', LDBookItV2HiddenFieldName) 324 LDBookItV2.setFormProvider('marketo') 325 326 const leanDataInterval = setInterval(function () { 327 const value = $(`#mktoForm_${id} input[name=${LDBookItV2HiddenFieldName}]`).val() 328 329 if (typeof value == 'string' && value.length > 0) { 330 clearInterval(leanDataInterval) 331 $(`#${id}${LDBookItV2HiddenFieldName}`).val(value) 332 } 333 }, 500) 334 } 335 336 mktoForm.onValidate(function (isValid) { 337 //window.sentry.captureException(`Form ${isValid ? 'valid' : 'invali
337d'}`, context) 338 }) 339 340 mktoForm.onSuccess(function (values, followURL) { 341 localStorage.setItem('form_' + id + '_submitted', 'true') 342 343 if (values.Email && window.signals 344 && window.location.pathname.indexOf('/communication-preferences/') === -1 345 && window.location.pathname.indexOf('/unsubscribe/') === -1) { 346 window.signals.identify({ email: values.Email }) 347 } 348 349 sendTrackingData(values).then(() => { 350 if ($(submit).data('enable-chili-piper') && typeof LDBookItV2 !== 'undefined' && isBusinessEmail($(`#${id}Email`).val())) { 351 window.addEventListener('message', function(e){ 352 if (e.data.message === 'LD_POPUP_CLOSED') { 353 try { 354 window.sendAmplitudeEvent('Calendar Booking Skipped', {}) 355 } catch (error) { 356 console.error(error) 357 } 358 location.href = redirectUrl 359 } 360 if (e.data.message === 'LD_POST_BOOKING') { 361 let properties = {} 362 363 if (e.data.hasOwnProperty('message') && e.data.message.hasOwnProperty('host')) { 364 properties.meeting_host = e.data.message.host 365 } 366 367 try { 368 window.sendAmplitudeEvent('Calendar Booking Confirmed', properties) 369 } catch (error) { 370 console.error(error) 371 } 372 373 location.href = redirectUrl 374 } 375 }) 376 377 LDBookItV2.initialize('00Dd0000000e46pEAA', 'Demo', LDBookItV2HiddenFieldName) 378 LDBookItV2.submit() 379 380 } else { 381 if (downloadFile) { 382 var a = document.createElement('a') 383 a.href = downloadFile 384 a.setAttribute('target', '_blank') 385 a.click() 386 } 387 location.href = redirectUrl 388 } 389 }) 390 return false 391 }) 392 393 $(`#form_${id}`).submit(async function (e) { 394 e.preventDefault() 395 396 const secondaryField = $(`#form_${id}`).find('input[name="secondary_email"]') 397 const secondaryValue = secondaryField.val() 398 399 if (secondaryValue && secondaryValue.trim() !== '') { 400 try { 401 // Collect form data 402 let formData = {} 403 $.each($(this).serializeArray(), function (i, field) { 404 formData[field.name] = field.value 405 }) 406 407 await fetch('/api/form/submit-validation/', { 408 method: 'POST', 409 headers: { 'Content-Type': 'application/json' }, 410 body: JSON.stringify({ 411 formId: id, 412 timestamp: new Date().toISOString(), 413 userAgent: window?.navigator?.userAgent || '', 414 url: window?.location?.href || '', 415 formData: formData 416 }) 417 }) 418 } catch (error) {} 419 420 localStorage.setItem('form_' + id + '_submitted', 'true') 421 location.href = '/company/contact-sales/thank-you-page/' 422 return false 423 } 424 425 // Name validation for spam/bot detection (current) + shadow (candidate) 426 if (typeof checkFormInput === 'function') { 427 var firstName = $(`#${id}FirstName`).val() || '' 428 var lastName = $(`#${id}LastName`).val() || '' 429 var company = $(`#${id}Company`).val() || '' 430 var nameResult = checkFormInput(firstName, lastName, company) 431 var candidateResult = 432 typeof checkFormInputCandidate === 'function' 433 ? checkFormInputCandidate(firstName, lastName, company) 434 : null 435 436 // Emit the observation only when the candidate helper is available AND 437 // either mechanism flags. Without a candidate there is nothing to observe, 438 // and a shadow:null request would be misclassified as enforcement by the 439 // route and duplicate the legacy alert. (Never blocks on the candidate.) 440 if (candidateResult && (!nameResult.isValid || !candidateResult.isValid)) { 441 try { 442 // keepalive so the observation survives the redirect / mktoForm.submit() 443 // navigation that follows â Slack is the only sink for these samples. 444 // NOTE: formData is intentionally omitted â it is unused on the shadow 445 // path (the report uses nameValidation, and the legacy alert is skipped 446 // for shadow pings), and a large free-form field would push the body 447 // past the ~64 KiB keepalive limit and silently drop the observation. 448 fetch('/api/form/submit-validation/', { 449 method: 'POST', 450 keepalive: true, 451 headers: { 'Content-Type': 'application/json' }, 452 body: JSON.stringify({ 453 formId: id, 454 timestamp: new Date().toISOString(), 455 userAgent: window?.navigator?.userAgent || '', 456 url: window?.location?.href || '', 457 detectionType: 'gibberish_name', 458 nameValidation: { 459 averageScore: nameResult.averageScore, 460 firstNameScore: nameResult.firstNameScore, 461 lastNameScore: nameResult.lastNameScore, 462 companyScore: nameResult.companyScore, 463 failedChecks: nameResult.failedChecks, 464 firstName: firstName, 465 lastName: lastName, 466 company: company 467 }, 468 shadow: { 469 isValid: candidateResult.isValid, 470 firstNameScore: candidateResult.firstNameScore, 471 lastNameScore: candidateResult.lastNameScore, 472 companyScore: candidateResult.companyScore, 473 failedChecks: candidateResult.failedChecks 474 } 475 }) 476 }).catch(function () {}) 477 } catch (error) {} 478 } 479 480 // Blocking is driven ONLY by the current detector (unchanged behavior). 481 if (!nameResult.isValid) { 482 var shouldBlock = false 483 try { 484 // The enforcement request is awaited (not keepalive), so it keeps the 485 // full serialized formData â the legacy Slack alert's Form Data section 486 // depends on it. (Only the keepalive shadow ping drops formData to stay 487 // under the ~64 KiB body limit.) 488 let nameFormData = {} 489 $.each($(this).serializeArray(), function (i, field) { 490 nameFormData[field.name] = field.value 491 }) 492 var response = await fetch('/api/form/submit-validation/', { 493 method: 'POST', 494 headers: { 'Content-Type': 'application/json' }, 495 body: JSON.stringify({ 496 formId: id, 497 timestamp: new Date().toISOString(), 498 userAgent: window?.navigator?.userAgent || '', 499 url: window?.location?.href || '', 500 detectionType: 'gibberish_name', 501 nameValidation: { 502 averageScore: nameResult.averageScore, 503 firstNameScore: nameResult.firstNameScore, 504 lastNameScore: nameResult.lastNameScore, 505 companyScore: nameResult.companyScore, 506 failedChecks: nameResult.failedChecks, 507 firstName: firstName, 508 lastName: lastName, 509 company: company 510 }, 511 formData: nameFormData 512 }) 513 }) 514 var responseData = await response.json() 515 shouldBlock = responseData.shouldBlock === true 516 } catch (error) {} 517 518 if (shouldBlock) { 519 localStorage.setItem('form_' + id + '_submitted', 'true') 520 location.href = '/company/contact-sales/thank-you-page/' 521 return false 522 } 523 } 524 } 525 526 const emailInput = $(`#${id}Email`)[0] 527 const emailValue = emailInput.value 528 529 if (!isEmailCorrect(emailValue)) { 530 mktoForm.submitable(false) 531 emailInput.setCustomValidity('Invalid Email Address') 532 emailInput.reportValidity() 533 return false 534 } 535 536 const isKickboxValid = await kickboxValidation(emailValue) 537 if (!isKickboxValid) { 538 mktoForm.submitable(false) 539 emailInput.setCustomValidity('Must be Business Email Address') 540 emailInput.reportValidity() 541 return false 542 } 543 544 let userId = null 545 if (typeof analytics !== 'undefined' && analytics.user !== undefined) {
546 userId = analytics.user().id() 547 } 548 549 const contextData = { 550 title: document?.title || '', 551 path: location?.pathname || '', 552 url: window?.location?.href || '', 553 referrer: document?.referrer || '', 554 search: window?.location?.search || '', 555 userAgent: window?.navigator?.userAgent || '', 556 locale: window?.navigator.language || '', 557 campaign: getUtmQueryData(), 558 userId, 559 } 560 561 $(submit).html($(submit).data('waiting-label')) 562 $(submit).attr('disabled', 'true') 563 564 let values = {} 565 $.each($(this).serializeArray(), function (i, field) { 566 values[field.name] = field.value 567 }) 568 569 const parsedData = await createTrackingData(values, contextData) 570 571 try { 572 const stringifyContent = JSON.stringify(parsedData) 573 const publicKey = await importPublicKey(PUBLIC_KEY_STRING) 574 const encryptedData = await encryptData(publicKey, stringifyContent) 575 const encryptedDataString = await uint8ArrayToBase64(encryptedData) 576 577 await fetch('/api/form_fill', { 578 method: 'POST', 579 headers: { 580 'Content-Type': 'text/plain', 581 }, 582 body: encryptedDataString, 583 }) 584 } catch(error) { 585 await window.SENTRY.captureMessage(`unable to send form_fill tracking request: ${error}`) 586 } 587 588 mktoForm.addHiddenFields(values) 589 mktoForm.submit() 590 591 return false 592 }) 593 }) 594} 595 596let marketoIntervalCnt = 0 597const marketoInterval = setInterval(function () { 598 if (typeof MktoForms2 !== 'undefined' ) { 599 clearInterval(marketoInterval) 600 initMarketo() 601 } 602 603 if (marketoIntervalCnt >= 10) { 604 clearInterval(marketoInterval) 605 } 606 607 marketoIntervalCnt ++ 608}, 500)
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.