All files / src/scripts/instrumentor/patcher/xhr open.ts

84.21% Statements 16/19
100% Branches 3/3
100% Functions 4/4
83.33% Lines 15/18

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                                19x   19x               15x   15x   1x         14x 14x                             14x 14x   14x       2x 2x     14x       14x             14x      
import { PatcherContext } from '../context'
import { FingerprintContextSymbol, XHRFingerprintMetadata, XHRContext } from './types'
import { handleSignalsInjection } from '../signalsInjection'
import { createPatcherRequest } from './patcherRequest'
import { logger } from '../../../shared/logger'
 
/**
 * Creates a patched version of the `XMLHttpRequest.prototype.open` method to capture request metadata,
 * apply signal handling, and provide additional context for fingerprinting.
 *
 * @param {PatcherContext} ctx - The context object used for configuring and managing the patching process
 *                              and interacting with signal handling mechanisms.
 * @return {function} Returns a new function that wraps the original `XMLHttpRequest.open` method and includes
 *                    additional behavior for metadata collection and signal injection.
 */
export function createPatchedOpen(ctx: PatcherContext): typeof XMLHttpRequest.prototype.open {
  const originalOpen = XMLHttpRequest.prototype.open
 
  return function patchedOpen(
    this: XMLHttpRequest & XHRFingerprintMetadata,
    method: string,
    url: string,
    async: boolean = true,
    username?: string | null,
    password?: string | null
  ) {
    const callOpen = () => originalOpen.call(this, method, url, async, username, password)
 
    if (!async) {
      // Sync requests are not supported for now
      return callOpen()
    }
 
    let metadata: XHRFingerprintMetadata
 
    try {
      metadata = {
        method: method?.toUpperCase?.(),
        // Resolve relative URLs against the current location
        url: new URL(url, location.origin).toString(),
      }
    } catch (e) {
      // If URL cannot be resolved (very unlikely)
      logger.warn('Failed to resolve XHR URL for patching:', e)
 
      metadata = {
        method: method?.toUpperCase?.(),
        url,
      }
    }
 
    try {
      const request = createPatcherRequest(this, metadata)
      // Start gathering signals as soon as possible.
      const signalsPromise = handleSignalsInjection({
        request,
        ctx,
      }).catch((error) => {
        logger.error('Error injecting signals:', error)
        return false
      })
 
      const fingerprintContext: XHRContext = {
        handleSignalsInjectionPromise: signalsPromise,
        ...metadata,
      }
      Object.assign(this, {
        [FingerprintContextSymbol]: fingerprintContext,
      })
    } catch (e) {
      logger.error('Error setting XHR fingerprint context:', e)
    }
 
    return callOpen()
  }
}