@thg-agentic/hyper-personalisation-web 0.0.0-stage → 3.17.1
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/LICENSE +21 -0
- package/README.md +825 -2
- package/dist/hp.min.js +3 -0
- package/dist/snippet.min.js +1 -0
- package/package.json +83 -3
- package/src/assignment.js +75 -0
- package/src/auto/attributes.js +16 -0
- package/src/auto/exposures.js +45 -0
- package/src/auto/lists.js +136 -0
- package/src/auto/promotions.js +104 -0
- package/src/auto/rails.js +217 -0
- package/src/auto/viewability.js +150 -0
- package/src/auto.js +13 -0
- package/src/bootstrap.js +476 -0
- package/src/carry.js +74 -0
- package/src/conformance.js +530 -0
- package/src/consent-gate.js +324 -0
- package/src/consent.js +604 -0
- package/src/context.js +439 -0
- package/src/datalayer.js +411 -0
- package/src/decisions.js +90 -0
- package/src/device-storage.js +201 -0
- package/src/experiments.js +220 -0
- package/src/ga4.js +124 -0
- package/src/hash.js +138 -0
- package/src/hp.js +135 -0
- package/src/index.js +818 -0
- package/src/preview.js +44 -0
- package/src/recommendation-context.js +101 -0
- package/src/segment.js +375 -0
- package/src/session/category.js +40 -0
- package/src/session/record.js +163 -0
- package/src/session/rules.js +195 -0
- package/src/session/signals.js +146 -0
- package/src/session-id.js +62 -0
- package/src/session.js +28 -0
- package/src/settings.js +211 -0
- package/src/shape.js +111 -0
- package/src/social-proof.js +139 -0
- package/src/spec-version.js +4 -0
- package/src/storage-scope.js +159 -0
- package/src/transport.js +436 -0
- package/src/version.js +2 -0
- package/types/auto.d.ts +51 -0
- package/types/bootstrap.d.ts +154 -0
- package/types/consent.d.ts +126 -0
- package/types/datalayer.d.ts +76 -0
- package/types/events.d.ts +680 -0
- package/types/hp.d.ts +64 -0
- package/types/index.d.ts +610 -0
- package/types/segment.d.ts +68 -0
package/types/hp.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CdpEventName, CdpEventProperties } from './events';
|
|
2
|
+
import type {
|
|
3
|
+
AnalyticsClient,
|
|
4
|
+
CallOptions,
|
|
5
|
+
RecommendationContext,
|
|
6
|
+
} from './index';
|
|
7
|
+
|
|
8
|
+
type Methods = {
|
|
9
|
+
[K in keyof AnalyticsClient as AnalyticsClient[K] extends (
|
|
10
|
+
...args: never[]
|
|
11
|
+
) => unknown
|
|
12
|
+
? K
|
|
13
|
+
: never]: AnalyticsClient[K];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `track`, with both of the client's forms. A mapped `Parameters<>` keeps only an overloaded
|
|
18
|
+
* function's last signature, which typed `hp.track` as the extension form alone and refused
|
|
19
|
+
* `hp.track('Identity Linked', …)`.
|
|
20
|
+
*/
|
|
21
|
+
interface HpTrack {
|
|
22
|
+
<E extends CdpEventName>(
|
|
23
|
+
event: E,
|
|
24
|
+
properties?: CdpEventProperties[E],
|
|
25
|
+
options?: CallOptions,
|
|
26
|
+
): void;
|
|
27
|
+
(
|
|
28
|
+
event: `${string}.${string}`,
|
|
29
|
+
properties?: Record<string, unknown>,
|
|
30
|
+
options?: CallOptions,
|
|
31
|
+
): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The page's client, callable from anywhere: calls made before `bootstrap` has started it wait and
|
|
36
|
+
* replay in order, keeping the messageId and time they were made with; on a page that turns out to
|
|
37
|
+
* be off they are dropped. A page that navigates first, or never reaches the API, carries them to the
|
|
38
|
+
* next page, which sends them under its own consent (3.9, 3.9.1). Nothing is returned (but by
|
|
39
|
+
* `assignmentDecisionId` and `recommendationContext`), nothing is awaited, and nothing ever throws
|
|
40
|
+
* into the caller.
|
|
41
|
+
*/
|
|
42
|
+
export declare const hp: {
|
|
43
|
+
[K in Exclude<keyof Methods, 'track' | 'recommendationContext'>]: (
|
|
44
|
+
...args: Parameters<Methods[K]>
|
|
45
|
+
) => void;
|
|
46
|
+
} & {
|
|
47
|
+
track: HpTrack;
|
|
48
|
+
/**
|
|
49
|
+
* The `decision_id` HP logged when it assigned `arm` to `slot` server-side for this visitor's
|
|
50
|
+
* session, for the page's `Slot Engaged`. Synchronous: it reads the session cookie (else the
|
|
51
|
+
* visitor cookie) bootstrap was given, so it answers once bootstrap has been called, started or
|
|
52
|
+
* not. Null before that, or with neither cookie. Since 3.10.
|
|
53
|
+
*/
|
|
54
|
+
assignmentDecisionId(assignment: {
|
|
55
|
+
slot: string;
|
|
56
|
+
arm: string;
|
|
57
|
+
}): string | null;
|
|
58
|
+
/**
|
|
59
|
+
* This visit as the recommendations API's session context, now: append
|
|
60
|
+
* `new URLSearchParams(hp.recommendationContext())` to the request. Synchronous; `{}` before the
|
|
61
|
+
* client has started, and without personalisation consent. Since 3.11.
|
|
62
|
+
*/
|
|
63
|
+
recommendationContext(): RecommendationContext;
|
|
64
|
+
};
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import type { ConsentAdapter, ConsentState } from './consent';
|
|
2
|
+
import type {
|
|
3
|
+
BackInStockSubscribed,
|
|
4
|
+
CartShared,
|
|
5
|
+
CartUpdated,
|
|
6
|
+
CartViewed,
|
|
7
|
+
CdpEventName,
|
|
8
|
+
CdpEventProperties,
|
|
9
|
+
CdpProduct,
|
|
10
|
+
CheckoutFailed,
|
|
11
|
+
CheckoutStarted,
|
|
12
|
+
CheckoutStepCompleted,
|
|
13
|
+
CheckoutStepViewed,
|
|
14
|
+
ConsentUpdated,
|
|
15
|
+
CouponApplied,
|
|
16
|
+
CouponDenied,
|
|
17
|
+
CouponEntered,
|
|
18
|
+
CouponRemoved,
|
|
19
|
+
IdentityLinked,
|
|
20
|
+
NewsletterSubscribed,
|
|
21
|
+
NewsletterUnsubscribed,
|
|
22
|
+
PaymentInfoEntered,
|
|
23
|
+
PersonalisationExposed,
|
|
24
|
+
ProductAdded,
|
|
25
|
+
ProductAddedToWishlist,
|
|
26
|
+
ProductClicked,
|
|
27
|
+
ProductListFiltered,
|
|
28
|
+
ProductListViewed,
|
|
29
|
+
ProductRemoved,
|
|
30
|
+
ProductRemovedFromWishlist,
|
|
31
|
+
ProductReviewed,
|
|
32
|
+
ProductShared,
|
|
33
|
+
ProductsSearched,
|
|
34
|
+
ProductVariantSelected,
|
|
35
|
+
ProductViewed,
|
|
36
|
+
PromotionClicked,
|
|
37
|
+
PromotionViewed,
|
|
38
|
+
RecommendationsViewed,
|
|
39
|
+
SignedIn,
|
|
40
|
+
SignedUp,
|
|
41
|
+
SlotEngaged,
|
|
42
|
+
WishlistProductAddedToCart,
|
|
43
|
+
} from './events';
|
|
44
|
+
|
|
45
|
+
export type { ConsentAdapter, ConsentBasis, ConsentState } from './consent';
|
|
46
|
+
export type * from './events';
|
|
47
|
+
|
|
48
|
+
export type PageType =
|
|
49
|
+
| 'home'
|
|
50
|
+
| 'list'
|
|
51
|
+
| 'product'
|
|
52
|
+
| 'search'
|
|
53
|
+
| 'basket'
|
|
54
|
+
| 'checkout'
|
|
55
|
+
| 'order_confirmation'
|
|
56
|
+
| 'login'
|
|
57
|
+
| 'register'
|
|
58
|
+
| 'account'
|
|
59
|
+
| 'info'
|
|
60
|
+
| 'blog'
|
|
61
|
+
| 'other';
|
|
62
|
+
|
|
63
|
+
export interface PageProperties {
|
|
64
|
+
/** Stored as context.page.type and inherited by later events on the page. */
|
|
65
|
+
type?: PageType;
|
|
66
|
+
name?: string | null;
|
|
67
|
+
navigation_trigger?:
|
|
68
|
+
| 'navigate'
|
|
69
|
+
| 'reload'
|
|
70
|
+
| 'back_forward'
|
|
71
|
+
| 'prerender'
|
|
72
|
+
| 'soft_navigate'
|
|
73
|
+
| null;
|
|
74
|
+
category?: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface IdentifyTraits {
|
|
78
|
+
/** Becomes the envelope userId for this and every later event, until reset() or signedOut(). */
|
|
79
|
+
userId?: string | number | null;
|
|
80
|
+
/** Use hashEmail(). Raw email is never sent. */
|
|
81
|
+
email_sha256?: string | null;
|
|
82
|
+
/** Use hashPhone(). Raw phone numbers are never sent. */
|
|
83
|
+
phone_sha256?: string | null;
|
|
84
|
+
accepts_marketing?: boolean | null;
|
|
85
|
+
gender?: string | null;
|
|
86
|
+
created_at?: string | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Per-call context overrides, deep-merged into the event's context. */
|
|
90
|
+
export type ContextOverrides = Record<string, unknown>;
|
|
91
|
+
/** Either the overrides themselves, or analytics-next style `{ context: overrides }`. */
|
|
92
|
+
export type CallOptions = ContextOverrides | { context: ContextOverrides };
|
|
93
|
+
|
|
94
|
+
export interface CdpEvent {
|
|
95
|
+
type: 'track' | 'page' | 'identify';
|
|
96
|
+
event?: string;
|
|
97
|
+
messageId: string;
|
|
98
|
+
timestamp: string;
|
|
99
|
+
anonymousId: string | null;
|
|
100
|
+
userId: string | null;
|
|
101
|
+
properties?: Record<string, unknown>;
|
|
102
|
+
traits?: Record<string, unknown>;
|
|
103
|
+
context: Record<string, unknown>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ValidationResult {
|
|
107
|
+
valid: boolean;
|
|
108
|
+
errors: string[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The per-event sources shared with cdpPlugins(). */
|
|
112
|
+
export interface AnalyticsContextSources {
|
|
113
|
+
/**
|
|
114
|
+
* The host's session id. Omitted, the SDK keeps its own (3.15): a first-party cookie
|
|
115
|
+
* (`storageNames.session`) ended by 30 minutes without an event, written only under a grant.
|
|
116
|
+
* Given, it is the only source, even when it returns nothing.
|
|
117
|
+
*/
|
|
118
|
+
getSessionId?(): string | null | undefined;
|
|
119
|
+
getRenderId?(): string | null | undefined;
|
|
120
|
+
getExperiments?(): Record<string, unknown>[] | null | undefined;
|
|
121
|
+
getAudiences?(): Record<string, unknown>[] | null | undefined;
|
|
122
|
+
getDecisions?(): Record<string, unknown>[] | null | undefined;
|
|
123
|
+
getRecommendations?(): Record<string, unknown>[] | null | undefined;
|
|
124
|
+
getExtensions?(): Record<string, unknown> | null | undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface AnalyticsOptions extends AnalyticsContextSources {
|
|
128
|
+
/** Ingest base URL, only for a first-party proxy; events are POSTed to `${apiHost}/v1/batch`. */
|
|
129
|
+
apiHost?: string;
|
|
130
|
+
/** Default 'web'. */
|
|
131
|
+
writeKey?: string;
|
|
132
|
+
propertyId: string | number;
|
|
133
|
+
channel?: string | null;
|
|
134
|
+
market?: string | null;
|
|
135
|
+
environment?: string | null;
|
|
136
|
+
/** ISO-4217 currency, when the site trades in one. `getCurrency` wins when it answers. */
|
|
137
|
+
currency?: string | null;
|
|
138
|
+
/** The site's locale (which storefront was served), not the browser's. `getLocale` wins. */
|
|
139
|
+
locale?: string | null;
|
|
140
|
+
country?: string | null;
|
|
141
|
+
/** ISO-3166 alpha-2 destination. `getShipToCountry` wins. */
|
|
142
|
+
shipToCountry?: string | null;
|
|
143
|
+
/** Read per event: the currency this request is priced in. Invalid values are dropped. */
|
|
144
|
+
getCurrency?(): string | null | undefined;
|
|
145
|
+
/** Read per event: where this visitor's order would ship. */
|
|
146
|
+
getShipToCountry?(): string | null | undefined;
|
|
147
|
+
/** Read per event: the storefront locale served, e.g. `en-GB` (kept as given). */
|
|
148
|
+
getLocale?(): string | null | undefined;
|
|
149
|
+
/** Initial context.page.type, before the first page() call. */
|
|
150
|
+
pageType?: PageType | null;
|
|
151
|
+
/** Used when there is no window (SSR). */
|
|
152
|
+
pageUrl?: string | null;
|
|
153
|
+
pagePath?: string | null;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Strict consent: nothing is sent or stored until analytics is granted, and a CMP that never
|
|
157
|
+
* answers is never a grant. Without an adapter the host has decided the basis itself, and events
|
|
158
|
+
* carry `context.consent: { basis: 'host' }` (3.7.0, spec 1.5).
|
|
159
|
+
*/
|
|
160
|
+
consent?: ConsentAdapter;
|
|
161
|
+
/** How long to hold events for the CMP before dropping them. Default 5000. */
|
|
162
|
+
consentTimeoutMs?: number;
|
|
163
|
+
/** Domain for the consent cookie, e.g. '.example.com' to share it with checkout. */
|
|
164
|
+
consentCookieDomain?: string;
|
|
165
|
+
/** Called with the gate's status: pending, granted, denied or unknown (timed out). */
|
|
166
|
+
onConsentStatus?(status: 'pending' | 'granted' | 'denied' | 'unknown'): void;
|
|
167
|
+
/** Rename what the SDK stores. Defaults are `thg_hp_*`; one SDK per page. */
|
|
168
|
+
storageNames?: StorageNames;
|
|
169
|
+
/** Segment-style tracking plan: `track[event].enabled === false` drops it, `__default` covers the rest. */
|
|
170
|
+
plan?: { track?: Record<string, { enabled?: boolean }> } | null;
|
|
171
|
+
|
|
172
|
+
/** The property's personalisation holdout, from the settings (`hp.holdouts`). */
|
|
173
|
+
holdout?: Holdout | null;
|
|
174
|
+
|
|
175
|
+
/** Keep only these query params (anchored, case-insensitive patterns), plus campaign params. */
|
|
176
|
+
queryParamAllowlist?: string[];
|
|
177
|
+
/** Strip these query params. Ignored when an allowlist is given. */
|
|
178
|
+
queryParamBlocklist?: string[];
|
|
179
|
+
/**
|
|
180
|
+
* Param names (anchored, case-insensitive patterns) exempt from the built-in sensitive-name rules.
|
|
181
|
+
* A value that is an email address or a card-length number is dropped regardless.
|
|
182
|
+
*/
|
|
183
|
+
sensitiveParamAllowlist?: string[];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Where device ids come from, as the source's settings serve it (`hp.anonymousId`, chosen in the
|
|
187
|
+
* console). One place per source, never a fallback between two:
|
|
188
|
+
*
|
|
189
|
+
* - `site_cookie`: the site's own first-party cookie, the one named here or, with `cookie: null`,
|
|
190
|
+
* whatever `getAnonymousId` reads. Read only: a visitor without it has no device id.
|
|
191
|
+
* - `hp_cookie`: Hyper-Personalisation's own first-party cookie (`storageNames.anonymousId`,
|
|
192
|
+
* `thg_hp_anonymous_id`), which the SDK mints and keeps; `getAnonymousId` is ignored.
|
|
193
|
+
*
|
|
194
|
+
* Absent, as in settings from before 3.6: `getAnonymousId`. `bootstrap` passes it from the
|
|
195
|
+
* settings. Since 3.6.
|
|
196
|
+
*/
|
|
197
|
+
anonymousIdSource?: AnonymousIdSource | null;
|
|
198
|
+
/** The site's own device id, under `site_cookie` with no cookie named, or with no setting. */
|
|
199
|
+
getAnonymousId?(): string | null | undefined;
|
|
200
|
+
/**
|
|
201
|
+
* @deprecated Ignored since 3.6. Where device ids come from is set per source in the console
|
|
202
|
+
* (`anonymousIdSource`): the site's own cookie, or Hyper-Personalisation's, which the SDK mints.
|
|
203
|
+
* With `debug`, the SDK says so once.
|
|
204
|
+
*/
|
|
205
|
+
mintAnonymousId?: boolean;
|
|
206
|
+
/**
|
|
207
|
+
* Strict consent: carry events held for an unanswered CMP across a navigation, in sessionStorage
|
|
208
|
+
* per property (`thg_hp_held`), so a redirect before the answer (a sign-in) does not lose them.
|
|
209
|
+
* They leave only on a grant, on whichever page it comes, and are deleted on a refusal or after
|
|
210
|
+
* 30 minutes. This writes event data to the device before the visitor has answered: enable it
|
|
211
|
+
* only where your consent policy allows that. With bootstrap it also lets `hp` carry calls made
|
|
212
|
+
* before the start across an unanswered navigation (3.9). Default false. Since 3.5.
|
|
213
|
+
*/
|
|
214
|
+
holdAcrossPages?: boolean;
|
|
215
|
+
getUserId?(): string | null | undefined;
|
|
216
|
+
/** Observer for every event that will be sent. Must not throw; failures are swallowed. */
|
|
217
|
+
onEvent?(event: CdpEvent): void;
|
|
218
|
+
|
|
219
|
+
debug?: boolean;
|
|
220
|
+
/** Inject @thg-agentic/hyper-personalisation-spec's validateEvent in development. */
|
|
221
|
+
validate?(event: CdpEvent, options: { producer: 'web' }): ValidationResult;
|
|
222
|
+
throwOnInvalid?: boolean;
|
|
223
|
+
/** Replace delivery, e.g. in tests. */
|
|
224
|
+
transport?: {
|
|
225
|
+
enqueue(event: CdpEvent): void;
|
|
226
|
+
flush(options?: { beacon?: boolean }): Promise<void>;
|
|
227
|
+
/** Strict consent: analytics was granted, so batches persisted earlier may be replayed. */
|
|
228
|
+
replayPersisted?(): unknown;
|
|
229
|
+
/** Strict consent: analytics was refused, so batches persisted earlier must be forgotten. */
|
|
230
|
+
clearPersisted?(): void;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The event as queued, or null when the tracking plan disables it. */
|
|
235
|
+
type Emit<P> = (properties: P, options?: CallOptions) => CdpEvent | null;
|
|
236
|
+
|
|
237
|
+
export interface AnalyticsClient {
|
|
238
|
+
/** Null when the tracking plan disables the event. */
|
|
239
|
+
track<E extends CdpEventName>(
|
|
240
|
+
event: E,
|
|
241
|
+
properties?: CdpEventProperties[E],
|
|
242
|
+
options?: CallOptions,
|
|
243
|
+
): CdpEvent | null;
|
|
244
|
+
/** A custom event under a registered extension namespace, e.g. 'acme.Size Guide Opened'. */
|
|
245
|
+
track(
|
|
246
|
+
event: `${string}.${string}`,
|
|
247
|
+
properties?: Record<string, unknown>,
|
|
248
|
+
options?: CallOptions,
|
|
249
|
+
): CdpEvent | null;
|
|
250
|
+
page(properties?: PageProperties, options?: CallOptions): CdpEvent;
|
|
251
|
+
identify(traits?: IdentifyTraits, options?: CallOptions): CdpEvent;
|
|
252
|
+
/** Clear identity and the page chain. */
|
|
253
|
+
reset(): void;
|
|
254
|
+
|
|
255
|
+
productViewed: Emit<ProductViewed>;
|
|
256
|
+
productListViewed: Emit<ProductListViewed>;
|
|
257
|
+
recommendationsViewed: Emit<RecommendationsViewed>;
|
|
258
|
+
/** Prefer `recordExposure`, which reads the serving response's `exposure` block. */
|
|
259
|
+
personalisationExposed: Emit<PersonalisationExposed>;
|
|
260
|
+
/**
|
|
261
|
+
* Log the exposure a serving response carries (membership, decisions or recommendations), once
|
|
262
|
+
* per token. Null when the response personalised nothing or its token was already recorded.
|
|
263
|
+
* A decisions response's logged decisions are then in force: every later event this session
|
|
264
|
+
* carries them as `context.decisions`, unless `getDecisions` is passed.
|
|
265
|
+
*/
|
|
266
|
+
recordExposure(body: ServingBody, options?: CallOptions): CdpEvent | null;
|
|
267
|
+
/**
|
|
268
|
+
* A surface rendered server-side from a serving answer the page never sees (a product rail
|
|
269
|
+
* the site's server fetched from HP) came into view: record the property holdout's exposure for it, for
|
|
270
|
+
* both groups, with the group serving decided. Null, and nothing recorded, for a visitor outside
|
|
271
|
+
* the experiment: no holdout in the settings, outside the ramp, no visitor or session id, or a
|
|
272
|
+
* rail or audience without personalisation consent. One per session per surface.
|
|
273
|
+
*/
|
|
274
|
+
surfaceViewed(
|
|
275
|
+
surface: { surface: ServingExposure['surface'] },
|
|
276
|
+
options?: CallOptions,
|
|
277
|
+
): CdpEvent | null;
|
|
278
|
+
/**
|
|
279
|
+
* Whether this shopper must see the default: held out, or outside the property's ramp (excluded),
|
|
280
|
+
* as the edge and the public API decide it. True: skip anything the page personalises itself.
|
|
281
|
+
* `personalisationGroup()` gives the exact group.
|
|
282
|
+
*/
|
|
283
|
+
inHoldout(): boolean;
|
|
284
|
+
/**
|
|
285
|
+
* This shopper's group, as the edge and the public API decide it. Personalise in the page only for
|
|
286
|
+
* `personalised`: `holdout` and `excluded` get the default everywhere.
|
|
287
|
+
*/
|
|
288
|
+
personalisationGroup(): PersonalisationGroup;
|
|
289
|
+
productClicked: Emit<ProductClicked>;
|
|
290
|
+
productListFiltered: Emit<ProductListFiltered>;
|
|
291
|
+
productsSearched: Emit<ProductsSearched>;
|
|
292
|
+
productAdded: Emit<ProductAdded>;
|
|
293
|
+
productRemoved: Emit<ProductRemoved>;
|
|
294
|
+
/**
|
|
295
|
+
* The basket as the platform confirmed it after a change, every line with its total quantity.
|
|
296
|
+
* Send alongside productAdded and productRemoved, never instead. `item_count` is summed from the
|
|
297
|
+
* lines when omitted.
|
|
298
|
+
*/
|
|
299
|
+
cartUpdated: Emit<ConfirmedBasket>;
|
|
300
|
+
/** An identity edge an identify cannot imply. Send the event-named trigger. */
|
|
301
|
+
identityLinked: Emit<IdentityLinked>;
|
|
302
|
+
/** A visitor saw, clicked or dismissed a decisioned slot. */
|
|
303
|
+
slotEngaged: Emit<SlotEngaged>;
|
|
304
|
+
/** A size, colour or flavour the shopper picked: product_id the master, sku the new variant. */
|
|
305
|
+
productVariantSelected: Emit<ProductVariantSelected>;
|
|
306
|
+
cartViewed: Emit<CartViewed>;
|
|
307
|
+
productAddedToWishlist: Emit<ProductAddedToWishlist>;
|
|
308
|
+
productRemovedFromWishlist: Emit<ProductRemovedFromWishlist>;
|
|
309
|
+
/** A wishlist conversion. Send productAdded for the cart add as well. */
|
|
310
|
+
wishlistProductAddedToCart: Emit<WishlistProductAddedToCart>;
|
|
311
|
+
productShared: Emit<ProductShared>;
|
|
312
|
+
cartShared: Emit<CartShared>;
|
|
313
|
+
/** Report each coupon fact from one producer per property, browser or server. */
|
|
314
|
+
couponEntered: Emit<CouponEntered>;
|
|
315
|
+
couponApplied: Emit<CouponApplied>;
|
|
316
|
+
couponDenied: Emit<CouponDenied>;
|
|
317
|
+
couponRemoved: Emit<CouponRemoved>;
|
|
318
|
+
checkoutStarted: Emit<CheckoutStarted>;
|
|
319
|
+
promotionViewed: Emit<PromotionViewed>;
|
|
320
|
+
promotionClicked: Emit<PromotionClicked>;
|
|
321
|
+
checkoutStepViewed: Emit<CheckoutStepViewed>;
|
|
322
|
+
checkoutStepCompleted: Emit<CheckoutStepCompleted>;
|
|
323
|
+
/** Send instead of checkoutStepCompleted for the payment step, never as well. */
|
|
324
|
+
paymentInfoEntered: Emit<PaymentInfoEntered>;
|
|
325
|
+
/** Where checkout failed and why. The kind of payment instrument only, never card data. */
|
|
326
|
+
checkoutFailed: Emit<CheckoutFailed>;
|
|
327
|
+
signedIn: Emit<SignedIn>;
|
|
328
|
+
signedUp: Emit<SignedUp>;
|
|
329
|
+
/** Sent under the signed-in identity, which is then cleared. */
|
|
330
|
+
signedOut(options?: CallOptions): CdpEvent | null;
|
|
331
|
+
newsletterSubscribed: Emit<NewsletterSubscribed>;
|
|
332
|
+
/** An on-site opt-out. The ESP reports its own as Email Unsubscribed. */
|
|
333
|
+
newsletterUnsubscribed: Emit<NewsletterUnsubscribed>;
|
|
334
|
+
/** An alert request for one product: back in stock, a price drop or a launch. */
|
|
335
|
+
backInStockSubscribed: Emit<BackInStockSubscribed>;
|
|
336
|
+
productReviewed: Emit<ProductReviewed>;
|
|
337
|
+
consentUpdated: Emit<ConsentUpdated>;
|
|
338
|
+
|
|
339
|
+
flush(options?: { beacon?: boolean }): Promise<void>;
|
|
340
|
+
readonly lastPageMessageId: string | null;
|
|
341
|
+
/** The device id events carry: the site's cookie's, or under `hp_cookie` the SDK's own. */
|
|
342
|
+
readonly anonymousId: string | null;
|
|
343
|
+
/** The consent state in force (strict mode), or null while the CMP has not answered. */
|
|
344
|
+
readonly consent: ConsentState | null;
|
|
345
|
+
/** Spread into every serving request (recommendations, decisions, audiences): `hp-consent`. */
|
|
346
|
+
servingHeaders(): { 'hp-consent': string };
|
|
347
|
+
/**
|
|
348
|
+
* This visit as the recommendations API's session context, in its wire form: append
|
|
349
|
+
* `new URLSearchParams(context)` to the request. `{}` without personalisation consent. Since 3.11.
|
|
350
|
+
*/
|
|
351
|
+
recommendationContext(): RecommendationContext;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The visit's products and searches, newest first, as `GET /v1/recommendations` takes them. Each
|
|
356
|
+
* key is present only when it has something in it. Since 3.11.
|
|
357
|
+
*/
|
|
358
|
+
export interface RecommendationContext {
|
|
359
|
+
/** Products engaged with (viewed, clicked, added), comma-separated. */
|
|
360
|
+
viewed?: string;
|
|
361
|
+
/** Products in the basket, comma-separated. */
|
|
362
|
+
basket?: string;
|
|
363
|
+
/** Searches, comma-separated. */
|
|
364
|
+
q?: string;
|
|
365
|
+
/** `id:count` pairs: products shown and not engaged with, most shown first. */
|
|
366
|
+
impressed?: string;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** A line's subscription, as products[].subscription carries it (spec 1.4). */
|
|
370
|
+
export interface CdpProductSubscription {
|
|
371
|
+
is_subscription?: boolean | null;
|
|
372
|
+
frequency_days?: number | null;
|
|
373
|
+
plan_id?: string | null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** One `discounts[]` entry on a basket event. */
|
|
377
|
+
export interface CdpBasketDiscount {
|
|
378
|
+
coupon_name: string;
|
|
379
|
+
coupon_id?: string | null;
|
|
380
|
+
discount?: number | null;
|
|
381
|
+
type?: string | null;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** The platform's confirmed basket, as cartUpdated takes it: Cart Updated, item_count optional. */
|
|
385
|
+
export interface ConfirmedBasket
|
|
386
|
+
extends Omit<CartUpdated, 'products' | 'discounts'> {
|
|
387
|
+
products: Array<
|
|
388
|
+
CdpProduct & { subscription?: CdpProductSubscription | null }
|
|
389
|
+
>;
|
|
390
|
+
discounts?: CdpBasketDiscount[] | null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Cart Updated's properties from a confirmed basket, with item_count summed when absent. */
|
|
394
|
+
export declare function cartSnapshot(basket: ConfirmedBasket): CartUpdated;
|
|
395
|
+
|
|
396
|
+
/** A property's personalisation holdout: its share of shoppers and the salt they are hashed with. */
|
|
397
|
+
export interface Holdout {
|
|
398
|
+
share: number;
|
|
399
|
+
/** The share of all shoppers personalised (the ramp); null or absent personalises everyone else. */
|
|
400
|
+
ramp?: number | null;
|
|
401
|
+
salt: string;
|
|
402
|
+
/** Readout slots: each splits personalised visitors 50/50 between the bandit and its default. Since 3.16. */
|
|
403
|
+
readoutSlots?: string[];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** A shopper's group: `holdout` and `excluded` (outside the ramp) both get the default. */
|
|
407
|
+
export type PersonalisationGroup = 'holdout' | 'personalised' | 'excluded';
|
|
408
|
+
|
|
409
|
+
/** The `exposure` block a serving response carries when it personalised, or would have. */
|
|
410
|
+
export interface ServingExposure {
|
|
411
|
+
token: string;
|
|
412
|
+
surface: 'audiences' | 'decisions' | 'recommendations';
|
|
413
|
+
holdout: boolean;
|
|
414
|
+
/** Absent: the property's, from the settings (a cached server-rendered page has no response). */
|
|
415
|
+
holdout_share?: number | null;
|
|
416
|
+
/** The share of all shoppers personalised when this was served (the ramp). Absent: as above. */
|
|
417
|
+
personalised_share?: number | null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** The parts of a membership, decisions or recommendations response `recordExposure` reads. */
|
|
421
|
+
export interface ServingBody {
|
|
422
|
+
exposure?: ServingExposure | null;
|
|
423
|
+
audiences?: string[];
|
|
424
|
+
decisions?: Array<{
|
|
425
|
+
slot: string;
|
|
426
|
+
decision_id?: string | null;
|
|
427
|
+
arm?: string | null;
|
|
428
|
+
/** Held out: the slot showed its default, and is not put in force. */
|
|
429
|
+
holdout?: boolean;
|
|
430
|
+
/**
|
|
431
|
+
* A readout slot's side of its bandit-vs-default split, as serving answered it. Absent: the SDK
|
|
432
|
+
* computes it from the settings. `default` is no decision and is not put in force. Since 3.16.
|
|
433
|
+
*/
|
|
434
|
+
variant?: 'bandit' | 'default';
|
|
435
|
+
}>;
|
|
436
|
+
recommendation_request_id?: string;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Where the SDK stores what it stores, by kind. Every default carries the `thg_hp_` prefix. */
|
|
440
|
+
export interface StorageNames {
|
|
441
|
+
/** Cookie. Default 'thg_hp_consent'. */
|
|
442
|
+
consent?: string;
|
|
443
|
+
/** localStorage. Default 'thg_hp_retry'. */
|
|
444
|
+
retry?: string;
|
|
445
|
+
/** localStorage / sessionStorage. Default 'thg_hp_session_signals'. */
|
|
446
|
+
sessionSignals?: string;
|
|
447
|
+
/** sessionStorage. Default 'thg_hp_identified'. */
|
|
448
|
+
identified?: string;
|
|
449
|
+
/** sessionStorage. Default 'thg_hp_prev_page'. */
|
|
450
|
+
previousPage?: string;
|
|
451
|
+
/** sessionStorage. Default 'thg_hp_decisions'. */
|
|
452
|
+
decisions?: string;
|
|
453
|
+
/** localStorage. Default 'thg_hp_settings'. */
|
|
454
|
+
settings?: string;
|
|
455
|
+
/** sessionStorage, with holdAcrossPages. Default 'thg_hp_held'. */
|
|
456
|
+
held?: string;
|
|
457
|
+
/** sessionStorage: `hp` calls carried to the next page (3.9). Default 'thg_hp_carried'. */
|
|
458
|
+
carried?: string;
|
|
459
|
+
/**
|
|
460
|
+
* localStorage: the visit for `hp.recommendationContext()` before the SDK starts on the next page
|
|
461
|
+
* (3.11). Default 'thg_hp_recommendation_context'.
|
|
462
|
+
*/
|
|
463
|
+
recommendationContext?: string;
|
|
464
|
+
/**
|
|
465
|
+
* Cookie, one per browser, for a source set to `hp_cookie`: Hyper-Personalisation's device id.
|
|
466
|
+
* Default 'thg_hp_anonymous_id'.
|
|
467
|
+
*/
|
|
468
|
+
anonymousId?: string;
|
|
469
|
+
/**
|
|
470
|
+
* Cookie: the session id the SDK keeps when the host names none, 30 minutes from the last event
|
|
471
|
+
* (3.15). Default 'thg_hp_session'.
|
|
472
|
+
*/
|
|
473
|
+
session?: string;
|
|
474
|
+
/** Session cookie: the preview token from `?hp_preview=` (3.15). Default 'thg_hp_preview'. */
|
|
475
|
+
preview?: string;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Where a source's device ids come from: the settings' `hp.anonymousId` (since 3.6). */
|
|
479
|
+
export type AnonymousIdSource =
|
|
480
|
+
| { source: 'site_cookie'; cookie: string | null }
|
|
481
|
+
| { source: 'hp_cookie' };
|
|
482
|
+
|
|
483
|
+
export declare const VERSION: string;
|
|
484
|
+
/** The HP API, where the SDK goes when no `apiHost` is given. */
|
|
485
|
+
export declare const DEFAULT_API_HOST: string;
|
|
486
|
+
/** An `apiHost` as an origin: DEFAULT_API_HOST when none is given, null when the one given is invalid. */
|
|
487
|
+
export declare function resolveApiHost(host?: string | null): string | null;
|
|
488
|
+
export declare const PAGE_TYPES: ReadonlySet<PageType>;
|
|
489
|
+
|
|
490
|
+
export declare function createAnalytics(
|
|
491
|
+
options: AnalyticsOptions,
|
|
492
|
+
): AnalyticsClient;
|
|
493
|
+
|
|
494
|
+
/** Default parent-id resolver: `item.item_group_id ?? item.item_id`. */
|
|
495
|
+
export declare function defaultProductId(
|
|
496
|
+
item: Record<string, unknown>,
|
|
497
|
+
): unknown;
|
|
498
|
+
/** GA4 item -> spec product. product_id comes from `productId` (default above), sku from item_id. */
|
|
499
|
+
export declare function fromGa4Item(
|
|
500
|
+
item: Record<string, unknown> | null | undefined,
|
|
501
|
+
productId?: (item: Record<string, unknown>) => unknown,
|
|
502
|
+
): CdpProduct | null;
|
|
503
|
+
/** GA4 items -> spec products, dropping any with no identity. */
|
|
504
|
+
export declare function toProducts(
|
|
505
|
+
items: unknown[] | null | undefined,
|
|
506
|
+
productId?: (item: Record<string, unknown>) => unknown,
|
|
507
|
+
): CdpProduct[];
|
|
508
|
+
export declare function pruneProperties<
|
|
509
|
+
T extends Record<string, unknown> | null | undefined,
|
|
510
|
+
>(properties: T): T;
|
|
511
|
+
|
|
512
|
+
/** SHA-256 as lowercase hex. */
|
|
513
|
+
export declare function sha256(value: string): Promise<string>;
|
|
514
|
+
/**
|
|
515
|
+
* `email_sha256` for an address, synchronously: trim, lowercase, SHA-256. Null when the input is not
|
|
516
|
+
* an email. Hash in the same call that reports it, so nothing is awaited. Since 3.9.
|
|
517
|
+
*/
|
|
518
|
+
export declare function emailSha256(
|
|
519
|
+
email: string | null | undefined,
|
|
520
|
+
): string | null;
|
|
521
|
+
/** As emailSha256, as a promise. */
|
|
522
|
+
export declare function hashEmail(
|
|
523
|
+
email: string | null | undefined,
|
|
524
|
+
): Promise<string | null>;
|
|
525
|
+
/** `phone_sha256` for identify: E.164, SHA-256. Null when the input is not an E.164 number. */
|
|
526
|
+
export declare function hashPhone(
|
|
527
|
+
phone: string | null | undefined,
|
|
528
|
+
): Promise<string | null>;
|
|
529
|
+
/**
|
|
530
|
+
* A SHA-256 digest (hex, base64 or base64url) as lowercase hex, for `email_sha256`. Re-encodes, never
|
|
531
|
+
* re-hashes; null for anything that is not a 32-byte digest.
|
|
532
|
+
*/
|
|
533
|
+
export declare function normaliseSha256(
|
|
534
|
+
value: string | null | undefined,
|
|
535
|
+
): string | null;
|
|
536
|
+
|
|
537
|
+
export declare function getCookie(name: string): string | null;
|
|
538
|
+
|
|
539
|
+
/** HP's fixed UUID namespace for the `decision_id` of a server-side assignment. Since 3.10. */
|
|
540
|
+
export declare const ASSIGNMENT_DECISION_NAMESPACE: string;
|
|
541
|
+
/**
|
|
542
|
+
* The `decision_id` HP logged for a server-side assignment (`POST /v1/assignments`):
|
|
543
|
+
* `UUIDv5(ASSIGNMENT_DECISION_NAMESPACE, "<session_id>:<slot>:<arm>")`, where `session_id` is the
|
|
544
|
+
* session HP assigned under (the renderer's session id, else the visitor id). Null when any part is
|
|
545
|
+
* missing. In a page, `hp.assignmentDecisionId({ slot, arm })` reads the session for you. Since 3.10.
|
|
546
|
+
*/
|
|
547
|
+
export declare function assignmentDecisionId(assignment: {
|
|
548
|
+
sessionId: string | null | undefined;
|
|
549
|
+
slot: string;
|
|
550
|
+
arm: string;
|
|
551
|
+
}): string | null;
|
|
552
|
+
|
|
553
|
+
export interface VariantAssignment {
|
|
554
|
+
experiment: string;
|
|
555
|
+
variant: string;
|
|
556
|
+
holdout: boolean;
|
|
557
|
+
assignment_bucket: number;
|
|
558
|
+
assignment_unit_kind: string;
|
|
559
|
+
}
|
|
560
|
+
export declare function assignRecommendationVariant(options: {
|
|
561
|
+
unit: string | null | undefined;
|
|
562
|
+
experiment: string;
|
|
563
|
+
variants: Array<{ name: string; share: number }>;
|
|
564
|
+
holdoutShare?: number;
|
|
565
|
+
holdoutName?: string;
|
|
566
|
+
salt?: string;
|
|
567
|
+
buckets?: number;
|
|
568
|
+
unitKind?: 'anonymous_id' | 'customer_id' | 'session_id' | 'other';
|
|
569
|
+
}): VariantAssignment | null;
|
|
570
|
+
export declare function candidateSetFingerprint(
|
|
571
|
+
productIds: Array<string | number> | null | undefined,
|
|
572
|
+
): string | null;
|
|
573
|
+
export declare function hashBucket(
|
|
574
|
+
unit: string,
|
|
575
|
+
salt: string,
|
|
576
|
+
buckets?: number,
|
|
577
|
+
): number;
|
|
578
|
+
export declare function hash32(input: string): number;
|
|
579
|
+
/**
|
|
580
|
+
* A visitor's side of a readout slot's bandit-vs-default 50/50, as the public API splits it;
|
|
581
|
+
* spec/assignment/bandit-split.json holds the vectors. Null without a slot or a unit. Since 3.16.
|
|
582
|
+
*/
|
|
583
|
+
export declare function banditSplit(
|
|
584
|
+
slot: string,
|
|
585
|
+
unit: string | null | undefined,
|
|
586
|
+
): 'bandit' | 'default' | null;
|
|
587
|
+
/** The same answer the edge and the public API give; spec/assignment/holdout.json holds the vectors. */
|
|
588
|
+
export declare function holdoutAssignment(
|
|
589
|
+
holdout: Holdout | null | undefined,
|
|
590
|
+
unit: string | null | undefined,
|
|
591
|
+
): { holdout: boolean; bucket: number | null; group: PersonalisationGroup };
|
|
592
|
+
|
|
593
|
+
export declare function createSocialProof(options: {
|
|
594
|
+
/** As for bootstrap(): optional, only for a first-party proxy. */
|
|
595
|
+
apiHost?: string;
|
|
596
|
+
/** The page's publishable key; it names the properties it may read. */
|
|
597
|
+
writeKey: string;
|
|
598
|
+
/** Only for a key scoped to several properties. */
|
|
599
|
+
propertyId?: string;
|
|
600
|
+
ttlMs?: number;
|
|
601
|
+
fetchImpl?: typeof fetch;
|
|
602
|
+
/** Sent as `hp-consent`. Default: read back from the consent cookie. */
|
|
603
|
+
getConsent?(): Partial<ConsentState> | null;
|
|
604
|
+
}): {
|
|
605
|
+
forProducts(
|
|
606
|
+
productIds: string[],
|
|
607
|
+
options?: { keyType?: string },
|
|
608
|
+
): Promise<Map<string, unknown>>;
|
|
609
|
+
trending(limit?: number, options?: { keyType?: string }): Promise<unknown[]>;
|
|
610
|
+
};
|