@thg-agentic/hyper-personalisation-web 3.17.1 → 3.19.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 +112 -39
- package/dist/hp.min.js +3 -3
- package/dist/snippet.min.js +1 -1
- package/package.json +2 -2
- package/src/bootstrap.js +40 -9
- package/src/consent-gate.js +8 -2
- package/src/debug.js +45 -0
- package/src/device-storage.js +35 -22
- package/src/guard.js +46 -0
- package/src/identity-option.js +6 -0
- package/src/index.js +159 -27
- package/src/recommendation-context.js +10 -1
- package/src/sampling.js +19 -0
- package/src/segment.js +15 -2
- package/src/session/record.js +23 -9
- package/src/session/signals.js +4 -0
- package/src/session-id.js +2 -2
- package/src/settings.js +25 -6
- package/src/shape.js +16 -7
- package/src/spec-version.js +1 -1
- package/src/storage-scope.js +6 -1
- package/src/transport.js +42 -4
- package/src/version.js +1 -1
- package/types/bootstrap.d.ts +11 -0
- package/types/consent.d.ts +4 -6
- package/types/events.d.ts +77 -2
- package/types/index.d.ts +28 -21
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @thg-agentic/hyper-personalisation-web
|
|
2
2
|
|
|
3
3
|
The browser SDK for THG Hyper-Personalisation, a customer data platform. It implements the event
|
|
4
|
-
specification (spec 1.
|
|
4
|
+
specification (spec 1.10, stamped on every event as `context.spec.version`) for any storefront: it
|
|
5
5
|
sends events in the Segment wire format, applies consent strictly whichever CMP the site uses, reads
|
|
6
6
|
the GA4 `dataLayer` the site already pushes to, and has no runtime dependencies.
|
|
7
7
|
|
|
@@ -37,8 +37,8 @@ const hp = await bootstrap(
|
|
|
37
37
|
organisation, the property and test or live, so there is no host, property or switch to set per
|
|
38
38
|
environment, as with Stripe's or LaunchDarkly's keys. `apiHost` is optional, only for a first-party
|
|
39
39
|
proxy (a bare host or an `https://` origin); without it the SDK uses the HP API
|
|
40
|
-
(`DEFAULT_API_HOST
|
|
41
|
-
[Default host](#default-host-
|
|
40
|
+
(`DEFAULT_API_HOST`, `https://hp.thgingenuity.com` since 3.18, for test and live keys alike; see
|
|
41
|
+
[Default host](#default-host-by-release)). Everything tenant-level comes from
|
|
42
42
|
`GET {apiHost}/v1/projects/{writeKey}/settings`, so switching a site on or off, or changing what it
|
|
43
43
|
collects, is a settings change and never a deploy:
|
|
44
44
|
|
|
@@ -51,7 +51,9 @@ collects, is a settings change and never a deploy:
|
|
|
51
51
|
| `queryParamAllowlist` | Query parameters kept on page URLs (plus campaign parameters). |
|
|
52
52
|
| `anonymousId` | Where device ids come from, chosen per source in the console (since 3.6): `{ source: 'site_cookie', cookie }` or `{ source: 'hp_cookie' }`. See [Device ids](#device-ids). |
|
|
53
53
|
| `apiHost` | Where events go, when not the host the settings came from. |
|
|
54
|
-
| `plan.track` | Segment's tracking plan: an event with `enabled: false` is dropped at the source. |
|
|
54
|
+
| `plan.track` | Segment's tracking plan: an event with `enabled: false` is dropped at the source. The console's blocked events arrive here. |
|
|
55
|
+
| `sampleRate` | The share of visitors events are sent for, by a hash of the anonymous id (since 3.18). Absent: 1. |
|
|
56
|
+
| `debug` | `{ enabled, tokenSha256 }`: console logging for everyone, or for a session opened with `?hp_debug=<token>` whose SHA-256 is `tokenSha256` (since 3.18). `?hp_debug=` ends it. |
|
|
55
57
|
|
|
56
58
|
**The page configures only what the page knows** (the second argument):
|
|
57
59
|
|
|
@@ -68,7 +70,7 @@ collects, is a settings change and never a deploy:
|
|
|
68
70
|
| `propertyId` | Only for a key scoped to several properties, whose settings name none. |
|
|
69
71
|
| `consentCookieDomain` | `Domain` for the consent cookie, e.g. `.acme.com` to share it with checkout. |
|
|
70
72
|
| `storageNames` | Rename what the SDK stores; see [Storage](#storage). |
|
|
71
|
-
| `debug` | Log every event and delivery problem. |
|
|
73
|
+
| `debug` | Log every event and delivery problem. Also on when the settings' `debug` says so. |
|
|
72
74
|
| `settings` | Development and tests only: use this settings response, never fetch or cache one. |
|
|
73
75
|
| `extend` | `createAnalytics` options that are functions, such as `getRecommendations` or `getExtensions`. |
|
|
74
76
|
|
|
@@ -79,14 +81,15 @@ What `bootstrap` does:
|
|
|
79
81
|
every problem. It never rejects, and a second call on the same page returns the first client.
|
|
80
82
|
- **Settings.** The last good answer is cached in `localStorage`, only once analytics is granted,
|
|
81
83
|
and forgotten on a refusal. A page with a cache starts from it at once and asks the API again for
|
|
82
|
-
the next page; a first visit waits for the API (which answers `Cache-Control: max-age=
|
|
83
|
-
|
|
84
|
+
the next page; a first visit waits for the API (which answers `Cache-Control: max-age=10`
|
|
85
|
+
and an `ETag`, so the browser revalidates for a `304`). A key the API switches off clears the cache, so the next page is
|
|
84
86
|
off too.
|
|
85
87
|
- **Consent**, identity, one page view, the dataLayer bridge and a once-per-session `identify`, as
|
|
86
88
|
configured.
|
|
87
89
|
- **Never waited for.** Call `hp` (`@thg-agentic/hyper-personalisation-web/hp`) from anywhere, at
|
|
88
90
|
any time, and never await or flush it: calls made before the client starts wait, each with the
|
|
89
|
-
messageId and time it was made at, and are sent in order once it has.
|
|
91
|
+
messageId and time it was made at, and are sent in order once it has. The page view is stamped
|
|
92
|
+
when the page was navigated to, so none of them precedes it (3.18). A page that navigates
|
|
90
93
|
first (a sign-in that redirects at once) carries them in sessionStorage, and the next page's
|
|
91
94
|
`bootstrap` sends them once, before its own page view (since 3.9). So does a page whose settings
|
|
92
95
|
request never answered: the API down, or the request cancelled by the navigation (3.9.1). The carry is a device write,
|
|
@@ -175,6 +178,13 @@ and `productAdded` (your search engine's id for the result set, such as Algolia'
|
|
|
175
178
|
`newsletterSubscribed`. A search page sends `productsSearched` with its results and never a
|
|
176
179
|
`productListViewed` for the same grid.
|
|
177
180
|
|
|
181
|
+
Since 3.19 (spec 1.12), a search box's suggestions are their own event:
|
|
182
|
+
|
|
183
|
+
| Method | Event | Notes |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `productsSearched` | Products Searched | A submitted search and the results page it produced: `query`, the results in `products`, and `pagination` with `total_results` |
|
|
186
|
+
| `searchSuggestionsViewed` | Search Suggestions Viewed | The products a search box suggested for a partly typed `query`, sent once per suggestion set with the products seen (`position` in the suggestions). Give each set its own `search_request_id`, and a click on one of its products carries that id on `productClicked`. Never `productsSearched`: suggestions and results are ranked by different models and are measured apart |
|
|
187
|
+
|
|
178
188
|
## Options
|
|
179
189
|
|
|
180
190
|
| Option | Default | |
|
|
@@ -187,16 +197,17 @@ and `productAdded` (your search engine's id for the result set, such as Algolia'
|
|
|
187
197
|
| `shipToCountry`, `getShipToCountry` | `null` | ISO-3166 alpha-2 destination, per event. |
|
|
188
198
|
| `locale`, `getLocale` | `null` | The storefront locale served (BCP-47), per event. |
|
|
189
199
|
| `country` | `null` | Visitor country; the ingest derives it at the edge and overwrites this. |
|
|
190
|
-
| `consent` | none | A consent adapter (see below). Makes the SDK strict. |
|
|
191
|
-
| `consentTimeoutMs` | `5000` | How long
|
|
200
|
+
| `consent` | none | A consent adapter (see below). Makes the SDK strict. Without one the SDK sends under your lawful basis (`basis: 'host'`) and says so once in the console (3.19). |
|
|
201
|
+
| `consentTimeoutMs` | `5000` | How long the CMP has to answer before the status is `unknown`. Events stay held, in memory, until it answers. |
|
|
192
202
|
| `consentCookieDomain` | host only | `Domain` for the consent cookie. |
|
|
193
203
|
| `onConsentStatus` | none | Called with the gate's status: `granted`, `denied` or `unknown` (timed out). |
|
|
194
204
|
| `storageNames` | `thg_hp_*` | Rename what the SDK stores; see [Storage](#storage). |
|
|
195
205
|
| `plan` | none | A Segment tracking plan, `{ track: { [event]: { enabled } } }`; `__default` covers unlisted events. |
|
|
206
|
+
| `sampleRate` | `1` | The share of visitors to send for, by a hash of the anonymous id salted `hp_sample`; a visitor with no anonymous id is sent for. A consent withdrawal is always sent. Since 3.18. |
|
|
196
207
|
| `queryParamAllowlist` | none | Keep only these query params (plus campaign params) in `context.page`. Patterns are anchored and case-insensitive. |
|
|
197
208
|
| `queryParamBlocklist` | `[]` | Strip these params. Ignored when an allowlist is given. |
|
|
198
209
|
| `sensitiveParamAllowlist` | `[]` | Whatever else is configured, a param is always dropped from every reported URL when its name contains `email`, `token`, `password`, `secret`, `auth`, `session`, `phone`, `address`, `card`, `customerid` and the like (see `SENSITIVE_PARAM_FRAGMENTS` in `src/context.js`), or when its value is an email address or a card-length number — campaign params and the referrer included. This exempts names from the first rule, for a false positive such as an `author` filter; nothing exempts a value. |
|
|
199
|
-
| `getAnonymousId`, `getUserId`, `getSessionId`, `getRenderId` | none | Identity accessors. Without `getSessionId` the SDK keeps its own session (see [Session ids](#session-ids));
|
|
210
|
+
| `getAnonymousId`, `getUserId`, `getSessionId`, `getRenderId` | none | Identity accessors. Without `getSessionId` the SDK keeps its own session (see [Session ids](#session-ids)); a missing or empty device id gives way to the SDK's own (see [Device ids](#device-ids)). |
|
|
200
211
|
| `getExperiments`, `getAudiences`, `getDecisions`, `getRecommendations`, `getExtensions` | none | Read per event into the matching `context` field. Without `getDecisions`, `context.decisions` is what `recordExposure` last recorded per slot. |
|
|
201
212
|
| `pageType` | `null` | `context.page.type` before the first `page()` call. |
|
|
202
213
|
| `onEvent` | none | Observer for every event that will be sent. |
|
|
@@ -207,8 +218,36 @@ and `productAdded` (your search engine's id for the result set, such as Algolia'
|
|
|
207
218
|
The client has one method per browser event in the spec, plus `track`, `page`, `identify`,
|
|
208
219
|
`reset`, `signedOut`, `flush`, `servingHeaders`, and the read-only `consent` and
|
|
209
220
|
`lastPageMessageId`. Every method takes an optional last argument of context overrides, either as
|
|
210
|
-
the overrides or as `{ context }`. An event method returns the event, or null when
|
|
211
|
-
plan disables it
|
|
221
|
+
the overrides or as `{ context }`. An event method returns the event, or null when it is not sent
|
|
222
|
+
(the tracking plan disables it, the visitor is outside the sample, or `beforeSend` drops it).
|
|
223
|
+
Declarations are in `types/`, and event properties are typed from the registry.
|
|
224
|
+
|
|
225
|
+
- **No method throws into your page** (3.19). A call that fails, whatever it was passed, logs a
|
|
226
|
+
warning and answers what a client that cannot decide answers, failing closed: an event method
|
|
227
|
+
null, `inHoldout()` true, `personalisationGroup()` `'excluded'`, `servingHeaders()` and
|
|
228
|
+
`recommendationContext()` `{}`, `flush()` a resolved promise. The one exception is
|
|
229
|
+
`throwOnInvalid`, which you ask for in development.
|
|
230
|
+
- **One event is at most 55 KB** of JSON (3.19), so that it can always travel in a keepalive request
|
|
231
|
+
at unload. A bigger one is not sent and its method returns null; with `debug` the SDK logs it.
|
|
232
|
+
Segment's limit is 32 KB; this one is higher so that a long product list still fits.
|
|
233
|
+
- **A sign-out forgets the visit** (3.19). `signedOut()`, or `reset()` while a user is signed in,
|
|
234
|
+
also forgets the SDK's session, the decisions in force and the recommendation context, so the
|
|
235
|
+
next person on a shared device inherits none of them. `reset()` with nobody signed in changes
|
|
236
|
+
nothing else, so a host that calls it on every page keeps its session. The SDK's own device id
|
|
237
|
+
starts afresh too, as Segment's `analytics.reset()` does; a device id from the site's own cookie
|
|
238
|
+
(`getAnonymousId`, or a `site_cookie` source) stays, because the site owns it. On one page,
|
|
239
|
+
`identify` with a different `userId` than an earlier `identify` named is a sign-out first; the
|
|
240
|
+
same user again, a first `identify`, a `getUserId` cookie or bootstrap's `identity` option of
|
|
241
|
+
another form changes nothing (with `debug`, a mismatch with `identity` is logged). Across
|
|
242
|
+
pages, call `signedOut()` or `reset()` at the sign-out.
|
|
243
|
+
- **A refusal forgets the visit at once** (3.19): the kept recommendation context and the session
|
|
244
|
+
signals go when consent is refused or personalisation withdrawn, even if `beforeSend` drops the
|
|
245
|
+
`Consent Updated` event. Before the client starts, `hp.recommendationContext()` reads the kept
|
|
246
|
+
visit only when the consent cookie grants personalisation.
|
|
247
|
+
- **Retries wait out `Retry-After`** on a 429 or 5xx (3.19), as well as backing off exponentially
|
|
248
|
+
with jitter, up to 30 seconds. The browser lets the SDK read the header only from a first-party
|
|
249
|
+
proxy for now: cross-origin it needs `Access-Control-Expose-Headers: Retry-After`, which the HP
|
|
250
|
+
ingest does not send yet. Without it the backoff alone applies.
|
|
212
251
|
|
|
213
252
|
## Calling the serving API
|
|
214
253
|
|
|
@@ -361,6 +400,19 @@ and put nothing in force: the trader is not a shopper, so must neither train a s
|
|
|
361
400
|
readout. Every other event is sent as usual. `?hp_preview=` with no value ends the preview. The
|
|
362
401
|
SDK never checks the token: serving does, and ignores one that has expired.
|
|
363
402
|
|
|
403
|
+
### Visit traits
|
|
404
|
+
|
|
405
|
+
A session audience's rules name fields the SDK keeps for the visit, from the events it sends:
|
|
406
|
+
`productViews`, `listViews`, `cartAdds`, `searches`, `browseViews.<path>`, `browseShare.<path>`,
|
|
407
|
+
`categoryViews.<path>`, `categoryShare.<path>` and, since 3.18, `basketValue`. A field this build
|
|
408
|
+
does not know fails its rule closed.
|
|
409
|
+
|
|
410
|
+
`basketValue` is the `value` of the last `cartUpdated` (Cart Updated), in that event's currency:
|
|
411
|
+
the basket as your platform priced it. It is never summed from `productAdded` lines, whose prices
|
|
412
|
+
can include tax where `value` does not. It is `null`, so every rule on it fails, until the visit
|
|
413
|
+
sends a Cart Updated, and again after one without a `value`. Send `cartUpdated` after every confirmed basket change, as the spec asks, or
|
|
414
|
+
a basket audience goes stale.
|
|
415
|
+
|
|
364
416
|
### Audiences only some integrations may target
|
|
365
417
|
|
|
366
418
|
Since 3.8, only audiences marked Targetable in the console are membership. Pass the membership
|
|
@@ -378,7 +430,8 @@ them, so `context.audiences` and exposures carry targetable audiences only. Befo
|
|
|
378
430
|
audience matches nobody, and a shipped untargetable visit audience the visitor meets may be reported
|
|
379
431
|
as membership. Every other audience resolves as it did.
|
|
380
432
|
|
|
381
|
-
Social proof has a client of its own, which batches
|
|
433
|
+
Social proof has a client of its own, which batches and caches, and never throws once created
|
|
434
|
+
(creating it without a `writeKey` or with an invalid `apiHost` throws):
|
|
382
435
|
|
|
383
436
|
```js
|
|
384
437
|
const proof = createSocialProof({ writeKey, getConsent: () => hp.consent });
|
|
@@ -397,8 +450,9 @@ With a `consent` adapter the SDK is strict:
|
|
|
397
450
|
- **Analytics granted:** held events are sent, each carrying the answer in `context.consent`.
|
|
398
451
|
- **Analytics refused:** held events are dropped, and so is everything after. Nothing goes into
|
|
399
452
|
local or session storage.
|
|
400
|
-
- **No answer by the timeout:** held and later events
|
|
401
|
-
|
|
453
|
+
- **No answer by the timeout:** the status is `unknown`, and held and later events stay held, in
|
|
454
|
+
memory, while the SDK keeps listening for the answer. Strict is the only mode: an unanswered CMP
|
|
455
|
+
is never a grant, however long it takes.
|
|
402
456
|
- **Page closed before an answer:** nothing is sent. Switching to another tab is not closing the
|
|
403
457
|
page: held events wait for the visitor to come back.
|
|
404
458
|
- **A navigation before an answer** (a sign-in that redirects) loses what the page held, unless
|
|
@@ -482,6 +536,22 @@ cookie with `storageNames.consent` gets the same name read back by `"forwarded"`
|
|
|
482
536
|
pass the same `storageNames`, or passes `forwardedConsent({ name })` directly. No cookie, or no analytics answer in it,
|
|
483
537
|
means not answered, and strict mode then drops the events.
|
|
484
538
|
|
|
539
|
+
A checkout on **another registrable domain** never receives that cookie. Carry it in the checkout
|
|
540
|
+
handoff your servers already make:
|
|
541
|
+
|
|
542
|
+
1. The storefront's server reads `thg_hp_consent` from the shopper's request and puts it on the
|
|
543
|
+
checkout session it creates.
|
|
544
|
+
2. On every page it renders, checkout's server sets `thg_hp_consent_handoff` on its own host
|
|
545
|
+
from the session. The value is `formatConsentCookie(parseConsentCookie(value))` (from
|
|
546
|
+
`@thg-agentic/hyper-personalisation-web/consent`), so an unreadable value is unknown, never a
|
|
547
|
+
grant. The cookie is not `HttpOnly`. With no value it deletes the cookie.
|
|
548
|
+
3. The checkout page boots with `storageNames: { consent: 'thg_hp_consent_handoff' }` and
|
|
549
|
+
`"forwarded"`. Use a name of its own: a checkout host that also serves a storefront on its
|
|
550
|
+
own domain sees that storefront's `thg_hp_consent` first, and a delete cannot remove it.
|
|
551
|
+
|
|
552
|
+
Hand the device cookie over the same way if checkout's events should land on the storefront's
|
|
553
|
+
visitor. Never put the value on the checkout URL: it would end up in access logs and referrers.
|
|
554
|
+
|
|
485
555
|
## GA4 dataLayer
|
|
486
556
|
|
|
487
557
|
```js
|
|
@@ -630,8 +700,11 @@ delivery differs.
|
|
|
630
700
|
removed when the ingest accepts it (or rejects it for good), so an outage costs a delay, not the
|
|
631
701
|
events. The buffer is per tenant and capped at 20 batches and 1 MB, oldest dropped first.
|
|
632
702
|
- On page unload, sends by `sendBeacon` in chunks that fit.
|
|
633
|
-
-
|
|
634
|
-
is
|
|
703
|
+
- Never throws into the page. An event JSON cannot serialise (a BigInt, a circular object, a
|
|
704
|
+
throwing `toJSON`) is dropped on its own, logged under `debug`, and the queue behind it carries
|
|
705
|
+
on. A `Date` in properties is sent as its ISO string.
|
|
706
|
+
- Size: the CDN script is `bootstrap` with every consent adapter and the dataLayer bridge. A test
|
|
707
|
+
fails it past 90 KB gzip, and the inline snippet past 1 KB; the budget catches a runaway bundle.
|
|
635
708
|
|
|
636
709
|
**analytics-next plugins** (`cdpPlugins` from `./segment`)
|
|
637
710
|
- For sites already using `@segment/analytics-next`: keep it, and add the spec on top of it.
|
|
@@ -687,7 +760,7 @@ to Segment.io after a withdrawal.
|
|
|
687
760
|
|
|
688
761
|
## CDN script
|
|
689
762
|
|
|
690
|
-
For a site without a bundler, `dist/hp.min.js` is `bootstrap` as one script
|
|
763
|
+
For a site without a bundler, `dist/hp.min.js` is `bootstrap` as one script, and
|
|
691
764
|
`dist/snippet.min.js` is the loader to paste inline in `<head>`, Segment-style: it puts a stub on
|
|
692
765
|
`window.hp` that queues every call, loads the script asynchronously, and the script drains the
|
|
693
766
|
queue into the client once the settings have answered.
|
|
@@ -732,6 +805,7 @@ SDK per page.
|
|
|
732
805
|
| `anonymousId` | `thg_hp_anonymous_id` | cookie, one per browser | Hyper-Personalisation's device id, for a source set to `hp_cookie`; only under a grant (see [Device ids](#device-ids)) |
|
|
733
806
|
| `session` | `thg_hp_session` | cookie, one per browser | the session id the SDK keeps when you name none, 30 minutes from the last event; only under a grant (3.15, see [Session ids](#session-ids)) |
|
|
734
807
|
| `preview` | `thg_hp_preview` | session cookie | a trader's preview token from `?hp_preview=` (3.15, see [Preview](#preview)) |
|
|
808
|
+
| `debug` | `thg_hp_debug` | sessionStorage | a developer's debug token from `?hp_debug=` (3.18), checked against the settings on every page |
|
|
735
809
|
| — | `thg_hp_conformance_page_type` | sessionStorage | the previous page's type, only while the development-only conformance checker runs |
|
|
736
810
|
|
|
737
811
|
Keys are scoped by property because one origin can serve several tenants. The names the SDK used
|
|
@@ -742,12 +816,12 @@ moved to the new names once, on the first page that loads 3.x, and deleted. A ba
|
|
|
742
816
|
### Device ids
|
|
743
817
|
|
|
744
818
|
Each web source takes its device ids (`anonymousId`) from one place, chosen in the console and
|
|
745
|
-
served in the settings as `hp.anonymousId`.
|
|
746
|
-
|
|
819
|
+
served in the settings as `hp.anonymousId`. Since 3.18 an event never leaves without one: when the
|
|
820
|
+
site's id is missing, empty or a placeholder, the SDK's own cookie stands in, as analytics.js does.
|
|
747
821
|
|
|
748
822
|
| `hp.anonymousId` | The device id | |
|
|
749
823
|
|---|---|---|
|
|
750
|
-
| `{ source: 'site_cookie', cookie: 'site_visitor_id' }` | your own first-party cookie, by the name the console gives | read only
|
|
824
|
+
| `{ source: 'site_cookie', cookie: 'site_visitor_id' }` | your own first-party cookie, by the name the console gives | read only; a visitor without it gets the SDK's `thg_hp_anonymous_id` (3.18; before, no `anonymousId`) |
|
|
751
825
|
| `{ source: 'site_cookie', cookie: null }` | the cookie you name in `identityCookies.anonymousId` (or return from `getAnonymousId`) | as above. Every web source that existed before the setting is set to this |
|
|
752
826
|
| `{ source: 'hp_cookie' }` | Hyper-Personalisation's first-party cookie, `thg_hp_anonymous_id` | minted by the SDK; your own device cookie is ignored. The default for a new source |
|
|
753
827
|
| absent (settings from before 3.6) | as `site_cookie` with `cookie: null` | |
|
|
@@ -757,8 +831,11 @@ The `hp_cookie` cookie is one per browser, not per property. It is written with
|
|
|
757
831
|
`Max-Age` is 400 days, and it is written again on every page under a grant, so it lasts 400 days
|
|
758
832
|
from the last visit. With a consent adapter it is never written before analytics is granted: until
|
|
759
833
|
then the id lives in memory, and in the held store with `holdAcrossPages`, so a redirect keeps it.
|
|
760
|
-
A refusal or withdrawal deletes it
|
|
761
|
-
|
|
834
|
+
A refusal or withdrawal deletes it, and the id with it: a later grant, on the same page or another,
|
|
835
|
+
starts a new device, as Segment's `analytics.reset()` does in analytics.js (3.18). A sign-out
|
|
836
|
+
(`signedOut()`, or `reset()` with a user signed in) deletes it too, and the next event mints a new
|
|
837
|
+
one (3.19). Without a consent adapter you have decided the lawful basis yourself, so it is written
|
|
838
|
+
at once.
|
|
762
839
|
|
|
763
840
|
Changing a source's choice gives every returning visitor a new device id. A device id 3.5 minted
|
|
764
841
|
into localStorage (`thg_hp_anonymous_id:<property>`) becomes the cookie's value under a grant, and
|
|
@@ -769,9 +846,8 @@ handed the same one.
|
|
|
769
846
|
### Session ids
|
|
770
847
|
|
|
771
848
|
Every event carries `context.session_id`. Name your own session cookie in
|
|
772
|
-
`identityCookies.sessionId`, or pass `getSessionId`, and the SDK
|
|
773
|
-
|
|
774
|
-
its own, as Amplitude and GA4 do: a random UUID in the `thg_hp_session` cookie, ended by 30 minutes
|
|
849
|
+
`identityCookies.sessionId`, or pass `getSessionId`, and the SDK reads it. Without either (since
|
|
850
|
+
3.15), or while it is missing or empty (since 3.18), the SDK keeps its own, as Amplitude and GA4 do: a random UUID in the `thg_hp_session` cookie, ended by 30 minutes
|
|
775
851
|
without an event. Its `Max-Age` is 30 minutes and every event writes it again, so it is shared by
|
|
776
852
|
the site's tabs and ends on its own. It is written with the attributes of the device cookie. With a
|
|
777
853
|
consent adapter it is never written before analytics is granted (until then the session lives in
|
|
@@ -781,24 +857,21 @@ memory), and a refusal deletes it. Before 3.15 such a site sent `session_id: nul
|
|
|
781
857
|
|
|
782
858
|
The SDK makes requests only to `apiHost` (the settings, and the events unless the settings name
|
|
783
859
|
another host), so allow it in `connect-src`: `resolveApiHost(apiHost)` is that origin, the HP API
|
|
784
|
-
(`DEFAULT_API_HOST`) when you set none. Since 3.
|
|
785
|
-
(`connect-src https://hp.
|
|
786
|
-
being retired. Allow the CDN host in `script-src` too if you use the script. Call the API host directly rather than through a CDN proxy route: behind a proxy the ingest
|
|
860
|
+
(`DEFAULT_API_HOST`) when you set none. Since 3.18 that is `https://hp.thgingenuity.com`
|
|
861
|
+
(`connect-src https://hp.thgingenuity.com`); 3.17 used HP's nonprod host and 3.16 and earlier a
|
|
862
|
+
staging host that is being retired. Allow the CDN host in `script-src` too if you use the script. Call the API host directly rather than through a CDN proxy route: behind a proxy the ingest
|
|
787
863
|
would see the CDN's IP and country instead of the shopper's.
|
|
788
864
|
|
|
789
|
-
### Default host
|
|
865
|
+
### Default host by release
|
|
790
866
|
|
|
791
867
|
| Release | `DEFAULT_API_HOST` | For |
|
|
792
868
|
|---|---|---|
|
|
793
869
|
| 3.12 to 3.16 | A staging host | Being retired. Upgrade. |
|
|
794
|
-
| 3.17 | `https://hp.uat.thgingenuity.com` | Interim: HP's nonprod stack. Test keys
|
|
795
|
-
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
and it sends nothing, which never breaks the page. Use it for UAT and preview builds with a
|
|
800
|
-
`hp_pk_test_…` key. **Do not ship a live key to production on 3.17**: go
|
|
801
|
-
live on the release that moves the default to `hp.thgingenuity.com`, whose notes will say so.
|
|
870
|
+
| 3.17 | `https://hp.uat.thgingenuity.com` | Interim: HP's nonprod stack. Test keys only. |
|
|
871
|
+
| 3.18 | `https://hp.thgingenuity.com` | Production, for test and live keys alike. Go live on this. |
|
|
872
|
+
|
|
873
|
+
The key, not the host, keeps test traffic out of live results, so UAT and preview builds use the
|
|
874
|
+
same host with a `hp_pk_test_…` key. Upgrade from 3.17 before shipping a live key.
|
|
802
875
|
|
|
803
876
|
The SDK loads no scripts and sets no cookie except `thg_hp_consent`, once the CMP has answered,
|
|
804
877
|
and `thg_hp_anonymous_id` for a source set to `hp_cookie`, once analytics is granted.
|