ngx-matomo-client
Version:
Matomo (fka. Piwik) client for Angular applications
1 lines • 136 kB
Source Map (JSON)
{"version":3,"file":"ngx-matomo-client-core.mjs","sources":["../../../projects/ngx-matomo-client/core/utils/function.ts","../../../projects/ngx-matomo-client/core/utils/url.ts","../../../projects/ngx-matomo-client/core/tracker/script-factory.ts","../../../projects/ngx-matomo-client/core/utils/coercion.ts","../../../projects/ngx-matomo-client/core/utils/script-injector.ts","../../../projects/ngx-matomo-client/core/tracker/configuration.ts","../../../projects/ngx-matomo-client/core/holder.ts","../../../projects/ngx-matomo-client/core/tracker/internal-matomo-tracker.service.ts","../../../projects/ngx-matomo-client/core/testing/testing-tracker.ts","../../../projects/ngx-matomo-client/core/tracker/errors.ts","../../../projects/ngx-matomo-client/core/tracker/matomo-tracker.service.ts","../../../projects/ngx-matomo-client/core/tracker/matomo-initializer.service.ts","../../../projects/ngx-matomo-client/core/providers.ts","../../../projects/ngx-matomo-client/core/directives/matomo-opt-out-form.component.ts","../../../projects/ngx-matomo-client/core/directives/matomo-opt-out-form.component.html","../../../projects/ngx-matomo-client/core/directives/matomo-track-click.directive.ts","../../../projects/ngx-matomo-client/core/directives/matomo-tracker.directive.ts","../../../projects/ngx-matomo-client/core/matomo.module.ts","../../../projects/ngx-matomo-client/core/public-api.ts","../../../projects/ngx-matomo-client/core/ngx-matomo-client-core.ts"],"sourcesContent":["function coerceErrorHandler<T, ARGS extends unknown[]>(\n errorOrHandler: string | ((...args: ARGS) => T | never),\n): (...args: ARGS) => T | never {\n return typeof errorOrHandler === 'string'\n ? () => {\n throw new Error(errorOrHandler);\n }\n : errorOrHandler;\n}\n\n/** Wrap a function to ensure it is called only once, ignoring all subsequent calls */\nexport function runOnce<T, ARGS extends unknown[]>(\n fn: (...args: ARGS) => T,\n): (...args: ARGS) => T | void;\n/** Wrap a function to ensure it is called only once, calling an error handler otherwise */\nexport function runOnce<T, ARGS extends unknown[], U = T>(\n fn: (...args: ARGS) => T,\n errorOrHandler: string | ((...args: ARGS) => U | never),\n): (...args: ARGS) => T | U;\nexport function runOnce<T, ARGS extends unknown[], U = T>(\n fn: (...args: ARGS) => T,\n errorOrHandler?: string | ((...args: ARGS) => U | never),\n): (...args: ARGS) => T | U | void {\n const errorHandler = errorOrHandler ? coerceErrorHandler(errorOrHandler) : () => undefined;\n let run = false;\n\n return (...args: ARGS) => {\n if (run) {\n return errorHandler(...args);\n }\n\n run = true;\n\n return fn(...args);\n };\n}\n","export function appendTrailingSlash(str: string): string {\n return str.endsWith('/') ? str : `${str}/`;\n}\n","import { InjectionToken } from '@angular/core';\n\nexport type MatomoScriptFactory = (scriptUrl: string, document: Document) => HTMLScriptElement;\n\nexport const createDefaultMatomoScriptElement: MatomoScriptFactory = (scriptUrl, document) => {\n const g = document.createElement('script');\n\n g.type = 'text/javascript';\n g.defer = true;\n g.async = true;\n g.src = scriptUrl;\n\n return g;\n};\n\nexport const MATOMO_SCRIPT_FACTORY = new InjectionToken<MatomoScriptFactory>(\n 'MATOMO_SCRIPT_FACTORY',\n {\n providedIn: 'root',\n factory: () => createDefaultMatomoScriptElement,\n },\n);\n","export type CssSizeInput = string | number | null | undefined;\n\nexport function requireNonNull<T>(value: T | null | undefined, message: string): T {\n if (value === null || value === undefined) {\n throw new Error('Unexpected ' + value + ' value: ' + message);\n }\n\n return value;\n}\n\n/** Coerce a data-bound value to a boolean */\nexport function coerceCssSizeBinding(value: CssSizeInput): string {\n if (value == null) {\n return '';\n }\n\n return typeof value === 'string' ? value : `${value}px`;\n}\n","import { inject, Injectable, INJECTOR, runInInjectionContext, DOCUMENT } from '@angular/core';\nimport { MATOMO_SCRIPT_FACTORY } from '../tracker/script-factory';\nimport { requireNonNull } from './coercion';\n\n@Injectable()\nexport class ScriptInjector {\n private readonly scriptFactory = inject(MATOMO_SCRIPT_FACTORY);\n private readonly injector = inject(INJECTOR);\n private readonly document = inject(DOCUMENT);\n\n injectDOMScript(scriptUrl: string): void {\n const scriptElement = runInInjectionContext(this.injector, () =>\n this.scriptFactory(scriptUrl, this.document),\n );\n const selfScript = requireNonNull(\n this.document.getElementsByTagName('script')[0],\n 'no existing script found',\n );\n const parent = requireNonNull(selfScript.parentNode, \"no script's parent node found\");\n\n parent.insertBefore(scriptElement, selfScript);\n }\n}\n","import { inject, InjectionToken } from '@angular/core';\nimport { requireNonNull } from '../utils/coercion';\n\nconst CONFIG_NOT_FOUND =\n 'No Matomo configuration found! Have you included Matomo module using MatomoModule.forRoot() or provideMatomo()?';\n\n/** Internal marker token to detect that router has been enabled */\nexport const MATOMO_ROUTER_ENABLED = new InjectionToken<boolean>('MATOMO_ROUTER_ENABLED', {\n factory() {\n return false;\n },\n});\n\n/** Injection token for {@link MatomoConfiguration} */\nexport const MATOMO_CONFIGURATION = new InjectionToken<MatomoConfiguration>('MATOMO_CONFIGURATION');\n\n/**\n * For internal use only. Injection token for {@link InternalMatomoConfiguration}\n *\n */\nexport const INTERNAL_MATOMO_CONFIGURATION = new InjectionToken<InternalMatomoConfiguration>(\n 'INTERNAL_MATOMO_CONFIGURATION',\n);\n\nexport function createInternalMatomoConfiguration(): InternalMatomoConfiguration {\n const { mode, requireConsent, ...restConfig } = requireNonNull(\n inject(MATOMO_CONFIGURATION, { optional: true }),\n CONFIG_NOT_FOUND,\n );\n\n return {\n mode: mode ? coerceInitializationMode(mode) : undefined,\n disabled: false,\n enableLinkTracking: true,\n trackAppInitialLoad: !inject(MATOMO_ROUTER_ENABLED),\n requireConsent: requireConsent ? coerceConsentRequirement(requireConsent) : 'none',\n enableJSErrorTracking: false,\n runOutsideAngularZone: false,\n disableCampaignParameters: false,\n acceptDoNotTrack: false,\n ...restConfig,\n };\n}\n\n/**\n * For internal use only. Injection token for deferred {@link InternalMatomoConfiguration}.\n *\n */\nexport const DEFERRED_INTERNAL_MATOMO_CONFIGURATION =\n new InjectionToken<DeferredInternalMatomoConfiguration>('DEFERRED_INTERNAL_MATOMO_CONFIGURATION');\n\nexport function createDeferredInternalMatomoConfiguration(): DeferredInternalMatomoConfiguration {\n const base = inject(INTERNAL_MATOMO_CONFIGURATION);\n let resolveFn: ((configuration: InternalMatomoConfiguration) => void) | undefined;\n const configuration = new Promise<InternalMatomoConfiguration>(resolve => (resolveFn = resolve));\n\n return {\n configuration,\n markReady(configuration) {\n requireNonNull(\n resolveFn,\n 'resolveFn',\n )({\n ...base,\n ...configuration,\n });\n },\n };\n}\n\n/**\n * For internal use only. Injection token for fully loaded async {@link InternalMatomoConfiguration}.\n *\n */\nexport const ASYNC_INTERNAL_MATOMO_CONFIGURATION = new InjectionToken<\n Promise<InternalMatomoConfiguration>\n>('ASYNC_INTERNAL_MATOMO_CONFIGURATION');\n\n/**\n * For internal use only. Module configuration merged with default values.\n *\n */\nexport type InternalMatomoConfiguration = Omit<MatomoConfiguration, 'mode' | 'requireConsent'> &\n Omit<Required<BaseMatomoConfiguration>, 'requireConsent'> & {\n mode?: MatomoInitializationBehavior;\n requireConsent: MatomoConsentRequirement;\n };\n\nexport interface DeferredInternalMatomoConfiguration {\n readonly configuration: Promise<InternalMatomoConfiguration>;\n\n markReady(configuration: InternalMatomoConfiguration): void;\n}\n\n/**\n * - __auto__: automatically inject matomo script using provided configuration\n * - __manual__: do not inject Matomo script. In this case, initialization script must be provided\n * - __deferred__: automatically inject matomo script when deferred tracker configuration is provided using `MatomoInitializerService.initializeTracker()`.\n */\nexport type MatomoInitializationBehavior = 'auto' | 'manual' | 'deferred';\n\n/** @deprecated Use {@link MatomoInitializationBehavior} instead */\nexport enum MatomoInitializationMode {\n /**\n * Automatically inject matomo script using provided configuration\n *\n * @deprecated Use `'auto'` instead\n */\n AUTO,\n /**\n * Do not inject Matomo script. In this case, initialization script must be provided\n *\n * @deprecated Use `'manual'` instead\n */\n MANUAL,\n /**\n * Automatically inject matomo script when deferred tracker configuration is provided using `MatomoInitializerService.initializeTracker()`.\n *\n * @deprecated Use `'deferred'` instead\n */\n AUTO_DEFERRED,\n}\n\nexport function coerceInitializationMode(\n value: MatomoInitializationBehaviorInput,\n): MatomoInitializationBehavior {\n switch (value) {\n case MatomoInitializationMode.AUTO:\n return 'auto';\n case MatomoInitializationMode.MANUAL:\n return 'manual';\n case MatomoInitializationMode.AUTO_DEFERRED:\n return 'deferred';\n default:\n return value;\n }\n}\n\nexport type MatomoConsentRequirement = 'none' | 'cookie' | 'tracking';\n\n/** @deprecated Use {@link MatomoConsentRequirement} instead */\nexport enum MatomoConsentMode {\n /** Do not require any consent, always track users */\n NONE,\n /** Require cookie consent */\n COOKIE,\n /** Require tracking consent */\n TRACKING,\n}\n\nexport function coerceConsentRequirement(\n value: MatomoConsentMode | MatomoConsentRequirement,\n): MatomoConsentRequirement {\n switch (value) {\n case MatomoConsentMode.NONE:\n return 'none';\n case MatomoConsentMode.COOKIE:\n return 'cookie';\n case MatomoConsentMode.TRACKING:\n return 'tracking';\n default:\n return value;\n }\n}\n\nexport interface MatomoTrackerConfiguration {\n /** Matomo site id */\n siteId: number | string;\n\n /** Matomo server url */\n trackerUrl: string;\n\n /** The trackerUrlSuffix is always appended to the trackerUrl. It defaults to matomo.php */\n trackerUrlSuffix?: string;\n}\n\nexport interface MultiTrackersConfiguration {\n /**\n * Configure multiple tracking servers. <b>Order matters: if no custom script url is\n * provided, Matomo script will be downloaded from first tracker.</b>\n */\n trackers: MatomoTrackerConfiguration[];\n}\n\nexport interface BaseMatomoConfiguration {\n /** Set to `true` to disable tracking */\n disabled?: boolean;\n\n /** If `true`, track a page view when app loads (default `false`) */\n trackAppInitialLoad?: boolean;\n\n /**\n * Configure link clicks tracking\n *\n * If `true` (the default value), enable link tracking, excluding middle-clicks and contextmenu events.\n * If `enable-pseudo`, enable link tracking, including middle-clicks and contextmenu events.\n * If `false`, to disable this Matomo feature (default `true`).\n *\n * Used when {@link trackAppInitialLoad} is `true` and when automatic page tracking is enabled.\n *\n * @see {@link MatomoTracker.enableLinkTracking} for more details\n */\n enableLinkTracking?: boolean | 'enable-pseudo';\n\n /** Set to `true` to not track users who opt out of tracking using <i>Do Not Track</i> setting */\n acceptDoNotTrack?: boolean;\n\n /**\n * Configure user consent requirement\n *\n * To identify whether you need to ask for any consent, you need to determine whether your lawful\n * basis for processing personal data is \"Consent\" or \"Legitimate interest\", or whether you can\n * avoid collecting personal data altogether.\n *\n * Matomo differentiates between cookie and tracking consent:\n * - In the context of <b>tracking consent</b> no cookies will be used and no tracking request\n * will be sent unless consent was given. As soon as consent was given, tracking requests will\n * be sent and cookies will be used.\n * - In the context of <b>cookie consent</b> tracking requests will always be sent. However,\n * cookies will be only used if consent for storing and using cookies was given by the user.\n *\n * Note that cookies impact reports accuracy.\n *\n * See Matomo guide: {@link https://developer.matomo.org/guides/tracking-consent}\n */\n requireConsent?: MatomoConsentRequirement | MatomoConsentMode;\n\n /** Set to `true` to enable Javascript errors tracking as <i>events</i> (with category <i>JavaScript Errors</i>) */\n enableJSErrorTracking?: boolean;\n\n /** Set to `true` to run matomo calls outside of angular NgZone. This may fix angular freezes. This has no effect in zoneless applications. */\n runOutsideAngularZone?: boolean;\n\n /**\n * Set to `true` to avoid sending campaign parameters\n *\n * By default, Matomo will send campaign parameters (mtm, utm, etc.) to the tracker and record that information.\n * Some privacy regulations may not allow for this information to be collected.\n *\n * <b>This is available as of Matomo 5.1 only.</b>\n */\n disableCampaignParameters?: boolean;\n}\n\n/**\n * Mapping type to extend input types with legacy `MatomoInitializationMode` type\n *\n * TODO remove when `MatomoInitializationMode` is removed\n */\nexport interface MatomoInitializationBehaviorInputMapping {\n manual: MatomoInitializationMode.MANUAL;\n auto: MatomoInitializationMode.AUTO;\n deferred: MatomoInitializationMode.AUTO_DEFERRED;\n}\n\n/**\n * Special type to map a `MatomoInitializationBehavior` to either itself or the legacy equivalent `MatomoInitializationMode`\n *\n * @example\n * MatomoInitializationBehaviorInput<'auto'>\n * // Equivalent to:\n * 'auto' | MatomoInitializationMode.AUTO\n *\n * MatomoInitializationBehaviorInput<'manual'>\n * // Equivalent to:\n * 'manual' | MatomoInitializationMode.MANUAL\n *\n * MatomoInitializationBehaviorInput<'deferred'>\n * // Equivalent to:\n * 'deferred' | MatomoInitializationMode.AUTO_DEFERRED\n *\n * TODO remove when `MatomoInitializationMode` is removed and use `MatomoInitializationBehavior` instead\n */\nexport type MatomoInitializationBehaviorInput<\n T extends MatomoInitializationBehavior = MatomoInitializationBehavior,\n> = T | MatomoInitializationBehaviorInputMapping[T];\n\nexport interface BaseAutoMatomoConfiguration<M extends 'auto' | 'deferred' = 'auto'> {\n /**\n * Set the script initialization mode (default is `'auto'`)\n *\n * @see MatomoInitializationBehavior\n */\n mode?: MatomoInitializationBehaviorInput<M>;\n\n /** Matomo script url (default is `matomo.js` appended to main tracker url) */\n scriptUrl: string;\n}\n\ntype Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };\ntype XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;\ntype XOR3<T, U, V> = XOR<T, XOR<U, V>>;\n\nexport type ManualMatomoConfiguration = {\n /**\n * Set the script initialization mode (default is `'auto'`)\n *\n * @see MatomoInitializationBehavior\n */\n mode: MatomoInitializationBehaviorInput<'manual'>;\n};\n\nexport type DeferredMatomoConfiguration = {\n /**\n * Set the script initialization mode (default is `'auto'`)\n *\n * @see MatomoInitializationBehavior\n */\n mode: MatomoInitializationBehaviorInput<'deferred'>;\n};\n\nexport type ExplicitAutoConfiguration<M extends 'auto' | 'deferred' = 'auto'> = Partial<\n BaseAutoMatomoConfiguration<M>\n> &\n XOR<MatomoTrackerConfiguration, MultiTrackersConfiguration>;\n\nexport type EmbeddedAutoConfiguration<M extends 'auto' | 'deferred' = 'auto'> =\n BaseAutoMatomoConfiguration<M> & Partial<MultiTrackersConfiguration>;\n\nexport type AutoMatomoConfiguration<M extends 'auto' | 'deferred' = 'auto'> = XOR<\n ExplicitAutoConfiguration<M>,\n EmbeddedAutoConfiguration<M>\n>;\n\nexport type MatomoConfiguration = BaseMatomoConfiguration &\n XOR3<AutoMatomoConfiguration, ManualMatomoConfiguration, DeferredMatomoConfiguration>;\n\nexport function isAutoConfigurationMode(\n config: MatomoConfiguration | InternalMatomoConfiguration,\n): config is AutoMatomoConfiguration {\n return (\n config.mode == null || config.mode === 'auto' || config.mode === MatomoInitializationMode.AUTO\n );\n}\n\nfunction hasMainTrackerConfiguration<M extends 'auto' | 'deferred'>(\n config: AutoMatomoConfiguration<M>,\n): config is ExplicitAutoConfiguration<M> {\n // If one is undefined, both should be\n return config.siteId != null && config.trackerUrl != null;\n}\n\nexport function isEmbeddedTrackerConfiguration<M extends 'auto' | 'deferred'>(\n config: AutoMatomoConfiguration<M>,\n): config is EmbeddedAutoConfiguration<M> {\n return config.scriptUrl != null && !hasMainTrackerConfiguration(config);\n}\n\nexport function isExplicitTrackerConfiguration<M extends 'auto' | 'deferred'>(\n config: AutoMatomoConfiguration<M>,\n): config is ExplicitAutoConfiguration<M> {\n return hasMainTrackerConfiguration(config) || isMultiTrackerConfiguration(config);\n}\n\nexport function isMultiTrackerConfiguration(\n config: AutoMatomoConfiguration<'auto' | 'deferred'>,\n): config is MultiTrackersConfiguration {\n return Array.isArray(config.trackers);\n}\n\nexport function getTrackersConfiguration(\n config: ExplicitAutoConfiguration<'auto' | 'deferred'>,\n): MatomoTrackerConfiguration[] {\n return isMultiTrackerConfiguration(config)\n ? config.trackers\n : [\n {\n trackerUrl: config.trackerUrl,\n siteId: config.siteId,\n trackerUrlSuffix: config.trackerUrlSuffix,\n },\n ];\n}\n","declare let window: MatomoHolder;\n\nexport interface MatomoHolder extends Window {\n _paq: { push: Array<unknown>['push'] };\n}\n\nexport function initializeMatomoHolder() {\n window._paq = window._paq || [];\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { inject, Injectable, NgZone, PLATFORM_ID } from '@angular/core';\nimport { initializeMatomoHolder, MatomoHolder } from '../holder';\nimport { Getters, NonEmptyArray, PrefixedType } from '../utils/types';\nimport { INTERNAL_MATOMO_CONFIGURATION } from './configuration';\n\ndeclare const window: MatomoHolder;\n\ntype ReturnType<T> = T extends (...args: any) => infer R ? R : any;\n\nfunction trimTrailingUndefinedElements<T>(array: T[]): T[] {\n const trimmed = [...array];\n\n while (trimmed.length > 0 && trimmed[trimmed.length - 1] === undefined) {\n trimmed.pop();\n }\n\n return trimmed;\n}\n\nexport type InternalMatomoTrackerType = Pick<\n InternalMatomoTracker<unknown, string>,\n 'get' | 'push' | 'pushFn'\n>;\n\nexport function createInternalMatomoTracker(): InternalMatomoTrackerType {\n const disabled = inject(INTERNAL_MATOMO_CONFIGURATION).disabled;\n const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n return disabled || !isBrowser ? new NoopMatomoTracker() : new InternalMatomoTracker();\n}\n\n@Injectable()\nexport class InternalMatomoTracker<MATOMO, PREFIX extends string = ''> {\n private readonly ngZone = inject(NgZone);\n private readonly config = inject(INTERNAL_MATOMO_CONFIGURATION);\n\n constructor() {\n initializeMatomoHolder();\n }\n\n /** Asynchronously call provided method name on matomo tracker instance */\n get<K extends Getters<PrefixedType<MATOMO, PREFIX>>>(\n getter: K extends keyof PrefixedType<MATOMO, PREFIX> ? K : never,\n ): Promise<ReturnType<PrefixedType<MATOMO, PREFIX>[K]>> {\n return this.pushFn(matomo => (matomo[getter as keyof PrefixedType<MATOMO, PREFIX>] as any)());\n }\n\n pushFn<T>(fn: (matomo: PrefixedType<MATOMO, PREFIX>) => T): Promise<T> {\n return new Promise(resolve => {\n this.push([\n function (this: PrefixedType<MATOMO, PREFIX>): void {\n resolve(fn(this));\n },\n ]);\n });\n }\n\n push(args: NonEmptyArray<unknown>): void {\n if (this.config.runOutsideAngularZone) {\n this.ngZone.runOutsideAngular(() => {\n window._paq.push(trimTrailingUndefinedElements(args));\n });\n } else {\n window._paq.push(trimTrailingUndefinedElements(args));\n }\n }\n}\n\n@Injectable()\nexport class NoopMatomoTracker<MATOMO = unknown, PREFIX extends string = ''>\n implements InternalMatomoTrackerType\n{\n /** Asynchronously call provided method name on matomo tracker instance */\n async get<K extends keyof PrefixedType<MATOMO, PREFIX>>(_: K): Promise<never> {\n return Promise.reject('MatomoTracker is disabled');\n }\n\n push(_: unknown[]): void {\n // No-op\n }\n\n async pushFn<T>(_: (matomo: PrefixedType<MATOMO, PREFIX>) => T): Promise<T> {\n return Promise.reject('MatomoTracker is disabled');\n }\n}\n","import { ApplicationInitStatus, inject, Injectable, Provider } from '@angular/core';\nimport {\n InternalMatomoTracker,\n InternalMatomoTrackerType,\n} from '../tracker/internal-matomo-tracker.service';\nimport { PrefixedType } from '../utils/types';\n\nexport function provideTestingTracker(): Provider[] {\n return [\n MatomoTestingTracker,\n {\n provide: InternalMatomoTracker,\n useExisting: MatomoTestingTracker,\n },\n ];\n}\n\n@Injectable()\nexport class MatomoTestingTracker<MATOMO = unknown, PREFIX extends string = ''>\n implements InternalMatomoTrackerType\n{\n private readonly initStatus = inject(ApplicationInitStatus);\n\n /** Get list of all calls until initialization */\n callsOnInit: unknown[][] = [];\n /** Get list of all calls after initialization */\n callsAfterInit: unknown[][] = [];\n\n /** Get a copy of all calls since application startup */\n get calls(): unknown[] {\n return [...this.callsOnInit, ...this.callsAfterInit];\n }\n\n countCallsAfterInit(command: string): number {\n return this.callsAfterInit.filter(call => call[0] === command).length;\n }\n\n reset() {\n this.callsOnInit = [];\n this.callsAfterInit = [];\n }\n\n /** Asynchronously call provided method name on matomo tracker instance */\n async get<K extends keyof PrefixedType<MATOMO, PREFIX>>(_: K): Promise<never> {\n return Promise.reject('MatomoTracker is disabled');\n }\n\n push(arg: unknown[]): void {\n if (this.initStatus.done) {\n this.callsAfterInit.push(arg);\n } else {\n this.callsOnInit.push(arg);\n }\n }\n\n async pushFn<T>(_: (matomo: PrefixedType<MATOMO, PREFIX>) => T): Promise<T> {\n return Promise.reject('MatomoTracker is disabled');\n }\n}\n","export const ALREADY_INJECTED_ERROR = 'Matomo trackers have already been initialized';\nexport const ALREADY_INITIALIZED_ERROR = 'Matomo has already been initialized';\n","import { DestroyRef, inject, Injectable } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { NonEmptyReadonlyArray, RequireAtLeastOne } from '../utils/types';\nimport { InternalMatomoTracker } from './internal-matomo-tracker.service';\n\nexport interface MatomoECommerceItem {\n productSKU: string;\n productName?: string;\n productCategory?: string;\n price?: number;\n quantity?: number;\n}\n\nexport type MatomoECommerceItemView = Omit<MatomoECommerceItem, 'quantity'>;\nexport type MatomoECommerceCategoryView = Required<Pick<MatomoECommerceItem, 'productCategory'>>;\nexport type MatomoECommerceView = MatomoECommerceItemView | MatomoECommerceCategoryView;\n\nexport type PagePerformanceTimings = RequireAtLeastOne<{\n networkTimeInMs?: number;\n serverTimeInMs?: number;\n transferTimeInMs?: number;\n domProcessingTimeInMs?: number;\n domCompletionTimeInMs?: number;\n onloadTimeInMs?: number;\n}>;\n\nfunction isECommerceCategoryView(\n param: string | MatomoECommerceView,\n): param is MatomoECommerceCategoryView {\n return (\n typeof param === 'object' && Object.keys(param).length === 1 && param.productCategory != null\n );\n}\n\nfunction isECommerceItemView(\n param: string | MatomoECommerceView,\n): param is MatomoECommerceItemView {\n return typeof param === 'object' && 'productSKU' in param;\n}\n\n/** Matomo's internal tracker instance */\nexport interface MatomoInstance {\n getMatomoUrl(): string;\n\n getPiwikUrl(): string;\n\n getCurrentUrl(): string;\n\n getLinkTrackingTimer(): number;\n\n getVisitorId(): string;\n\n // see https://github.com/matomo-org/matomo/blob/3cee577117ad92db739dd3f22571520207eca02e/js/piwik.js#L3306\n getVisitorInfo(): unknown[];\n\n /**\n *\n * @return array of getAttributionCampaignName, getAttributionCampaignKeyword, getAttributionReferrerTimestamp, getAttributionReferrerUrl\n */\n getAttributionInfo(): string[];\n\n getAttributionCampaignName(): string;\n\n getAttributionCampaignKeyword(): string;\n\n getAttributionReferrerTimestamp(): string;\n\n getAttributionReferrerUrl(): string;\n\n getUserId(): string;\n\n getPageViewId(): string;\n\n getCustomData(): unknown;\n\n getCustomVariable(index: number, scope: string): string;\n\n getCustomDimension(customDimensionId: number): string;\n\n getEcommerceItems(): MatomoECommerceItem[];\n\n hasCookies(): boolean;\n\n getCrossDomainLinkingUrlParameter(): string;\n\n hasRememberedConsent(): boolean;\n\n getRememberedConsent(): number | string;\n\n getRememberedCookieConsent(): number | string;\n\n isConsentRequired(): boolean;\n\n areCookiesEnabled(): boolean;\n\n isUserOptedOut(): boolean;\n\n getCustomPagePerformanceTiming(): string;\n\n getExcludedReferrers(): string[];\n\n getIgnoreCampaignsForReferrers(): string[];\n}\n\n@Injectable()\nexport class MatomoTracker {\n private readonly delegate: InternalMatomoTracker<MatomoInstance> = inject(InternalMatomoTracker);\n\n private readonly _pageViewTracked = new Subject<void>();\n\n readonly pageViewTracked = this._pageViewTracked.asObservable();\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this._pageViewTracked.complete());\n }\n\n /**\n * Logs a visit to this page.\n *\n * @param [customTitle] Optional title of the visited page.\n */\n trackPageView(customTitle?: string): void {\n this.delegate.push(['trackPageView', customTitle]);\n this._pageViewTracked.next();\n }\n\n /**\n * Logs an event with an event category (Videos, Music, Games…), an event action (Play, Pause, Duration,\n * Add Playlist, Downloaded, Clicked…), and an optional event name and optional numeric value.\n *\n * @param category Category of the event.\n * @param action Action of the event.\n * @param [name] Optional name of the event.\n * @param [value] Optional value for the event.\n * @param [customData] Optional custom data for the event.\n */\n trackEvent(\n category: string,\n action: string,\n name?: string,\n value?: number,\n customData?: unknown,\n ): void {\n this.delegate.push(['trackEvent', category, action, name, value, customData]);\n }\n\n /**\n * Logs an internal site search for a specific keyword, in an optional category,\n * specifying the optional count of search results in the page.\n *\n * @param keyword Keywords of the search query.\n * @param [category] Optional category of the search query.\n * @param [resultsCount] Optional number of results returned by the search query.\n * @param [customData] Optional custom data for the search query.\n */\n trackSiteSearch(\n keyword: string,\n category?: string,\n resultsCount?: number,\n customData?: unknown,\n ): void {\n this.delegate.push(['trackSiteSearch', keyword, category, resultsCount, customData]);\n }\n\n /**\n * Manually logs a conversion for the numeric goal ID, with an optional numeric custom revenue customRevenue.\n *\n * @param idGoal numeric ID of the goal to log a conversion for.\n * @param [customRevenue] Optional custom revenue to log for the goal.\n * @param [customData] Optional custom data for the goal.\n */\n trackGoal(idGoal: number, customRevenue?: number, customData?: unknown): void {\n this.delegate.push(['trackGoal', idGoal, customRevenue, customData]);\n }\n\n /**\n * Manually logs a click from your own code.\n *\n * @param url Full URL which is to be tracked as a click.\n * @param linkType Either 'link' for an outlink or 'download' for a download.\n * @param [customData] Optional custom data for the link.\n */\n trackLink(url: string, linkType: 'link' | 'download', customData?: unknown): void {\n this.delegate.push(['trackLink', url, linkType, customData]);\n }\n\n /**\n * Scans the entire DOM for all content blocks and tracks all impressions once the DOM ready event has been triggered.\n *\n */\n trackAllContentImpressions(): void {\n this.delegate.push(['trackAllContentImpressions']);\n }\n\n /**\n * Scans the entire DOM for all content blocks as soon as the page is loaded.<br />\n * It tracks an impression only if a content block is actually visible.\n *\n * @param checkOnScroll If true, checks for new content blocks while scrolling the page.\n * @param timeInterval Duration, in milliseconds, between two checks upon scroll.\n */\n trackVisibleContentImpressions(checkOnScroll: boolean, timeInterval: number): void {\n this.delegate.push(['trackVisibleContentImpressions', checkOnScroll, timeInterval]);\n }\n\n /**\n * Scans the given DOM node and its children for content blocks and tracks an impression for them\n * if no impression was already tracked for it.\n *\n * @param node DOM node in which to look for content blocks which have not been previously tracked.\n */\n trackContentImpressionsWithinNode(node: Node): void {\n this.delegate.push(['trackContentImpressionsWithinNode', node]);\n }\n\n /**\n * Tracks an interaction with the given DOM node/content block.\n *\n * @param node DOM node for which to track a content interaction.\n * @param contentInteraction Name of the content interaction.\n */\n trackContentInteractionNode(node: Node, contentInteraction: string): void {\n this.delegate.push(['trackContentInteractionNode', node, contentInteraction]);\n }\n\n /**\n * Tracks a content impression using the specified values.\n *\n * @param contentName Content name.\n * @param contentPiece Content piece.\n * @param contentTarget Content target.\n */\n trackContentImpression(contentName: string, contentPiece: string, contentTarget: string): void {\n this.delegate.push(['trackContentImpression', contentName, contentPiece, contentTarget]);\n }\n\n /**\n * Tracks a content interaction using the specified values.\n *\n * @param contentInteraction Content interaction.\n * @param contentName Content name.\n * @param contentPiece Content piece.\n * @param contentTarget Content target.\n */\n trackContentInteraction(\n contentInteraction: string,\n contentName: string,\n contentPiece: string,\n contentTarget: string,\n ): void {\n this.delegate.push([\n 'trackContentInteraction',\n contentInteraction,\n contentName,\n contentPiece,\n contentTarget,\n ]);\n }\n\n /**\n * Logs all found content blocks within a page to the console. This is useful to debug / test content tracking.\n */\n logAllContentBlocksOnPage(): void {\n this.delegate.push(['logAllContentBlocksOnPage']);\n }\n\n /**\n * Send a ping request\n * <p>\n * Ping requests do not track new actions.\n * If they are sent within the standard visit length, they will update the existing visit time.\n * If sent after the standard visit length, ping requests will be ignored.\n * See also {@link #enableHeartBeatTimer enableHeartBeatTimer()}.\n *\n * @see enableHeartBeatTimer\n */\n ping(): void {\n this.delegate.push(['ping']);\n }\n\n /**\n * Install a Heart beat timer that will regularly send requests to Matomo in order to better measure the time spent on the page.<br />\n * These requests will be sent only when the user is actively viewing the page (when the tab is active and in focus).<br />\n * These requests will not track additional actions or page views.<br />\n * By default, the delay is set to 15 seconds.\n *\n * @param delay Delay, in seconds, between two heart beats to the server.\n */\n enableHeartBeatTimer(delay: number): void {\n this.delegate.push(['enableHeartBeatTimer', delay]);\n }\n\n /**\n * Installs link tracking on all applicable link elements.\n *\n * @param usePseudoClickHandler Set to `true` to use pseudo click-handler (treat middle click and open contextmenu as\n * left click).<br />\n * A right click (or any click that opens the context menu) on a link will be tracked as clicked even if \"Open in new tab\"\n * is not selected.<br />\n * If \"false\" (default), nothing will be tracked on open context menu or middle click.\n */\n enableLinkTracking(usePseudoClickHandler = false): void {\n this.delegate.push(['enableLinkTracking', usePseudoClickHandler]);\n }\n\n /** Disables page performance tracking */\n disablePerformanceTracking(): void {\n this.delegate.push(['disablePerformanceTracking']);\n }\n\n /**\n * Enables cross domain linking. By default, the visitor ID that identifies a unique visitor is stored in the browser's\n * first party cookies.<br />\n * This means the cookie can only be accessed by pages on the same domain.<br />\n * If you own multiple domains and would like to track all the actions and pageviews of a specific visitor into the same visit,\n * you may enable cross domain linking.<br />\n * Whenever a user clicks on a link it will append a URL parameter pk_vid to the clicked URL which forwards the current\n * visitor ID value to the page of the different domain.\n *\n */\n enableCrossDomainLinking(): void {\n this.delegate.push(['enableCrossDomainLinking']);\n }\n\n /**\n * By default, the two visits across domains will be linked together when the link is clicked and the page is loaded within\n * a 180 seconds timeout window.\n *\n * @param timeout Timeout, in seconds, between two actions across two domains before creating a new visit.\n */\n setCrossDomainLinkingTimeout(timeout: number): void {\n this.delegate.push(['setCrossDomainLinkingTimeout', timeout]);\n }\n\n /**\n * Get the query parameter to append to links to handle cross domain linking.\n *\n * Use this to add cross domain support for links that are added to the DOM dynamically\n */\n getCrossDomainLinkingUrlParameter(): Promise<string> {\n return this.delegate.get('getCrossDomainLinkingUrlParameter');\n }\n\n /**\n * Set array of referrers where campaign parameters should be ignored\n */\n setIgnoreCampaignsForReferrers(referrers: string | string[]): void {\n this.delegate.push(['setIgnoreCampaignsForReferrers', referrers]);\n }\n\n /**\n * Get array of referrers where campaign parameters should be ignored\n */\n getIgnoreCampaignsForReferrers(): Promise<string[]> {\n return this.delegate.get('getIgnoreCampaignsForReferrers');\n }\n\n /**\n * Overrides document.title\n *\n * @param title Title of the document.\n */\n setDocumentTitle(title: string): void {\n this.delegate.push(['setDocumentTitle', title]);\n }\n\n /**\n * Set array of hostnames or domains to be treated as local.<br />\n * For wildcard subdomains, you can use: `setDomains('.example.com')`; or `setDomains('*.example.com');`.<br />\n * You can also specify a path along a domain: `setDomains('*.example.com/subsite1');`.\n *\n * @param domains List of hostnames or domains, with or without path, to be treated as local.\n */\n setDomains(domains: string[]): void {\n this.delegate.push(['setDomains', domains]);\n }\n\n /**\n * Override the page's reported URL.\n *\n * @param url URL to be reported for the page.\n */\n setCustomUrl(url: string): void {\n this.delegate.push(['setCustomUrl', url]);\n }\n\n /**\n * Overrides the detected Http-Referer.\n *\n * @param url URL to be reported for the referer.\n */\n setReferrerUrl(url: string): void {\n this.delegate.push(['setReferrerUrl', url]);\n }\n\n /**\n * Specifies the website ID.<br />\n * Redundant: can be specified in getTracker() constructor.\n *\n * @param siteId Site ID for the tracker.\n */\n setSiteId(siteId: number | string): void {\n this.delegate.push(['setSiteId', siteId]);\n }\n\n /**\n * Specify the Matomo HTTP API URL endpoint. Points to the root directory of matomo,\n * e.g. http://matomo.example.org/ or https://example.org/matomo/.<br />\n * This function is only useful when the 'Overlay' report is not working.<br />\n * By default, you do not need to use this function.\n *\n * @param url URL for Matomo HTTP API endpoint.\n */\n setApiUrl(url: string): void {\n this.delegate.push(['setApiUrl', url]);\n }\n\n /**\n * Specifies the Matomo server URL.<br />\n * Redundant: can be specified in getTracker() constructor.\n *\n * @param url URL for the Matomo server.\n */\n setTrackerUrl(url: string): void {\n this.delegate.push(['setTrackerUrl', url]);\n }\n\n /**\n * Register an additional Matomo server<br />\n * Redundant: can be specified in getTracker() constructor.\n *\n * @param url URL for the Matomo server.\n * @param siteId Site ID for the tracker\n */\n addTracker(url: string, siteId: number | string): void {\n this.delegate.push(['addTracker', url, siteId]);\n }\n\n /**\n * Returns the Matomo server URL.\n *\n * @returns Promise for the Matomo server URL.\n */\n getMatomoUrl(): Promise<string> {\n return this.delegate.get('getMatomoUrl');\n }\n\n /** @deprecated use `getMatomoUrl` instead */\n getPiwikUrl(): Promise<string> {\n return this.delegate.get('getPiwikUrl');\n }\n\n /**\n * Returns the current url of the page that is currently being visited.<br />\n * If a custom URL was set before calling this method, the custom URL will be returned.\n *\n * @returns Promise for the URL of the current page.\n */\n getCurrentUrl(): Promise<string> {\n return this.delegate.get('getCurrentUrl');\n }\n\n /**\n * Set classes to be treated as downloads (in addition to piwik_download).\n *\n * @param classes Class, or list of classes to be treated as downloads.\n */\n setDownloadClasses(classes: string | string[]): void {\n this.delegate.push(['setDownloadClasses', classes]);\n }\n\n /**\n * Set list of file extensions to be recognized as downloads.<br />\n * Example: `'docx'` or `['docx', 'xlsx']`.\n *\n * @param extensions Extension, or list of extensions to be recognized as downloads.\n */\n setDownloadExtensions(extensions: string | string[]): void {\n this.delegate.push(['setDownloadExtensions', extensions]);\n }\n\n /**\n * Set additional file extensions to be recognized as downloads.<br />\n * Example: `'docx'` or `['docx', 'xlsx']`.\n *\n * @param extensions Extension, or list of extensions to be recognized as downloads.\n */\n addDownloadExtensions(extensions: string | string[]): void {\n this.delegate.push(['addDownloadExtensions', extensions]);\n }\n\n /**\n * Set file extensions to be removed from the list of download file extensions.<br />\n * Example: `'docx'` or `['docx', 'xlsx']`.\n *\n * @param extensions Extension, or list of extensions not to be recognized as downloads.\n */\n removeDownloadExtensions(extensions: string | string[]): void {\n this.delegate.push(['removeDownloadExtensions', extensions]);\n }\n\n /**\n * Set classes to be ignored if present in link (in addition to piwik_ignore).\n *\n * @param classes Class, or list of classes to be ignored if present in link.\n */\n setIgnoreClasses(classes: string | string[]): void {\n this.delegate.push(['setIgnoreClasses', classes]);\n }\n\n /**\n * Set classes to be treated as outlinks (in addition to piwik_link).\n *\n * @param classes Class, or list of classes to be treated as outlinks.\n */\n setLinkClasses(classes: string | string[]): void {\n this.delegate.push(['setLinkClasses', classes]);\n }\n\n /**\n * Set delay for link tracking (in milliseconds).\n *\n * @param delay Delay, in milliseconds, for link tracking.\n */\n setLinkTrackingTimer(delay: number): void {\n this.delegate.push(['setLinkTrackingTimer', delay]);\n }\n\n /**\n * Returns delay for link tracking.\n *\n * @returns Promise for the delay in milliseconds.\n */\n getLinkTrackingTimer(): Promise<number> {\n return this.delegate.get('getLinkTrackingTimer');\n }\n\n /**\n * Set to true to not record the hash tag (anchor) portion of URLs.\n *\n * @param value If true, the hash tag portion of the URLs won't be recorded.\n */\n discardHashTag(value: boolean): void {\n this.delegate.push(['discardHashTag', value]);\n }\n\n /**\n * By default, Matomo uses the browser DOM Timing API to accurately determine the time it takes to generate and download\n * the page. You may overwrite this value with this function.\n *\n * <b>This feature has been deprecated since Matomo 4. Any call will be ignored with Matomo 4. Use {@link setPagePerformanceTiming setPagePerformanceTiming()} instead.</b>\n *\n * @param generationTime Time, in milliseconds, of the page generation.\n */\n setGenerationTimeMs(generationTime: number): void {\n this.delegate.push(['setGenerationTimeMs', generationTime]);\n }\n\n /**\n * Manually set performance metrics in milliseconds in a Single Page App or when Matomo cannot detect some metrics.\n *\n * You can set parameters to undefined if you do not want to track this metric. At least one parameter needs to be set.\n * The set performance timings will be tracked only on the next page view. If you track another page view then you will need to set the performance timings again.\n *\n * <b>Requires Matomo 4.5 or newer.</b>\n *\n */\n setPagePerformanceTiming(timings: PagePerformanceTimings): void;\n /**\n * Manually set performance metrics in milliseconds in a Single Page App or when Matomo cannot detect some metrics.\n *\n * You can set parameters to undefined if you do not want to track this metric. At least one parameter needs to be set.\n * The set performance timings will be tracked only on the next page view. If you track another page view then you will need to set the performance timings again.\n *\n * <b>Requires Matomo 4.5 or newer.</b>\n *\n */\n setPagePerformanceTiming(\n networkTimeInMs: number | undefined,\n serverTimeInMs?: number,\n transferTimeInMs?: number,\n domProcessingTimeInMs?: number,\n domCompletionTimeInMs?: number,\n onloadTimeInMs?: number,\n ): void;\n setPagePerformanceTiming(\n networkTimeInMsOrTimings: PagePerformanceTimings | number | undefined,\n serverTimeInMs?: number,\n transferTimeInMs?: number,\n domProcessingTimeInMs?: number,\n domCompletionTimeInMs?: number,\n onloadTimeInMs?: number,\n ): void {\n let networkTimeInMs: number | undefined;\n\n if (typeof networkTimeInMsOrTimings === 'object' && !!networkTimeInMsOrTimings) {\n networkTimeInMs = networkTimeInMsOrTimings.networkTimeInMs;\n serverTimeInMs = networkTimeInMsOrTimings.serverTimeInMs;\n transferTimeInMs = networkTimeInMsOrTimings.transferTimeInMs;\n domProcessingTimeInMs = networkTimeInMsOrTimings.domProcessingTimeInMs;\n domCompletionTimeInMs = networkTimeInMsOrTimings.domCompletionTimeInMs;\n onloadTimeInMs = networkTimeInMsOrTimings.onloadTimeInMs;\n } else {\n networkTimeInMs = networkTimeInMsOrTimings;\n }\n\n this.delegate.push([\n 'setPagePerformanceTiming',\n networkTimeInMs,\n serverTimeInMs,\n transferTimeInMs,\n domProcessingTimeInMs,\n domCompletionTimeInMs,\n onloadTimeInMs,\n ]);\n }\n\n getCustomPagePerformanceTiming(): Promise<string> {\n return this.delegate.get('getCustomPagePerformanceTiming');\n }\n\n /**\n * Appends a custom string to the end of the HTTP request to matomo.php.\n *\n * @param appendToUrl String to append to the end of the HTTP request to matomo.php.\n */\n appendToTrackingUrl(appendToUrl: string): void {\n this.delegate.push(['appendToTrackingUrl', appendToUrl]);\n }\n\n /** Set to `true` to not track users who opt out of tracking using <i>Do Not Track</i> setting */\n setDoNotTrack(doNotTrack: boolean): void {\n this.delegate.push(['setDoNotTrack', doNotTrack]);\n }\n\n /**\n * Enables a frame-buster to prevent the tracked web page from being framed/iframed.\n */\n killFrame(): void {\n this.delegate.push(['killFrame']);\n }\n\n /**\n * Forces the browser to load the live URL if the tracked web page is loaded from a local file\n * (e.g., saved to someone's desktop).\n *\n * @param url URL to track instead of file:// URLs.\n */\n redirectFile(url: string): void {\n this.delegate.push(['redirectFile', url]);\n }\n\n /**\n * Records how long the page has been viewed if the minimumVisitLength is attained;\n * the heartBeatDelay determines how frequently to update the server.\n *\n * @param minimumVisitLength Duration before notifying the server for the duration of the visit to a page.\n * @param heartBeatDelay Delay, in seconds, between two updates to the server.\n */\n setHeartBeatTimer(minimumVisitLength: number, heartBeatDelay: number): void {\n this.delegate.push(['setHeartBeatTimer', minimumVisitLength, heartBeatDelay]);\n }\n\n /**\n * Returns the 16 characters ID for the visitor.\n *\n * @returns Promise for the the 16 characters ID for the visitor.\n */\n getVisitorId(): Promise<string> {\n return this.delegate.get('getVisitorId');\n }\n\n /**\n * Set the 16 characters ID for the visitor\n * <p/>\n * The visitorId needs to be a 16 digit hex string.\n * It won't be persisted in a cookie and needs to be set on every new page load.\n *\n * @param visitorId a 16 digit hex string\n */\n setVisitorId(visitorId: string): void {\n this.delegate.push(['setVisitorId', visitorId]);\n }\n\n /**\n * Returns the visitor cookie contents in an array.\n *\n * @returns Promise for the cookie contents in an array.\n *\n * TODO better return type\n */\n getVisitorInfo(): Promise<unknown[]> {\n return this.delegate.get('getVisitorInfo');\n }\n\n /**\n * Returns the visitor attribution array (Referer information and/or Campaign name & keyword).<br />\n * Attribution information is used by Matomo to credit the correct referrer (first or last referrer)\n * used when a user triggers a goal conversion.\n *\n * @returns Promise for the visitor attribution array (Referer information and/or Campaign name & keyword).\n */\n getAttributionInfo(): Promise<string[]> {\n return this.delegate.get('getAttributionInfo');\n }\n\n /**\n * Returns the attribution campaign name.\n *\n * @returns Promise for the the attribution campaign name.\n */\n getAttributionCampaignName(): Promise<string> {\n return this.delegate.get('getAttributionCampaignName');\n }\n\n /**\n * Returns the attribution campaign keyword.\n *\n * @returns Promise for the attribution campaign keyword.\n */\n getAttributionCampaignKeyword(): Promise<string> {\n return this.delegate.get('getAttributionCampaignKeyword');\n }\n\n /**\n * Returns the attribution referrer timestamp.\n *\n * @returns Promise for the attribution referrer timestamp (as string).\n */\n getAttributionReferrerTimestamp(): Promise<string> {\n return this.delegate.get('getAttributionReferrerTimestamp');\n }\n\n /**\n * Returns the attribution referrer URL.\n *\n * @returns Promise for the attribution referrer URL\n */\n getAttributionReferrerUrl(): Promise<string> {\n return this.delegate.get('getAttributionReferrerUrl');\n }\n\n /**\n * Returns the User ID string if it was set.\n *\n * @returns Promise for the User ID for the visitor.\n */\n getUserId(): Promise<string> {\n return this.delegate.get('getUserId');\n }\n\n /**\n * Set a User ID to this user (such as an email address or a username).\n *\n * @param userId User ID to set for the current visitor.\n */\n setUserId(userId: string): void {\n this.delegate.push(['setUserId', userId]);\n }\n\n /**\n * Reset the User ID which also generates a new Visitor ID.\n *\n */\n resetUserId(): void {\n this.delegate.push(['resetUserId']);\n }\n\n /**\n * Override PageView id for every use of logPageView() <b>THIS SHOULD PROBABLY NOT BE CALLED IN A SINGLE-PAGE APP!</b>\n *\n * Do not use this if you call trackPageView() multiple times during tracking (e.g. when tracking a single page application)\n *\n * @param pageView\n */\n setPageViewId(pageView: string): void {\n this.delegate.push(['setPageViewId', pageView]);\n }\n\n /**\n * Returns the PageView id. If not set manually using setPageViewId, this method will return the dynamic PageView id, used in the last tracked page view, or undefined if no page view was tracked yet\n */\n getPageViewId(): Promise<string> {\n return this.delegate.get('getPageViewId');\n }\n\n /**\n * Set custom data for the next request\n *\n * @param key\n * @param value\n */\n setCustomData(key: PropertyKey, value: unknown): void;\n /**\n * Overwrite custom data for the next request\n *\n * @param data\n */\n setCustomData(data: unknown): void;\n setCustomData(...args: unknown[]): void {\n this.delegate.push(['setCustomData', ...args]);\n }\n\n /**\n * Retrieves custom data.\n *\n * @returns Promise for the value of custom data.\n */\n getCustomData(): Promise<unknown> {\n return this.delegate.get('getCustomData'