diff --git a/CHANGELOG.md b/CHANGELOG.md index a04cfafc..1251798b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Prefix the change with one of these keywords: - _Security_: in case of vulnerabilities. ## [0.5.0] +- Added: `getEcommerceItems` getter - Added: Support outbound links on `` child elements. - Added: `removeEcommerceItem` and `clearEcommerceCart` action diff --git a/packages/js/README.md b/packages/js/README.md index 8d289815..debf5978 100644 --- a/packages/js/README.md +++ b/packages/js/README.md @@ -29,6 +29,7 @@ const tracker = new MatomoTracker({ seconds: 10 // optional, default value: `15 }, linkTracking: false, // optional, default value: true + alwaysUseSendBeacon: true, // optional, default value: true configurations: { // optional, default value: {} // any valid matomo configuration, all below are optional disableCookies: true, diff --git a/packages/js/src/MatomoTracker.ts b/packages/js/src/MatomoTracker.ts index 6a46ddbe..315aef07 100644 --- a/packages/js/src/MatomoTracker.ts +++ b/packages/js/src/MatomoTracker.ts @@ -1,5 +1,7 @@ import { TRACK_TYPES } from './constants' import { + AsyncTracker, + EcommerceItems, AddEcommerceItemParams, RemoveEcommerceItemParams, CustomDimension, @@ -16,6 +18,8 @@ import { class MatomoTracker { mutationObserver?: MutationObserver + private asyncTrackers?: AsyncTracker[] + constructor(userOptions: UserOptions) { if (!userOptions.urlBase) { throw new Error('Matomo urlBase is required.') @@ -36,6 +40,7 @@ class MatomoTracker { disabled, heartBeat, linkTracking = true, + alwaysUseSendBeacon = true, configurations = {}, }: UserOptions) { const normalizedUrlBase = @@ -87,6 +92,15 @@ class MatomoTracker { scriptElement.async = true scriptElement.defer = true scriptElement.src = srcUrl || `${normalizedUrlBase}matomo.js` + scriptElement.onload = () => { + this.asyncTrackers = window.Matomo.getAsyncTrackers() + + // If enabled (default), always use sendBeacon if the browser supports it + // Disabling this makes sense when you're waiting for a tracking operation to be done and immediately do a page redirect afterwards. + if (alwaysUseSendBeacon === false) { + this.callMethod('disableAlwaysUseSendBeacon') + } + } if (scripts && scripts.parentNode) { scripts.parentNode.insertBefore(scriptElement, scripts) @@ -235,6 +249,11 @@ class MatomoTracker { ) } + // Return all ecommerce items currently in the untracked ecommerce order. + // https://matomo.org/docs/ecommerce-analytics + getEcommerceItems(): EcommerceItems { + return (this.callMethod('getEcommerceItems') || {}) as EcommerceItems + } // Removes a product from an Ecommerce order to be tracked via trackEcommerceOrder. // https://matomo.org/docs/ecommerce-analytics removeEcommerceItem({ sku }: RemoveEcommerceItemParams): void { @@ -328,6 +347,25 @@ class MatomoTracker { } } + /** + * Calls a specific matomo method which is helpful for any non-push action (getter). + * For example `getEcommerceItems` + * + * @param name The name of the method to be called. + * @param args The arguments which gets passed to the called method. + */ + private callMethod(name: string, ...args: unknown[]): unknown { + if (this.asyncTrackers && this.asyncTrackers.length) { + const asyncTracker = this.asyncTrackers[0] as Record< + string, + (args?: unknown) => unknown + > + + return asyncTracker[name](...args) + } + return undefined + } + /** * Pushes an instruction to Matomo for execution, this is equivalent to pushing entries into the `_paq` array. * diff --git a/packages/js/src/index.ts b/packages/js/src/index.ts index 7396c45f..a6c0a9a6 100644 --- a/packages/js/src/index.ts +++ b/packages/js/src/index.ts @@ -4,6 +4,9 @@ import * as types from './types' declare global { interface Window { _paq: [string, ...any[]][] + Matomo: { + getAsyncTrackers: () => types.AsyncTracker[] + } } } diff --git a/packages/js/src/types.ts b/packages/js/src/types.ts index 9cfca6ce..2d0112c9 100644 --- a/packages/js/src/types.ts +++ b/packages/js/src/types.ts @@ -1,3 +1,5 @@ +export type AsyncTracker = any + export interface CustomDimension { id: number value: string @@ -15,6 +17,7 @@ export interface UserOptions { seconds?: number } linkTracking?: boolean + alwaysUseSendBeacon?: boolean configurations?: { [key: string]: any } @@ -75,3 +78,8 @@ export interface SetEcommerceViewParams { productCategory?: string productPrice?: number } + +export type EcommerceItems = Record< + number, + [string, string, string, number, number] +>