diff --git a/src/lib/request.spec.ts b/src/lib/request.spec.ts index 4016de0..5a4137f 100644 --- a/src/lib/request.spec.ts +++ b/src/lib/request.spec.ts @@ -19,6 +19,13 @@ let xhrMockClass: ReturnType; const xmr = jest.spyOn(window, 'XMLHttpRequest'); +Object.assign(navigator, { + sendBeacon() { + // just making node aware this function exists + }, +}); +const sendBeacon = jest.spyOn(navigator, 'sendBeacon'); + const defaultData: Required = { hashMode: false, trackLocalhost: false, @@ -78,6 +85,25 @@ describe('sendEvent', () => { expect(xhrMockClass.send).toHaveBeenCalledWith(JSON.stringify(payload)); }); + test('sends via Navigator#sendBeacon', () => { + expect(sendBeacon).not.toHaveBeenCalled(); + sendEvent('myEvent', defaultData, { useSendBeacon: true }); + expect(sendBeacon).toHaveBeenCalledTimes(1); + + const payload = { + n: 'myEvent', + u: defaultData.url, + d: defaultData.domain, + r: defaultData.referrer, + w: defaultData.deviceWidth, + h: 0, + }; + + expect(sendBeacon).toHaveBeenCalledWith( + `${defaultData.apiHost}/api/event`, + JSON.stringify(payload) + ); + }); test('hash mode', () => { expect(xmr).not.toHaveBeenCalled(); sendEvent('myEvent', { ...defaultData, hashMode: true }); diff --git a/src/lib/request.ts b/src/lib/request.ts index e7c5f2a..ee64279 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -13,16 +13,20 @@ type EventPayload = { readonly p?: string; }; -// eslint-disable-next-line functional/no-mixed-type export type EventOptions = { /** * Callback called when the event is successfully sent. + * Does not work with `useSendBeacon = true`. */ readonly callback?: () => void; /** * Properties to be bound to the event. */ readonly props?: { readonly [propName: string]: string | number | boolean }; + /** + * Whether to use `Navigator#sendBeacon`. + */ + readonly useSendBeacon?: boolean; }; /** @@ -68,15 +72,19 @@ export function sendEvent( p: options && options.props ? JSON.stringify(options.props) : undefined, }; - const req = new XMLHttpRequest(); - req.open('POST', `${data.apiHost}/api/event`, true); - req.setRequestHeader('Content-Type', 'text/plain'); - req.send(JSON.stringify(payload)); - // eslint-disable-next-line functional/immutable-data - req.onreadystatechange = () => { - if (req.readyState !== 4) return; - if (options && options.callback) { - options.callback(); - } - }; + if (!options?.useSendBeacon) { + const req = new XMLHttpRequest(); + req.open('POST', `${data.apiHost}/api/event`, true); + req.setRequestHeader('Content-Type', 'text/plain'); + req.send(JSON.stringify(payload)); + // eslint-disable-next-line functional/immutable-data + req.onreadystatechange = () => { + if (req.readyState !== 4) return; + if (options && options.callback) { + options.callback(); + } + }; + } else { + navigator.sendBeacon(`${data.apiHost}/api/event`, JSON.stringify(payload)); + } } diff --git a/src/lib/tracker.spec.ts b/src/lib/tracker.spec.ts index 95dd922..88093eb 100644 --- a/src/lib/tracker.spec.ts +++ b/src/lib/tracker.spec.ts @@ -38,6 +38,7 @@ describe('tracker', () => { variation3: 1, variation4: true, }, + useSendBeacon: false, }); test('inits with default config', () => {