# All files beaconProxy.ts

0% Statements 0/51
0% Branches 0/24
0% Functions 0/5
0% Lines 0/51

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
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105

     import NetworkMessage from './networkMessage'
    import { RequestState, INetworkMessage, RequestResponseData } from './types';
    import { genStringBody, getURL } from './utils'

    // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
    const getContentType = (data?: BodyInit) => {
      if (data instanceof Blob) {
        return data.type
      }
      if (data instanceof FormData) {
        return 'multipart/form-data'
      }
      if (data instanceof URLSearchParams) {
        return 'application/x-www-form-urlencoded;charset=UTF-8'
      }
      return 'text/plain;charset=UTF-8'
    }

    export class BeaconProxyHandler<T extends typeof navigator.sendBeacon> implements ProxyHandler<T> {
      constructor(
        private readonly ignoredHeaders: boolean | string[],
        private readonly setSessionTokenHeader: (cb: (name: string, value: string) => void) => void,
        private readonly sanitize: (data: RequestResponseData) => RequestResponseData | null,
        private readonly sendMessage: (item: INetworkMessage) => void,
        private readonly isServiceUrl: (url: string) => boolean,
      ) {}

      public apply(target: T, thisArg: T, argsList: any[]) {
        const urlString: string = argsList[0]
        const data: BodyInit = argsList[1]
        const item = new NetworkMessage(this.ignoredHeaders, this.setSessionTokenHeader, this.sanitize)
        if (this.isServiceUrl(urlString)) {
          return target.apply(thisArg, argsList)
        }
        const url = getURL(urlString)
        item.method = 'POST'
        item.url = urlString
        item.name = (url.pathname.split('/').pop() || '') + url.search
        item.requestType = 'beacon'
        item.requestHeader = { 'Content-Type': getContentType(data) }
        item.status = 0
        item.statusText = 'Pending'

        if (url.search && url.searchParams) {
          item.getData = {}
          for (const [key, value] of url.searchParams) {
            item.getData[key] = value
          }
        }
        item.requestData = genStringBody(data)

        if (!item.startTime) {
          item.startTime = performance.now()
        }

        const isSuccess = target.apply(thisArg, argsList)
        if (isSuccess) {
          item.endTime = performance.now()
          item.duration = item.endTime - (item.startTime || item.endTime)
          item.status = 0
          item.statusText = 'Sent'
          item.readyState = 4
        } else {
          item.status = 500
          item.statusText = 'Unknown'
        }

        const msg = item.getMessage()
        if (msg) {
          this.sendMessage(msg)
        }
        return isSuccess
      }
    }

    export default class BeaconProxy {
      public static origSendBeacon = window?.navigator?.sendBeacon

      public static hasSendBeacon() {
        return !!BeaconProxy.origSendBeacon
      }

      public static create(
        ignoredHeaders: boolean | string[],
        setSessionTokenHeader: (cb: (name: string, value: string) => void) => void,
        sanitize: (data: RequestResponseData) => RequestResponseData | null,
        sendMessage: (item: INetworkMessage) => void,
        isServiceUrl: (url: string) => boolean,
      ) {
        if (!BeaconProxy.hasSendBeacon()) {
          return undefined
        }
        return new Proxy(
          BeaconProxy.origSendBeacon,
          new BeaconProxyHandler(
            ignoredHeaders,
            setSessionTokenHeader,
            sanitize,
            sendMessage,
            isServiceUrl,
          ),
        )
      }
    }