PageSourceSearch

https://www.pubnub.com/scripts/marketo.js

js pubnub.com collected 2026-09-24 08:40:37 UTC 19,406 bytes, 608 lines download raw bytes

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.