Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Web Worker and ServiceWorker scope #45

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

Tombarr
Copy link

@Tombarr Tombarr commented Apr 16, 2023

Add support for Web Worker and ServiceWorker contexts with the following changes

  • When navigator.sendBeacon isn't available, default to XMLHttpRequest (Worker) or fetch (ServiceWorker)
  • When sessionStorage or localStorage isn't available (i.e. Worker/ ServiceWorker), use an in-memory Map and warn that clientId is not persisted
  • Allow use of global self or window, depending on context
  • Don't set window or document scoped events when not available
  • Because localStorage isn't available in a Worker scope, per Unable to override default parameters #43, allow param overrides, i.e. to pull clientId from IndexedDB

@Tombarr
Copy link
Author

Tombarr commented Apr 19, 2023

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   96.58 |    77.25 |     100 |   96.49 |                   
 index.ts |   96.46 |    77.25 |     100 |   96.39 | 283,305,393,403   
 model.ts |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Jest: "global" coverage threshold for branches (78%) not met: 77.25%

Looks like CI ran well this time, but failed due to test coverage. I'll take a look at covering additional branches within ga4

Copy link
Owner

@jahilldev jahilldev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work! This looks great 👍

Just a few initial comments, and I believe you'll need to either run eslint . --fix or setup format on save for your IDE using ESLint & Prettier to get the formatting just right.

I'm also going to have a think about any other opportunities we might have to reduce the output file size a bit.

packages/ga4/src/index.ts Outdated Show resolved Hide resolved
@@ -96,8 +114,8 @@ function getArguments(args: any[]): [string | undefined, IProps] {
*
* -------------------------------- */

function getEventMeta({ type = '', event }: Pick<IProps, 'type' | 'event'>) {
const searchString = document.location.search;
function getEventMeta({ type = '', event }: Pick<IProps, 'type' | 'event'>): [string, string][] {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: this should be across multiple lines according to Prettier 👍

Suggested change
function getEventMeta({ type = '', event }: Pick<IProps, 'type' | 'event'>): [string, string][] {
function getEventMeta({
type = '',
event,
}: Pick<IProps, 'type' | 'event'>): [string, string][] {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't seem to get these configs loaded into VSCode with the Prettier extension. I'll look for a CLI command soon. Might be worth adding as a pre-commit hook if it can be automated.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be obvious, but just to make sure, it's worth checking these:

Format on save:
Screenshot 2023-04-26 at 13 38 19

Formatter:
Screenshot 2023-04-26 at 13 38 11

packages/ga4/src/index.ts Outdated Show resolved Hide resolved
const eventType = this?.event?.type;
const sync = syncEvents.has((eventType || '').toLowerCase());

const xhr = new XMLHttpRequest();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Is there a reason we're not using fetch() as the primary fallback for navigator.sendBeacon()? Is there a need to use XMLHttpRequest here? Is there an opportunity to save some bytes here by going down that route?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although fetch with the keepalive property is a fallback for navigator.sendBeacon, it's not available on any version of Firefox. Synchronous XHR is more broadly supported, so it's the preferred fallback. See https://developer.mozilla.org/en-US/docs/Web/API/Fetch#keepalive

*
* -------------------------------- */

function sendBeaconFetch(url: string | URL, data?: XMLHttpRequestBodyInit | null): boolean {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Although we're not making use of the Promise returned via fetch() here (yet), it's still probably worth returning one here for future sake. Can we rework this so we can return & resolve the Promise used within this functions scope?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work! This looks great 👍

Just a few initial comments, and I believe you'll need to either run eslint . --fix or setup format on save for your IDE using ESLint & Prettier to get the formatting just right.

I'm also going to have a think about any other opportunities we might have to reduce the output file size a bit.

Thanks for the suggestions, I (hopefully) brought test coverage high enough now that it'll pass CI. Returning and passing through Promise will be a larger refactor that I'll get to when time permits.

// #3: fetch (ServiceWorker Scope)
const hasFetch = (typeof fetch !== 'undefined');
if (hasFetch) {
if (data) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Do we need this condition here? It seems like sendBeaconFetch() can handle an undefined value for data, resulting in an empty body value. Any thoughts?

The same goes for this condition and this one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was writing to the tests, because without this the toBeCalledWith need to explicitly include undefined. From what I can tell, jest-extended is needed for expect.anything() to include undefined or include some other hack.

https://stackoverflow.com/questions/47027011/using-expect-anyobject-or-expect-anything-does-not-work-to-match-undefi

Copy link
Owner

@jahilldev jahilldev Apr 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, if we know in the test case whether data will be undefined (or something else), couldn't we do one of the following:

// data is defined in test case
expect(sendBeaconFetch).toBeCalledWith(expect.any(String), stubOfData);

// data is undefined in test case
expect(sendBeaconFetch).toBeCalledWith(expect.any(String), undefined);

I may be missing something, but we should be able to do something similar I believe. Ideally we'd also want an explicit value for each, so rather than using expect.any() we pass in the stub or test value.

What do you think?

@jahilldev jahilldev added ga4 enhancement New feature or request labels Apr 25, 2023
@@ -9,8 +9,11 @@ import {
isTargetElement,
getUrlData,
getEventParams,
mergeEventParams,
EventParamArray,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: This seems to be very similar to the following type, can we re-use that instead?

type EventParams = Record<string, ParamValue> | [string, ParamValue][];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request ga4
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants