Skip to content

Commit

Permalink
Make the load() function idempotent
Browse files Browse the repository at this point in the history
  • Loading branch information
derrickreimer committed Jun 13, 2024
1 parent 7a9b528 commit c4a6e58
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 15 deletions.
44 changes: 35 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface Fathom {
export interface Fathom {
blockTrackingForMe: () => void;
enableTrackingForMe: () => void;
trackPageview: (opts?: PageViewOptions) => void;
Expand All @@ -7,17 +7,25 @@ interface Fathom {
setSite: (id: string) => void;
}

/**
* @see https://usefathom.com/docs/script/script-advanced#api
*/
export type PageViewOptions = {
url?: string;
referrer?: string;
};

/**
* @see https://usefathom.com/docs/features/events
*/
export type EventOptions = {
_value?: number;
_site_id?: string;
};

// refer to https://usefathom.com/support/tracking-advanced
/**
* @see https://usefathom.com/support/tracking-advanced
**/
export type LoadOptions = {
url?: string;
auto?: boolean;
Expand All @@ -39,7 +47,11 @@ type FathomCommand =
declare global {
interface Window {
fathom?: Fathom;

/** @internal */
__fathomClientQueue: FathomCommand[];
/** @internal */
__fathomIsLoading?: boolean;
}
}

Expand All @@ -57,7 +69,9 @@ const enqueue = (command: FathomCommand): void => {
* Flushes the command queue.
*/
const flushQueue = (): void => {
window.__fathomIsLoading = false;
window.__fathomClientQueue = window.__fathomClientQueue || [];

window.__fathomClientQueue.forEach(command => {
switch (command.type) {
case 'trackPageview':
Expand Down Expand Up @@ -85,13 +99,13 @@ const flushQueue = (): void => {
return;
}
});

window.__fathomClientQueue = [];
};

/**
* Loops through list of domains and warns if they start with
* http, https, http://, etc... as this does not work with the
* Fathom script.
* Loops through list of domains and warns if they start with http, https,
* http://, etc... as this does not work with the Fathom script.
*
* @param domains - List of domains to check
*/
Expand All @@ -106,7 +120,20 @@ const checkDomainsAndWarn = (domains: string[]): void => {
});
};

/**
* Loads the Fathom script.
*
* @param siteId - the id for the Fathom site.
* @param opts - advanced tracking options (https://usefathom.com/support/tracking-advanced)
*/
export const load = (siteId: string, opts?: LoadOptions): void => {
if (window.__fathomIsLoading || window.fathom) return;

// Mark that fathom is loading, so that we can prevent a race condition if
// `load` is called again BEFORE the fathom script finishes initializing (and
// so before `window.fathom` is actually defined)
window.__fathomIsLoading = true;

let tracker = document.createElement('script');

let firstScript =
Expand All @@ -116,8 +143,7 @@ export const load = (siteId: string, opts?: LoadOptions): void => {
tracker.id = 'fathom-script';
tracker.async = true;
tracker.setAttribute('data-site', siteId);
tracker.src =
opts && opts.url ? opts.url : 'https://cdn.usefathom.com/script.js';
tracker.src = opts?.url || 'https://cdn.usefathom.com/script.js';

if (opts) {
if (opts.auto !== undefined)
Expand Down Expand Up @@ -199,7 +225,7 @@ export const trackEvent = (eventName: string, opts?: EventOptions) => {
/**
* Blocks tracking for the current visitor.
*
* See https://usefathom.com/docs/features/exclude
* @see https://usefathom.com/docs/features/exclude
*/
export const blockTrackingForMe = (): void => {
if (window.fathom) {
Expand All @@ -212,7 +238,7 @@ export const blockTrackingForMe = (): void => {
/**
* Enables tracking for the current visitor.
*
* See https://usefathom.com/docs/features/exclude
* @see https://usefathom.com/docs/features/exclude
*/
export const enableTrackingForMe = (): void => {
if (window.fathom) {
Expand Down
43 changes: 37 additions & 6 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@

import * as Fathom from '../src';

const fathomStub = () => {
return {
trackPageview: jest.fn(),
trackGoal: jest.fn(),
trackEvent: jest.fn()
};
};

beforeEach(() => {
window.fathom = undefined;
delete window.__fathomIsLoading;
delete window.__fathomClientQueue;
});

Expand All @@ -25,6 +34,31 @@ describe('load', () => {
expect(fathomScript.src).toBe('https://cdn.usefathom.com/script.js');
});

it('skips injecting the script if already loaded or currently loading', () => {
// simulate the script already being loaded
Fathom.load();
// ↓
window.fathom = fathomStub();

const firstScript = document.createElement('script');
document.body.appendChild(firstScript);
Fathom.load();

const fathomScripts = Array.from(
document.getElementsByTagName('script')
).filter(s => {
return s.src == 'https://cdn.usefathom.com/script.js';
});

expect(fathomScripts.length).toBe(1);

// simulate 'onload' firing
const fathomScript = document.getElementById('fathom-script');
fathomScript.dispatchEvent(new Event('load'));

expect(window.__fathomIsLoading).toBe(false);
});

it('injects the Fathom script with options', () => {
const firstScript = document.createElement('script');
document.body.appendChild(firstScript);
Expand All @@ -46,15 +80,12 @@ describe('load', () => {
it('runs the queue after load', () => {
Fathom.trackPageview();

window.fathom = {
trackPageview: jest.fn(),
trackGoal: jest.fn(),
trackEvent: jest.fn()
};

const firstScript = document.createElement('script');
document.body.appendChild(firstScript);

Fathom.load();
// ↓
window.fathom = fathomStub();

// simulate 'onload' firing
const fathomScript = document.getElementById('fathom-script');
Expand Down

0 comments on commit c4a6e58

Please sign in to comment.