@dotcms/events 0.0.0-stage → 26.10.5-1-next.2827

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +516 -2
  2. package/decision.cjs.js +108 -0
  3. package/decision.esm.js +92 -0
  4. package/index.cjs.default.js +1 -0
  5. package/index.cjs.js +3674 -0
  6. package/index.cjs.mjs +2 -0
  7. package/index.d.ts +1 -0
  8. package/index.esm.js +3672 -0
  9. package/markup.cjs.default.js +1 -0
  10. package/markup.cjs.js +161 -0
  11. package/markup.cjs.mjs +2 -0
  12. package/markup.d.ts +1 -0
  13. package/markup.esm.js +159 -0
  14. package/package.json +77 -4
  15. package/react.cjs.default.js +1 -0
  16. package/react.cjs.js +39 -0
  17. package/react.cjs.mjs +2 -0
  18. package/react.d.ts +1 -0
  19. package/react.esm.js +37 -0
  20. package/src/index.d.ts +8 -0
  21. package/src/lib/clicks/constants.d.ts +14 -0
  22. package/src/lib/clicks/plugin.d.ts +35 -0
  23. package/src/lib/clicks/tracker.d.ts +42 -0
  24. package/src/lib/clicks/utils.d.ts +13 -0
  25. package/src/lib/contentlets/constants.d.ts +36 -0
  26. package/src/lib/contentlets/utils.d.ts +64 -0
  27. package/src/lib/contentlets/viewport.d.ts +23 -0
  28. package/src/lib/events.d.ts +17 -0
  29. package/src/lib/experiments/api.d.ts +9 -0
  30. package/src/lib/experiments/boot.d.ts +53 -0
  31. package/src/lib/experiments/constants.d.ts +46 -0
  32. package/src/lib/experiments/decision.d.ts +53 -0
  33. package/src/lib/experiments/dom.d.ts +62 -0
  34. package/src/lib/experiments/engine.d.ts +66 -0
  35. package/src/lib/experiments/markup.d.ts +17 -0
  36. package/src/lib/experiments/models.d.ts +62 -0
  37. package/src/lib/experiments/plugin.d.ts +27 -0
  38. package/src/lib/experiments/store.d.ts +54 -0
  39. package/src/lib/impressions/constants.d.ts +28 -0
  40. package/src/lib/impressions/plugin.d.ts +38 -0
  41. package/src/lib/impressions/tracker.d.ts +67 -0
  42. package/src/lib/impressions/utils.d.ts +10 -0
  43. package/src/lib/models.d.ts +130 -0
  44. package/src/lib/pipeline/constants.d.ts +88 -0
  45. package/src/lib/pipeline/enricher/plugin.d.ts +32 -0
  46. package/src/lib/pipeline/identity/activity.d.ts +19 -0
  47. package/src/lib/pipeline/identity/plugin.d.ts +60 -0
  48. package/src/lib/pipeline/logger.d.ts +74 -0
  49. package/src/lib/pipeline/models.d.ts +588 -0
  50. package/src/lib/pipeline/navigation.d.ts +21 -0
  51. package/src/lib/pipeline/sender/http.d.ts +9 -0
  52. package/src/lib/pipeline/sender/plugin.d.ts +47 -0
  53. package/src/lib/pipeline/sender/queue.d.ts +33 -0
  54. package/src/lib/pipeline/utils.d.ts +119 -0
  55. package/src/lib/react/DotCMSExperiment.d.ts +36 -0
  56. package/src/markup.d.ts +7 -0
  57. package/src/react.d.ts +8 -0
@@ -0,0 +1,64 @@
1
+ import type { ContentletData, PipelineConfig } from '../pipeline/models';
2
+ import type { AnalyticsPlugin } from 'analytics';
3
+ /**
4
+ * Creates a throttled version of a callback function
5
+ * Ensures the callback is executed at most once every `limitMs` milliseconds
6
+ * @param callback - The function to throttle
7
+ * @param limitMs - The time limit in milliseconds
8
+ * @param options - `trailing`: instead of dropping the calls made inside the window, run the
9
+ * last one when the window ends
10
+ * @returns A throttled function
11
+ */
12
+ export declare function createThrottle<T extends (...args: unknown[]) => void>(callback: T, limitMs: number, options?: {
13
+ trailing?: boolean;
14
+ }): (...args: Parameters<T>) => void;
15
+ /**
16
+ * Extracts the contentlet identifier from a DOM element
17
+ * @param element - The HTML element containing data attributes
18
+ * @returns The contentlet identifier or null if not found
19
+ */
20
+ export declare function extractContentletIdentifier(element: HTMLElement): string | null;
21
+ /**
22
+ * Extracts all contentlet data from a DOM element's data attributes
23
+ * @param element - The HTML element containing data attributes
24
+ * @returns Complete contentlet data object
25
+ */
26
+ export declare function extractContentletData(element: HTMLElement): ContentletData;
27
+ /**
28
+ * Initial scan delay for DOM readiness
29
+ * Allows React/Next.js to finish rendering before scanning for contentlets
30
+ */
31
+ export declare const INITIAL_SCAN_DELAY_MS = 100;
32
+ /**
33
+ * Finds all contentlet elements in the DOM
34
+ * @returns Array of contentlet HTMLElements
35
+ */
36
+ export declare const findContentlets: () => HTMLElement[];
37
+ /**
38
+ * Creates a MutationObserver that watches for contentlet changes in the DOM
39
+ * @param callback - Function to call when mutations are detected
40
+ * @param debounceMs - Debounce time in milliseconds (default: 250ms)
41
+ * @param options - `identifiers`: also call back when a contentlet receives its
42
+ * `data-dot-identifier`. The renderers print it after hydration, usually within
43
+ * milliseconds of the contentlet itself, so calls inside the window are not dropped: the
44
+ * last one runs when it ends.
45
+ * @returns Configured and active MutationObserver
46
+ */
47
+ export declare const createContentletObserver: (callback: () => void, debounceMs?: number, options?: {
48
+ identifiers?: boolean;
49
+ }) => MutationObserver;
50
+ /**
51
+ * Cleans up a content tracker when the browser discards the page. A page kept in the
52
+ * back/forward cache keeps its tracker, which works again when the visitor goes back to it.
53
+ * @param cleanup - Function to call when the page is discarded
54
+ */
55
+ export declare const setupPluginCleanup: (cleanup: () => void) => void;
56
+ /**
57
+ * Gets enhanced tracking plugins based on configuration
58
+ * Returns content impression and click tracking plugins if enabled
59
+ * @param config - Analytics configuration
60
+ * @param impressionPlugin - Impression tracking plugin factory
61
+ * @param clickPlugin - Click tracking plugin factory
62
+ * @returns Array of enabled tracking plugins
63
+ */
64
+ export declare const getEnhancedTrackingPlugins: (config: PipelineConfig, impressionPlugin: (config: PipelineConfig) => AnalyticsPlugin, clickPlugin: (config: PipelineConfig) => AnalyticsPlugin) => AnalyticsPlugin[];
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Where a contentlet sits in the viewport, which both content trackers measure.
3
+ * Pure functions without class dependencies for better testability and reusability
4
+ */
5
+ import type { ViewportMetrics } from '../pipeline/models';
6
+ /**
7
+ * Calculates the visibility ratio of an element in the viewport
8
+ * @param element - The HTML element to check
9
+ * @returns A number between 0 and 1 representing the visible percentage
10
+ */
11
+ export declare function calculateElementVisibilityRatio(element: HTMLElement): number;
12
+ /**
13
+ * Calculates the offset percentage of an element from the top of the viewport
14
+ * @param element - The HTML element to check
15
+ * @returns Percentage value (can be negative if above viewport)
16
+ */
17
+ export declare function calculateViewportOffset(element: HTMLElement): number;
18
+ /**
19
+ * Gets comprehensive viewport metrics for an element
20
+ * @param element - The HTML element to analyze
21
+ * @returns Object containing offset percentage and visibility ratio
22
+ */
23
+ export declare function getViewportMetrics(element: HTMLElement): ViewportMetrics;
@@ -0,0 +1,17 @@
1
+ import type { ExperimentPages } from './experiments/engine';
2
+ import type { DotCMSEvents, DotCMSEventsConfig } from './models';
3
+ /**
4
+ * Starts the SDK: what `dotEvents.init` does, with how the pages mark their experiment. Apps call
5
+ * `dotEvents.init`, for pages that print the experiment markup; the script dotCMS injects into
6
+ * traditional pages calls this with `contentlets`, since those pages carry dotCMS's contentlet
7
+ * wrappers instead. Not exported by the package.
8
+ *
9
+ * @param config - The app's config
10
+ * @param pages - How the pages mark what their experiment varies
11
+ */
12
+ export declare const initEvents: (config: DotCMSEventsConfig, pages: ExperimentPages) => void;
13
+ /**
14
+ * The events SDK. There is one object per page: every import gets the same instance,
15
+ * configured by the one `init` call.
16
+ */
17
+ export declare const dotEvents: DotCMSEvents;
@@ -0,0 +1,9 @@
1
+ import type { IsUserIncludedResult } from './models';
2
+ /**
3
+ * Asks dotCMS which running experiments the visitor is in.
4
+ *
5
+ * @param dotcmsUrl - The dotCMS origin, without a trailing slash
6
+ * @param exclude - Experiments already evaluated, so the server skips them
7
+ * @returns `ok` with the entity, `disabled` on a 403 (Experiments off), or `failed`
8
+ */
9
+ export declare const fetchIsUserIncluded: (dotcmsUrl: string, exclude: string[]) => Promise<IsUserIncludedResult>;
@@ -0,0 +1,53 @@
1
+ import { decideVariant } from './decision';
2
+ /**
3
+ * What the boot script needs, embedded as JSON because the function runs detached.
4
+ */
5
+ export interface ExperimentBootConfig {
6
+ id: string;
7
+ rendered: string;
8
+ assignmentsKey: string;
9
+ disabledUntilKey: string;
10
+ bootKey: string;
11
+ styleId: string;
12
+ revealRule: string;
13
+ param: string;
14
+ defaultVariant: string;
15
+ quietMs: number;
16
+ }
17
+ /**
18
+ * Applies, while the HTML is parsed and before the app's scripts load, what `decide` says from
19
+ * what the engine stored on an earlier page: it shows the content, or stops the page and
20
+ * replaces it with the assigned variant. It never calls dotCMS: when nothing is stored, the
21
+ * content stays hidden and the engine decides once it loads.
22
+ *
23
+ * It is printed into the page with `Function.prototype.toString`, next to `decideVariant`, so
24
+ * it must not reference anything outside itself: the decision arrives as `decide`, and every
25
+ * value in `config`.
26
+ *
27
+ * @param decide - `decideVariant`, the decision the engine makes too
28
+ * @param config - The page's experiment and the shared names
29
+ * @param w - The window, a parameter so tests can pass their own
30
+ */
31
+ export declare function dotcmsExperimentBoot(decide: typeof decideVariant, config: ExperimentBootConfig, w?: Window): void;
32
+ /** The page's experiment, as the markup passes it to `buildExperimentBootScript`. */
33
+ export interface ExperimentBootOptions {
34
+ /** The experiment running on the page. */
35
+ experimentId: string;
36
+ /** The variant the server rendered. */
37
+ variant: string;
38
+ }
39
+ /**
40
+ * Builds the configuration the boot script runs with.
41
+ *
42
+ * @param options - The experiment and the variant the server rendered
43
+ * @returns The configuration
44
+ */
45
+ export declare const createExperimentBootConfig: ({ experimentId, variant }: ExperimentBootOptions) => ExperimentBootConfig;
46
+ /**
47
+ * Builds the inline script printed before an experiment's content, right after the rule
48
+ * that hides it: the boot function, called with the decision function and its config.
49
+ *
50
+ * @param options - The experiment and the variant the server rendered
51
+ * @returns The script's source
52
+ */
53
+ export declare const buildExperimentBootScript: (options: ExperimentBootOptions) => string;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Attribute on the element that wraps what an experiment varies, with the experiment's id.
3
+ * `DotCMSExperiment` and `experimentMarkup` put it there; the engine reads it.
4
+ */
5
+ export declare const EXPERIMENT_ATTRIBUTE = "data-dot-experiment";
6
+ /** Attribute with the variant the server rendered in that element. */
7
+ export declare const VARIANT_ATTRIBUTE = "data-dot-variant";
8
+ /** Name dotCMS gives the original variant. */
9
+ export declare const DEFAULT_VARIANT = "DEFAULT";
10
+ /** Query parameter the page API reads to render a variant. */
11
+ export declare const VARIANT_QUERY_PARAM = "variantName";
12
+ /** Endpoint that assigns the visitor to the running experiments. */
13
+ export declare const IS_USER_INCLUDED_PATH = "/api/v1/experiments/isUserIncluded";
14
+ /**
15
+ * How long an experiment's content may stay hidden while its variant is decided. Past it the
16
+ * hiding rule shows the content, the boot script no longer redirects, and the engine's default
17
+ * wait ends.
18
+ */
19
+ export declare const DECISION_TIMEOUT_MS = 3000;
20
+ /** Default wait for the assignment on the pages the engine decides itself. */
21
+ export declare const DEFAULT_ASSIGNMENT_TIMEOUT_MS = 3000;
22
+ /** The request itself may run longer than the wait, so the next page can use its answer. */
23
+ export declare const IS_USER_INCLUDED_REQUEST_TIMEOUT_MS = 10000;
24
+ export declare const ONE_DAY_MS: number;
25
+ /** How long the page being replaced keeps its new requests waiting. */
26
+ export declare const QUIET_MS = 5000;
27
+ /**
28
+ * Rule printed before an experiment's content: the marked element stays hidden until the
29
+ * variant is decided, and shows on its own after `DECISION_TIMEOUT_MS` if nothing decides it
30
+ * (JavaScript off, SDK missing).
31
+ */
32
+ export declare const HIDING_RULE: string;
33
+ export declare const STORAGE_KEYS: {
34
+ /** localStorage: assignments and the experiment ids already evaluated. */
35
+ readonly assignments: "dot_events_experiments";
36
+ /** localStorage: until when the site answered 403 (Experiments off). */
37
+ readonly disabledUntil: "dot_events_experiments_off_until";
38
+ /** sessionStorage: this tab already asked `isUserIncluded`. */
39
+ readonly checkedThisTab: "dot_events_experiments_checked";
40
+ /** sessionStorage: the cumulative `context.experiments` of the analytics session. */
41
+ readonly sessionExperiments: "dot_events_session_experiments";
42
+ };
43
+ /** Window property where the boot script leaves its decision on the page it ran on. */
44
+ export declare const BOOT_STATE_KEY = "__dotEventsExperimentBoot";
45
+ /** Id of the style element that reveals decided content. */
46
+ export declare const REVEAL_STYLE_ID = "dotcms-experiment-reveal";
@@ -0,0 +1,53 @@
1
+ /**
2
+ * What to do with an experiment's content on the current page, from what is stored:
3
+ *
4
+ * - `unknown`: nothing is stored about the experiment; dotCMS has to be asked.
5
+ * - `excluded`: experiments are off, or the visitor was evaluated and is not in this one.
6
+ * - `assigned`: the server rendered the visitor's variant.
7
+ * - `ignored`: the visitor has another variant and the URL already asks for it, so the route
8
+ * does not pass `variantName` to its page request; loading the URL again would loop.
9
+ * - `redirect`: the visitor has another variant, at `url`.
10
+ */
11
+ export type VariantDecision = {
12
+ kind: 'unknown';
13
+ } | {
14
+ kind: 'excluded';
15
+ } | {
16
+ kind: 'assigned';
17
+ variant: string;
18
+ } | {
19
+ kind: 'ignored';
20
+ variant: string;
21
+ } | {
22
+ kind: 'redirect';
23
+ variant: string;
24
+ url: string;
25
+ };
26
+ /** What `decideVariant` reads. */
27
+ export interface VariantDecisionInput {
28
+ /** The experiment the page runs. */
29
+ experimentId: string;
30
+ /** The variant the server rendered. */
31
+ rendered: string;
32
+ /** The current URL. */
33
+ href: string;
34
+ /** The stored assignments, as parsed from storage: any shape is tolerated. */
35
+ stored: unknown;
36
+ /** Experiments are off (a 403 in the last day). */
37
+ disabled: boolean;
38
+ /** The current time, in ms. */
39
+ now: number;
40
+ /** The query parameter that selects a variant. */
41
+ param: string;
42
+ /** The name of the original variant. */
43
+ defaultVariant: string;
44
+ }
45
+ /**
46
+ * Decides an experiment's content from what is stored. It is pure, and the boot script prints
47
+ * it with `Function.prototype.toString`, so it must not reference anything outside itself:
48
+ * the engine and the boot script run this same function.
49
+ *
50
+ * @param input - The page's experiment, the URL, the stored assignments and the shared names
51
+ * @returns What to do with the content
52
+ */
53
+ export declare function decideVariant(input: VariantDecisionInput): VariantDecision;
@@ -0,0 +1,62 @@
1
+ import type { PageExperimentMark } from './models';
2
+ /**
3
+ * Reads the experiments the current page renders, from their marks: each experiment and
4
+ * rendered variant once, in document order.
5
+ *
6
+ * @returns The experiments and the variants the server rendered
7
+ */
8
+ export declare const readPageExperiments: () => PageExperimentMark[];
9
+ /**
10
+ * Calls back when marks are inserted or rewritten, so the engine decides content that arrives
11
+ * after its first decision: Suspense content that streams in, or a route that changes only its
12
+ * query and keeps its elements.
13
+ *
14
+ * @param changed - Called once per batch of DOM changes that touches a mark
15
+ * @returns Stops observing
16
+ */
17
+ export declare const observeMarks: (changed: () => void) => (() => void);
18
+ /**
19
+ * Shows rows the renderer hid. With an id and a variant, only that experiment's rows rendered
20
+ * with that variant, so a route kept in the page with another render stays hidden; without an
21
+ * id, all of them (engine off, inside UVE).
22
+ *
23
+ * The rule beats the renderer's hiding rule through `:root` and `!important`, whatever the
24
+ * order of the two in the document.
25
+ *
26
+ * @param experimentId - The experiment whose rows to show
27
+ * @param variant - The variant they were rendered with
28
+ */
29
+ export declare const revealRows: (experimentId?: string, variant?: string) => void;
30
+ /**
31
+ * Hides the contentlet wrappers dotCMS prints on the pages it renders: they hold what a variant
32
+ * changes. Like the markup's rule, it shows them on its own after `DECISION_TIMEOUT_MS`.
33
+ */
34
+ export declare const hideContentlets: () => void;
35
+ /**
36
+ * Shows the contentlet wrappers `hideContentlets` hid, and asks the impression tracker to scan
37
+ * again for them.
38
+ */
39
+ export declare const revealContentlets: () => void;
40
+ /**
41
+ * The variant a page dotCMS rendered shows: the one its contentlet wrappers carry in
42
+ * `data-dot-variant`, or, when the variant changes none of them (or the wrappers are not
43
+ * parsed yet), the one its URL asks for. dotCMS renders the variant `variantName` names.
44
+ *
45
+ * @param href - The page's URL
46
+ * @returns The rendered variant
47
+ */
48
+ export declare const readRenderedVariant: (href: string) => string;
49
+ /**
50
+ * Holds the page's new `fetch` calls, except `keepalive` ones (an analytics flush), for
51
+ * `QUIET_MS`. A page being replaced runs until the variant's document arrives, and a hydrated
52
+ * app goes on prefetching its links; held, they do not compete with the variant's server render.
53
+ * A held call never settles, and after `QUIET_MS` only new calls go out.
54
+ */
55
+ export declare const holdRequests: () => void;
56
+ /**
57
+ * Replaces the page with a variant's URL: stops its loading, navigates, and holds its new
58
+ * requests, as the boot script does when it decides from storage.
59
+ *
60
+ * @param url - The variant's URL
61
+ */
62
+ export declare const leavePageFor: (url: string) => void;
@@ -0,0 +1,66 @@
1
+ import type { DotCMSEventsError } from '../models';
2
+ import type { DotCMSEventContextExperiment } from '../pipeline/models';
3
+ /** What the engine decided for the current page. */
4
+ export interface PageDecision {
5
+ /** True when the engine navigated to the assigned variant; the pageview is dropped. */
6
+ redirected: boolean;
7
+ /**
8
+ * True when the visitor went to another page while this one was decided; its pageview is
9
+ * dropped, and the other page is decided and counted on its own.
10
+ */
11
+ left?: true;
12
+ }
13
+ /**
14
+ * How the pages mark what their experiment varies. `markup`: the element `experimentMarkup`
15
+ * and `DotCMSExperiment` print, on headless apps. `contentlets`: the contentlet wrappers dotCMS
16
+ * prints on the pages it renders, which carry the rendered variant in `data-dot-variant`; the
17
+ * experiment comes from the `isExperimentPage` rule `isUserIncluded` returns.
18
+ */
19
+ export type ExperimentPages = 'markup' | 'contentlets';
20
+ /** The experiments engine behind `dotEvents`. */
21
+ export interface ExperimentsEngine {
22
+ /**
23
+ * On `contentlets` pages, decides from storage while the page's head is parsed: it replaces
24
+ * the page with the assigned variant before it paints, or, on a first visit, hides the
25
+ * contentlets until `isUserIncluded` answers. Does nothing on `markup` pages.
26
+ *
27
+ * @returns True when it replaced the page
28
+ */
29
+ prepare(): boolean;
30
+ /** Starts the assignment check, when one is due. */
31
+ start(): void;
32
+ /**
33
+ * Decides the current page: reveal its rows, or redirect to the assigned variant. From then
34
+ * on it also decides the marks that arrive later, as content streams in.
35
+ */
36
+ decide(): Promise<PageDecision>;
37
+ /**
38
+ * The session's cumulative `context.experiments`, while the page on screen runs an
39
+ * experiment the visitor is in; none on any other page.
40
+ */
41
+ contextExperiments(sessionId: string): DotCMSEventContextExperiment[];
42
+ /** Stops watching the page: for marks that arrive later, and for its navigations. */
43
+ stop(): void;
44
+ }
45
+ interface EngineOptions {
46
+ dotcmsUrl: string;
47
+ timeoutMs: number;
48
+ log: (...args: unknown[]) => void;
49
+ /** Reports a setup mistake; prints only in debug, like `log`. */
50
+ warn: (message: string) => void;
51
+ /** Reports a failed experiments check to the app's `onError`. */
52
+ onError?: (error: DotCMSEventsError) => void;
53
+ /** Replaces the page with a variant's URL (`leavePageFor`); a parameter so specs can watch it. */
54
+ navigate?: (url: string) => void;
55
+ /** How the pages mark their experiment; `markup` by default. */
56
+ pages?: ExperimentPages;
57
+ }
58
+ /**
59
+ * Creates the engine that assigns the visitor, holds and releases pageviews, redirects to
60
+ * the assigned variant and fills `context.experiments`.
61
+ *
62
+ * @param options - dotCMS origin, the assignment timeout and a logger
63
+ * @returns The engine
64
+ */
65
+ export declare const createExperimentsEngine: ({ dotcmsUrl, timeoutMs, log, warn, onError, navigate, pages }: EngineOptions) => ExperimentsEngine;
66
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { DotCMSEventsExperimentMarkup, DotCMSEventsExperimentPage } from '../models';
2
+ /**
3
+ * Builds what a page prints so its experiment runs: the attributes for the element that wraps
4
+ * what the experiment varies, the rule that keeps it hidden, and the boot script that decides
5
+ * the visitor's variant while the HTML is parsed. Render it on the server, with the style and
6
+ * the script before the element. `DotCMSExperiment` (`@dotcms/events/react`) does it for React.
7
+ *
8
+ * @param page - The page asset, requested for the variant in the URL's `variantName`
9
+ * @returns The markup, or null when no experiment runs on the page
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const markup = experimentMarkup(pageAsset);
14
+ * // <style>{markup.style}</style><script>{markup.script}</script><div {...markup.attributes}>…</div>
15
+ * ```
16
+ */
17
+ export declare const experimentMarkup: (page: DotCMSEventsExperimentPage | null | undefined) => DotCMSEventsExperimentMarkup | null;
@@ -0,0 +1,62 @@
1
+ /** A variant, as `isUserIncluded` returns it. */
2
+ export interface DotCMSExperimentVariant {
3
+ name: string;
4
+ url: string;
5
+ }
6
+ /** An experiment the visitor was assigned to, as `isUserIncluded` returns it. */
7
+ export interface DotCMSAssignedExperiment {
8
+ id: string;
9
+ name: string;
10
+ runningId: string;
11
+ pageUrl: string;
12
+ lookBackWindow: {
13
+ expireMillis: number;
14
+ value: string;
15
+ };
16
+ regexs: {
17
+ isExperimentPage: string;
18
+ isTargetPage: string | null;
19
+ };
20
+ variant: DotCMSExperimentVariant;
21
+ }
22
+ /** The `entity` of the `isUserIncluded` response. */
23
+ export interface DotCMSIsUserIncludedEntity {
24
+ experiments: DotCMSAssignedExperiment[];
25
+ includedExperimentIds: string[];
26
+ excludedExperimentIds: string[];
27
+ excludedExperimentIdsEnded: string[];
28
+ }
29
+ /** An assignment kept in localStorage, with the moment it stops being valid. */
30
+ export type StoredExperiment = DotCMSAssignedExperiment & {
31
+ expiresAt: number;
32
+ };
33
+ /** What the engine keeps in localStorage between visits. */
34
+ export interface StoredAssignments {
35
+ fetchedAt: number;
36
+ experiments: StoredExperiment[];
37
+ /** Every experiment already evaluated, assigned or not, sent back as `exclude`. */
38
+ evaluatedIds: string[];
39
+ }
40
+ /** The result of asking `isUserIncluded`. */
41
+ export type IsUserIncludedResult = {
42
+ status: 'ok';
43
+ entity: DotCMSIsUserIncludedEntity;
44
+ } | {
45
+ status: 'disabled';
46
+ } | {
47
+ status: 'failed';
48
+ httpStatus?: number;
49
+ };
50
+ /** The experiment a page carries in its marks. */
51
+ export interface PageExperimentMark {
52
+ experimentId: string;
53
+ variant: string;
54
+ }
55
+ /**
56
+ * What the boot script leaves on `window[BOOT_STATE_KEY]` when it replaces the page with the
57
+ * assigned variant, so the engine neither redirects again nor sends that page's pageview.
58
+ */
59
+ export interface ExperimentBootState {
60
+ /** The variant's URL. */
61
+ redirectedTo: string;
62
+ }
@@ -0,0 +1,27 @@
1
+ import type { ExperimentsEngine } from './engine';
2
+ import type { EventContext } from '../pipeline/models';
3
+ interface PayloadWithContext {
4
+ context?: EventContext;
5
+ [key: string]: unknown;
6
+ }
7
+ /**
8
+ * Analytics.js plugin that adds the session's `context.experiments` to every event sent from
9
+ * a page that runs an experiment the visitor is in. The engine's `isUserIncluded` check starts
10
+ * in `init`, before Analytics.js loads.
11
+ *
12
+ * It must come right after the identity plugin, which creates `context`.
13
+ *
14
+ * @param engine - The experiments engine
15
+ * @returns The plugin
16
+ */
17
+ export declare const experimentsPlugin: (engine: ExperimentsEngine) => {
18
+ name: string;
19
+ pageStart: ({ payload }: {
20
+ payload: PayloadWithContext;
21
+ }) => PayloadWithContext;
22
+ trackStart: ({ payload }: {
23
+ payload: PayloadWithContext;
24
+ }) => PayloadWithContext;
25
+ loaded: () => boolean;
26
+ };
27
+ export {};
@@ -0,0 +1,54 @@
1
+ import type { DotCMSIsUserIncludedEntity, StoredAssignments } from './models';
2
+ import type { DotCMSEventContextExperiment } from '../pipeline/models';
3
+ /**
4
+ * Loads the stored assignments, without the ones whose look-back window has passed.
5
+ *
6
+ * @returns The assignments, or null when there are none
7
+ */
8
+ export declare const loadAssignments: () => StoredAssignments | null;
9
+ /**
10
+ * Merges an `isUserIncluded` answer into the stored assignments and saves them.
11
+ * Ended experiments drop out; every evaluated experiment is remembered, so a visitor
12
+ * left out by traffic allocation is not evaluated again.
13
+ *
14
+ * @param entity - The response entity
15
+ * @param previous - What was stored before the request
16
+ * @returns The assignments now stored
17
+ */
18
+ export declare const saveAssignments: (entity: DotCMSIsUserIncludedEntity, previous: StoredAssignments | null) => StoredAssignments;
19
+ /**
20
+ * Tells whether this tab should ask `isUserIncluded`: once per tab session, when nothing
21
+ * is stored, or when the stored answer is older than a day.
22
+ *
23
+ * @param stored - The current assignments
24
+ * @returns True when a request is due
25
+ */
26
+ export declare const isCheckDue: (stored: StoredAssignments | null) => boolean;
27
+ /** Records that this tab asked `isUserIncluded`. */
28
+ export declare const markCheckedThisTab: () => void;
29
+ /** Tells whether the site answered 403 in the last day. */
30
+ export declare const isDisabledForNow: () => boolean;
31
+ /** Remembers a 403 for a day, so sites without Experiments stop paying for the call. */
32
+ export declare const disableForADay: () => void;
33
+ /**
34
+ * Returns the experiments the visitor reached in this analytics session. A new session
35
+ * starts empty.
36
+ *
37
+ * @param sessionId - The analytics session id
38
+ * @returns The cumulative `context.experiments`
39
+ */
40
+ export declare const getSessionExperiments: (sessionId: string) => DotCMSEventContextExperiment[];
41
+ /**
42
+ * Adds an experiment to the session's `context.experiments`, replacing an older entry
43
+ * for the same experiment.
44
+ *
45
+ * @param sessionId - The analytics session id
46
+ * @param entry - The experiment the visitor reached
47
+ */
48
+ export declare const joinSessionExperiment: (sessionId: string, entry: DotCMSEventContextExperiment) => void;
49
+ /**
50
+ * Removes ended experiments from the session's `context.experiments`.
51
+ *
52
+ * @param endedIds - Ids `isUserIncluded` reported as ended
53
+ */
54
+ export declare const leaveEndedExperiments: (endedIds: string[]) => void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Impression tracking configuration constants
3
+ */
4
+ /**
5
+ * Default minimum percentage of element that must be visible (0.0 to 1.0)
6
+ */
7
+ export declare const DEFAULT_IMPRESSION_VISIBILITY_THRESHOLD = 0.5;
8
+ /**
9
+ * Default minimum time in milliseconds element must be visible
10
+ */
11
+ export declare const DEFAULT_IMPRESSION_DWELL_MS = 750;
12
+ /**
13
+ * Default maximum number of elements to track (performance limit)
14
+ */
15
+ export declare const DEFAULT_IMPRESSION_MAX_NODES = 100;
16
+ /**
17
+ * Default impression tracking configuration
18
+ */
19
+ export declare const DEFAULT_IMPRESSION_CONFIG: {
20
+ readonly visibilityThreshold: 0.5;
21
+ readonly dwellMs: 750;
22
+ readonly maxNodes: 100;
23
+ };
24
+ /**
25
+ * Event type for content impressions
26
+ * Must match DotCMSPredefinedEventType.CONTENT_IMPRESSION
27
+ */
28
+ export declare const IMPRESSION_EVENT_TYPE = "content_impression";
@@ -0,0 +1,38 @@
1
+ import type { PipelineConfig } from '../pipeline/models';
2
+ import type { AnalyticsInstance } from 'analytics';
3
+ /**
4
+ * Impressions plugin of the events pipeline.
5
+ * Handles automatic tracking of content visibility and impressions.
6
+ *
7
+ * This plugin initializes the impression tracker which:
8
+ * - Uses IntersectionObserver to detect when contentlets are visible
9
+ * - Tracks dwell time (how long elements are visible)
10
+ * - Fires 'content-impression' events via instance.track()
11
+ * - Deduplicates impressions per session
12
+ *
13
+ * Analytics.js runs the plugins in the order events.ts registers them: identity, experiments,
14
+ * impressions, clicks, enricher, sender. This one hooks into no event: its tracker fires
15
+ * events through instance.track(), which runs them through the other plugins like any event.
16
+ *
17
+ * Note: This plugin is only registered if config.impressions is enabled.
18
+ * See getEnhancedTrackingPlugins() for conditional loading logic.
19
+ *
20
+ * @param {PipelineConfig} config - Configuration with impressions settings
21
+ * @returns {Object} Plugin object with lifecycle methods
22
+ */
23
+ export declare const impressionsPlugin: (config: PipelineConfig) => {
24
+ name: string;
25
+ /**
26
+ * Initialize impression tracking
27
+ * Called when Analytics.js initializes the plugin with instance context
28
+ * @param instance - Analytics.js instance with track method
29
+ */
30
+ initialize: ({ instance }: {
31
+ instance: AnalyticsInstance;
32
+ }) => Promise<void>;
33
+ /**
34
+ * Setup cleanup handlers when plugin is loaded
35
+ * Called after Analytics.js completes plugin loading
36
+ */
37
+ loaded: () => boolean;
38
+ };