@dotcms/events 0.0.0-stage → 26.10.5-1-alpha.0
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.
- package/README.md +516 -2
- package/decision.cjs.js +108 -0
- package/decision.esm.js +92 -0
- package/index.cjs.default.js +1 -0
- package/index.cjs.js +3674 -0
- package/index.cjs.mjs +2 -0
- package/index.d.ts +1 -0
- package/index.esm.js +3672 -0
- package/markup.cjs.default.js +1 -0
- package/markup.cjs.js +161 -0
- package/markup.cjs.mjs +2 -0
- package/markup.d.ts +1 -0
- package/markup.esm.js +159 -0
- package/package.json +77 -4
- package/react.cjs.default.js +1 -0
- package/react.cjs.js +39 -0
- package/react.cjs.mjs +2 -0
- package/react.d.ts +1 -0
- package/react.esm.js +37 -0
- package/src/index.d.ts +8 -0
- package/src/lib/clicks/constants.d.ts +14 -0
- package/src/lib/clicks/plugin.d.ts +35 -0
- package/src/lib/clicks/tracker.d.ts +42 -0
- package/src/lib/clicks/utils.d.ts +13 -0
- package/src/lib/contentlets/constants.d.ts +36 -0
- package/src/lib/contentlets/utils.d.ts +64 -0
- package/src/lib/contentlets/viewport.d.ts +23 -0
- package/src/lib/events.d.ts +17 -0
- package/src/lib/experiments/api.d.ts +9 -0
- package/src/lib/experiments/boot.d.ts +53 -0
- package/src/lib/experiments/constants.d.ts +46 -0
- package/src/lib/experiments/decision.d.ts +53 -0
- package/src/lib/experiments/dom.d.ts +62 -0
- package/src/lib/experiments/engine.d.ts +66 -0
- package/src/lib/experiments/markup.d.ts +17 -0
- package/src/lib/experiments/models.d.ts +62 -0
- package/src/lib/experiments/plugin.d.ts +27 -0
- package/src/lib/experiments/store.d.ts +54 -0
- package/src/lib/impressions/constants.d.ts +28 -0
- package/src/lib/impressions/plugin.d.ts +38 -0
- package/src/lib/impressions/tracker.d.ts +67 -0
- package/src/lib/impressions/utils.d.ts +10 -0
- package/src/lib/models.d.ts +130 -0
- package/src/lib/pipeline/constants.d.ts +88 -0
- package/src/lib/pipeline/enricher/plugin.d.ts +32 -0
- package/src/lib/pipeline/identity/activity.d.ts +19 -0
- package/src/lib/pipeline/identity/plugin.d.ts +60 -0
- package/src/lib/pipeline/logger.d.ts +74 -0
- package/src/lib/pipeline/models.d.ts +588 -0
- package/src/lib/pipeline/navigation.d.ts +21 -0
- package/src/lib/pipeline/sender/http.d.ts +9 -0
- package/src/lib/pipeline/sender/plugin.d.ts +47 -0
- package/src/lib/pipeline/sender/queue.d.ts +33 -0
- package/src/lib/pipeline/utils.d.ts +119 -0
- package/src/lib/react/DotCMSExperiment.d.ts +36 -0
- package/src/markup.d.ts +7 -0
- 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
|
+
};
|