All files / src/scripts/instrumentor/patcher/form injectSignalsElement.ts

75.75% Statements 25/33
71.42% Branches 10/14
100% Functions 5/5
75.75% Lines 25/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88        2x                   738x   738x 348x     390x         390x   6x         6x   6x 6x           6x     6x 6x           390x   390x     395x                     738x 738x 395x 395x                     6x 6x 6x 6x 6x    
import { PatcherContext } from '../context'
import { SIGNALS_KEY } from '../../../../shared/const'
import { logger } from '../../../shared/logger'
 
const REMOVE_LISTENER_SYMBOL = Symbol('removeListener')
 
/**
 * Patches a given HTML form to include additional fingerprinting signals during submission.
 *
 * @param {HTMLFormElement} form The form element to be patched.
 * @param {PatcherContext} ctx The context used for retrieving signals and additional operations.
 */
export function injectSignalsElement(form: HTMLFormElement, ctx: PatcherContext) {
  // Always remove the existing listener before adding a new one to prevent duplicates
  removeSubmitListener(form)
 
  if (!ctx.isProtectedUrl(form.action, form.method) || form.enctype === 'text/plain') {
    return
  }
 
  Iif (form.method?.toLowerCase() === 'get') {
    logger.warn('Forms with method "get" are currently not supported.')
    return
  }
 
  const handleSubmit = async (event: SubmitEvent) => {
    // Ignore form submissions if they were prevented by another handler (since this patcher is only for native submissions)
    Iif (event.defaultPrevented) {
      logger.debug('Form submission prevented by another handler')
      return
    }
 
    const signals = await ctx.getSignals()
 
    try {
      Iif (!signals) {
        logger.debug('No signals found for form submission')
        return
      }
 
      // If signals are already present, we don't need to add them again
      Iif (form.querySelector(`input[name="${SIGNALS_KEY}"]`)) {
        return
      }
      const field = createSignalsField(signals)
      form.appendChild(field)
    } catch (e) {
      logger.error('Error getting signals during form submission:', e)
    }
  }
 
  form.addEventListener('submit', handleSubmit)
 
  Object.assign(form, {
    // Attach a custom property to the form element to store the remove listener function if needed.
    [REMOVE_LISTENER_SYMBOL]: () => {
      form.removeEventListener('submit', handleSubmit)
    },
  })
}
 
/**
 * Removes a previously attached submit event listener from a given form element, if it exists.
 *
 * @param {HTMLFormElement & { [REMOVE_LISTENER_SYMBOL]?: () => void }} form - The form element from which the submit event listener is to be removed. It may include the custom property defined by REMOVE_LISTENER_SYMBOL.
 */
function removeSubmitListener(form: HTMLFormElement & { [REMOVE_LISTENER_SYMBOL]?: () => void }) {
  const removeListener = form[REMOVE_LISTENER_SYMBOL]
  if (typeof removeListener === 'function') {
    logger.debug('Removing existing submit listener from form')
    removeListener()
  }
}
 
/**
 * Creates a hidden input field with the specified signal value.
 *
 * @param {string} signals - The value to be assigned to the hidden input field.
 * @return {HTMLInputElement} The created hidden input field with the specified signal value.
 */
function createSignalsField(signals: string): HTMLInputElement {
  const field = document.createElement('input')
  field.hidden = true
  field.value = signals
  field.name = SIGNALS_KEY
  return field
}