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,
),
)
}
}
|