@thg-agentic/hyper-personalisation-web 3.18.0 → 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 +46 -8
- package/dist/hp.min.js +3 -3
- package/dist/snippet.min.js +1 -1
- package/package.json +2 -2
- package/src/bootstrap.js +7 -4
- package/src/consent-gate.js +8 -2
- package/src/device-storage.js +3 -2
- package/src/guard.js +46 -0
- package/src/identity-option.js +6 -0
- package/src/index.js +125 -15
- package/src/recommendation-context.js +10 -1
- package/src/session/record.js +10 -9
- package/src/spec-version.js +1 -1
- package/src/transport.js +26 -3
- package/src/version.js +1 -1
- package/types/consent.d.ts +4 -6
- package/types/events.d.ts +44 -2
- package/types/index.d.ts +17 -17
package/README.md
CHANGED
|
@@ -178,6 +178,13 @@ and `productAdded` (your search engine's id for the result set, such as Algolia'
|
|
|
178
178
|
`newsletterSubscribed`. A search page sends `productsSearched` with its results and never a
|
|
179
179
|
`productListViewed` for the same grid.
|
|
180
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
|
+
|
|
181
188
|
## Options
|
|
182
189
|
|
|
183
190
|
| Option | Default | |
|
|
@@ -190,8 +197,8 @@ and `productAdded` (your search engine's id for the result set, such as Algolia'
|
|
|
190
197
|
| `shipToCountry`, `getShipToCountry` | `null` | ISO-3166 alpha-2 destination, per event. |
|
|
191
198
|
| `locale`, `getLocale` | `null` | The storefront locale served (BCP-47), per event. |
|
|
192
199
|
| `country` | `null` | Visitor country; the ingest derives it at the edge and overwrites this. |
|
|
193
|
-
| `consent` | none | A consent adapter (see below). Makes the SDK strict. |
|
|
194
|
-
| `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. |
|
|
195
202
|
| `consentCookieDomain` | host only | `Domain` for the consent cookie. |
|
|
196
203
|
| `onConsentStatus` | none | Called with the gate's status: `granted`, `denied` or `unknown` (timed out). |
|
|
197
204
|
| `storageNames` | `thg_hp_*` | Rename what the SDK stores; see [Storage](#storage). |
|
|
@@ -211,8 +218,36 @@ and `productAdded` (your search engine's id for the result set, such as Algolia'
|
|
|
211
218
|
The client has one method per browser event in the spec, plus `track`, `page`, `identify`,
|
|
212
219
|
`reset`, `signedOut`, `flush`, `servingHeaders`, and the read-only `consent` and
|
|
213
220
|
`lastPageMessageId`. Every method takes an optional last argument of context overrides, either as
|
|
214
|
-
the overrides or as `{ context }`. An event method returns the event, or null when
|
|
215
|
-
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.
|
|
216
251
|
|
|
217
252
|
## Calling the serving API
|
|
218
253
|
|
|
@@ -415,8 +450,9 @@ With a `consent` adapter the SDK is strict:
|
|
|
415
450
|
- **Analytics granted:** held events are sent, each carrying the answer in `context.consent`.
|
|
416
451
|
- **Analytics refused:** held events are dropped, and so is everything after. Nothing goes into
|
|
417
452
|
local or session storage.
|
|
418
|
-
- **No answer by the timeout:** held and later events
|
|
419
|
-
|
|
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.
|
|
420
456
|
- **Page closed before an answer:** nothing is sent. Switching to another tab is not closing the
|
|
421
457
|
page: held events wait for the visitor to come back.
|
|
422
458
|
- **A navigation before an answer** (a sign-in that redirects) loses what the page held, unless
|
|
@@ -796,8 +832,10 @@ The `hp_cookie` cookie is one per browser, not per property. It is written with
|
|
|
796
832
|
from the last visit. With a consent adapter it is never written before analytics is granted: until
|
|
797
833
|
then the id lives in memory, and in the held store with `holdAcrossPages`, so a redirect keeps it.
|
|
798
834
|
A refusal or withdrawal deletes it, and the id with it: a later grant, on the same page or another,
|
|
799
|
-
starts a new device, as `analytics.reset()` does in analytics.js (3.18).
|
|
800
|
-
|
|
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.
|
|
801
839
|
|
|
802
840
|
Changing a source's choice gives every returning visitor a new device id. A device id 3.5 minted
|
|
803
841
|
into localStorage (`thg_hp_anonymous_id:<property>`) becomes the cookie's value under a grant, and
|
package/dist/hp.min.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
(()=>{var Pt=e=>{throw TypeError(e)};var ir=(e,t,n)=>t.has(e)||Pt("Cannot "+n);var Lt=(e,t,n)=>t.has(e)?Pt("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n);var N=(e,t,n)=>(ir(e,t,"access private method"),n);var ge=[];for(let e=2;ge.length<64;e++)ge.every(t=>e%t!==0)&&ge.push(e);var Ot=e=>(e-Math.floor(e))*2**32>>>0,ar=ge.slice(0,8).map(e=>Ot(Math.sqrt(e))),cr=ge.map(e=>Ot(Math.cbrt(e))),z=(e,t)=>e>>>t|e<<32-t;function Nt(e){let t=new TextEncoder().encode(String(e)),n=new Uint32Array((t.length+8>>6)*16+16);t.forEach((s,i)=>{n[i>>2]|=s<<24-i%4*8}),n[t.length>>2]|=128<<24-t.length%4*8,n[n.length-2]=Math.floor(t.length/2**29),n[n.length-1]=t.length*8;let r=Uint32Array.from(ar),o=new Uint32Array(64);for(let s=0;s<n.length;s+=16){for(let g=0;g<64;g++){if(g<16){o[g]=n[s+g];continue}let _=o[g-15],x=o[g-2],v=z(_,7)^z(_,18)^_>>>3,h=z(x,17)^z(x,19)^x>>>10;o[g]=o[g-16]+v+o[g-7]+h}let[i,a,u,d,p,f,m,b]=r;for(let g=0;g<64;g++){let _=z(p,6)^z(p,11)^z(p,25),x=b+_+(p&f^~p&m)+cr[g]+o[g]|0,h=(z(i,2)^z(i,13)^z(i,22))+(i&a^i&u^a&u)|0;[b,m,f,p,d,u,a,i]=[m,f,p,d+x|0,u,a,i,x+h|0]}[i,a,u,d,p,f,m,b].forEach((g,_)=>{r[_]+=g})}return Array.from(r,s=>s.toString(16).padStart(8,"0")).join("")}var He=(e,t)=>e<<t|e>>>32-t;function Tt(e){let t=new Uint32Array((e.length+8>>6)*16+16);e.forEach((s,i)=>{t[i>>2]|=s<<24-i%4*8}),t[e.length>>2]|=128<<24-e.length%4*8,t[t.length-2]=Math.floor(e.length/2**29),t[t.length-1]=e.length*8;let n=Uint32Array.of(1732584193,4023233417,2562383102,271733878,3285377520),r=new Uint32Array(80);for(let s=0;s<t.length;s+=16){for(let f=0;f<80;f++)r[f]=f<16?t[s+f]:He(r[f-3]^r[f-8]^r[f-14]^r[f-16],1);let[i,a,u,d,p]=n;for(let f=0;f<80;f++){let m,b;f<20?(m=a&u|~a&d,b=1518500249):f<40?(m=a^u^d,b=1859775393):f<60?(m=a&u|a&d|u&d,b=2400959708):(m=a^u^d,b=3395469782);let g=He(i,5)+m+p+b+r[f]|0;[p,d,u,a,i]=[d,u,He(a,30),i,g]}[i,a,u,d,p].forEach((f,m)=>{n[m]+=f})}let o=new Uint8Array(20);return n.forEach((s,i)=>{o[i*4]=s>>>24,o[i*4+1]=s>>>16,o[i*4+2]=s>>>8,o[i*4+3]=s}),o}var ur="e28e8ada-5b76-4209-bba9-79b823a0af75",lr=e=>Array.from(e,t=>t.toString(16).padStart(2,"0")).join("");function Rt(e,t){let n=Uint8Array.from(e.replace(/-/g,"").match(/../g),a=>Number.parseInt(a,16)),r=new TextEncoder().encode(t),o=new Uint8Array(16+r.length);o.set(n),o.set(r,16);let s=Tt(o).slice(0,16);s[6]=s[6]&15|80,s[8]=s[8]&63|128;let i=lr(s);return`${i.slice(0,8)}-${i.slice(8,12)}-${i.slice(12,16)}-${i.slice(16,20)}-${i.slice(20)}`}var ee=(e,t)=>typeof e=="string"&&e!==""&&e.length<=t?e:null;function be(e,t){return ee(e,64)??ee(t,256)}function Dt({sessionId:e,slot:t,arm:n}={}){let r=ee(e,256);return!r||!ee(t,256)||!ee(n,256)?null:Rt(ur,`${r}:${t}:${n}`)}var dr="4b1f2c7e-9a3d-4e58-b6c1-2d7f0a9e8c53";function Mt({sessionId:e,surface:t}={}){let n=ee(e,256);return!n||!ee(t,64)?null:Rt(dr,`${n}:${t}`)}var fr=Array.from({length:11},(e,t)=>t/10);function pr(e,t){if(!e.isIntersecting)return!1;let{intersectionRect:n,boundingClientRect:r,rootBounds:o}=e;if(!n||!r)return!0;let s=n.width*n.height,i=r.width*r.height,a=o?Math.min(i,o.width*o.height):i;return a>0&&s/a>=t}function Y(e){let{selector:t,onViewable:n,visibleRatio:r=.5,dwellMs:o=1e3,root:s=document}=e,i=new Map,a=new WeakSet,u=new Set,d=h=>{i.has(h)||i.set(h,setTimeout(()=>{i.delete(h),n(h)&&(f?.unobserve(h),u.delete(h))},o))},p=h=>{clearTimeout(i.get(h)),i.delete(h)},f=typeof IntersectionObserver>"u"?null:new IntersectionObserver(h=>{for(let E of h){let S=E.target;if(!pr(E,r)){u.delete(S),p(S);continue}u.add(S),m()&&d(S)}},{threshold:fr}),m=()=>typeof document>"u"||document.visibilityState!=="hidden",b=()=>{if(m())for(let h of u)d(h);else for(let h of u)p(h)};document.addEventListener("visibilitychange",b);let g=()=>{for(let h of s.querySelectorAll(t))a.has(h)||(a.add(h),f?.observe(h))},_=typeof MutationObserver>"u"?null:new MutationObserver(()=>g()),x=()=>{g(),_?.observe(s===document?document.body:s,{childList:!0,subtree:!0})},v=s===document&&!document.body;return v?document.addEventListener("DOMContentLoaded",x,{once:!0}):x(),()=>{v&&document.removeEventListener("DOMContentLoaded",x),document.removeEventListener("visibilitychange",b),f?.disconnect(),_?.disconnect();for(let h of i.values())clearTimeout(h);i.clear(),u.clear()}}function Ke(e,t={}){if(typeof window>"u"||typeof document>"u")return()=>{};let{selector:n="[data-hp-exposure]",visibleRatio:r=.5,dwellMs:o=1e3,root:s=document}=t;return Y({selector:n,root:s,visibleRatio:r,dwellMs:o,onViewable(i){let a=i.dataset?.hpExposure?.trim();return a&&e.surfaceViewed({surface:a}),!0}})}var Ut=e=>{try{return new URL(e,document.baseURI).toString()}catch{return e}};function Ge(e,t={}){if(typeof window>"u"||typeof document>"u")return()=>{};let{selector:n="[data-hp-list]",tileSelector:r="[data-hp-product]",visibleRatio:o=.5,dwellMs:s=1e3,root:i=document}=t,a=new Set,u=g=>g.closest?.(n)??null,d=g=>{let _=g.dataset.hpList?.trim();return _?{list_id:_,category:g.dataset.hpListCategory?.trim()||null}:null},p=g=>Array.from(g.querySelectorAll(r)).filter(_=>u(_)===g),f=(g,_)=>{try{let x=JSON.parse(g.dataset.hpProduct??"");return!x||typeof x!="object"||!x.product_id?null:{...x,position:x.position??_+1,...x.url?{url:Ut(x.url)}:{},...x.image_url?{image_url:Ut(x.image_url)}:{}}}catch{return null}},m=Y({selector:`${n} ${r}`,root:i,visibleRatio:o,dwellMs:s,onViewable(g){let _=u(g);if(!_||_.dataset.hpListView==="manual")return!0;let x=d(_);if(!x||a.has(x.list_id))return!0;let v=new Set,h=p(_).map(f).filter(E=>!E||v.has(E.product_id)?!1:(v.add(E.product_id),!0));return h.length===0||(a.add(x.list_id),e.productListViewed({...x,products:h})),!0}}),b=g=>{let x=g.target?.closest?.("a[href]")?.closest(r);if(!x)return;let v=u(x),h=f(x,v?p(v).indexOf(x):0);h&&e.productClicked({list_id:v?d(v)?.list_id??null:null,products:[h]})};return document.addEventListener("click",b,!0),()=>{document.removeEventListener("click",b,!0),m()}}function T(e,...t){let n=e.dataset??{};for(let r of t){let o=n[r];if(typeof o=="string"&&o.trim()!=="")return o.trim()}return null}function xe(e,...t){let n=T(e,...t);if(n===null)return null;let r=Number(n);return Number.isFinite(r)?Math.trunc(r):null}function mr(e,t){let n=T(e,"hpPromotion");return n?{promotion_id:n,name:T(e,"hpPromotionName"),creative:T(e,"hpPromotionCreative"),position:T(e,"hpPromotionSlot")??String(t+1)}:null}function We(e,t={}){if(typeof window>"u"||typeof document>"u")return()=>{};let{selector:n="[data-hp-promotion]",resolve:r=mr,visibleRatio:o=.5,dwellMs:s=1e3,root:i=document}=t,a=new Set,u=f=>{let m=Array.from(i.querySelectorAll(n)),b=m.indexOf(f);try{return r(f,b<0?m.length:b)}catch{return null}},d=Y({selector:n,root:i,visibleRatio:o,dwellMs:s,onViewable(f){let m=u(f);return!m||a.has(m.promotion_id)?!1:(a.add(m.promotion_id),e.promotionViewed(m),!0)}}),p=f=>{let m=f.target;if(!m||typeof m.closest!="function")return;let b=m.closest(n),g=m.closest("a[href]");if(!b||!g||!b.contains(g)&&!g.contains(b))return;let _=u(b);_&&e.promotionClicked({..._,href:g.getAttribute("href")})};return document.addEventListener("click",p,!0),()=>{document.removeEventListener("click",p,!0),d()}}function hr(e){let t=T(e,"hpRecoRequest"),n=T(e,"hpRecoPlacement"),r=T(e,"hpRecoStrategy");if(!t||!n||!r)return null;let o=T(e,"hpRecoHoldout");return{recommendation_request_id:t,placement:n,strategy:r,recipe:T(e,"hpRecoRecipe"),algorithm_version:T(e,"hpRecoAlgorithmVersion"),experiment:T(e,"hpRecoExperiment"),variant:T(e,"hpRecoVariant"),holdout:o===null?null:o==="true",assignment_bucket:xe(e,"hpRecoBucket"),assignment_unit_kind:T(e,"hpRecoUnitKind"),candidate_set_fingerprint:T(e,"hpRecoFingerprint"),candidate_set_size:xe(e,"hpRecoCandidates"),ranked_by:T(e,"hpRecoRankedBy"),served_at:T(e,"hpRecoServedAt")}}function gr(e,t){let n=T(e,"hpRecoProduct");if(!n)return null;let r=T(e,"hpRecoPrice");return{product_id:n,sku:T(e,"hpRecoSku"),name:T(e,"hpRecoName"),brand:T(e,"hpRecoBrand"),price:r===null||Number.isNaN(Number(r))?null:Number(r),position:xe(e,"hpRecoPosition")??t+1}}function ze(e,t={}){if(typeof window>"u"||typeof document>"u")return()=>{};let{selector:n="[data-hp-reco-request]",tileSelector:r="[data-hp-reco-product]",resolveRail:o=hr,resolveTile:s=gr,visibleRatio:i=.5,dwellMs:a=1e3,root:u=document}=t,d=new Set,p=new Map,f=new Map,m=(h,...E)=>{try{return h(...E)}catch{return null}},b=h=>typeof h.closest=="function"?h.closest(n):null,g=h=>Array.from(h.querySelectorAll(r)),_=(h,E)=>{if(f.delete(E.recommendation_request_id),d.has(E.recommendation_request_id))return;let S=g(h).map((w,L)=>m(s,w,L)).filter(w=>w?.product_id);if(S.length===0)return;d.add(E.recommendation_request_id);let y=[...p.get(E.recommendation_request_id)?.values()??[]].sort((w,L)=>w.position-L.position);e.recommendationsViewed({...E,products:S,viewable:y})},x=Y({selector:`${n} ${r}`,root:u,visibleRatio:i,dwellMs:a,onViewable(h){let E=b(h);if(!E)return!0;let S=m(o,E);if(!S)return!0;let y=S.recommendation_request_id,w=m(s,h,g(E).indexOf(h));if(w?.product_id){p.has(y)||p.set(y,new Map);let L=p.get(y);L.has(w.position)||L.set(w.position,{position:w.position,product_id:w.product_id,viewable_at:new Date().toISOString()})}return!d.has(y)&&!f.has(y)&&f.set(y,setTimeout(()=>_(E,S),0)),!0}}),v=h=>{let E=h.target;if(!E||typeof E.closest!="function")return;let S=E.closest(r);if(!S)return;let y=E.closest("[data-hp-reco-interaction]"),w=E.closest("a[href]"),L=w&&(S.contains(w)||w.contains(S));if(!(y&&S.contains(y))&&!L)return;let D=b(S);if(!D)return;let H=m(o,D);if(!H)return;let K=m(s,S,g(D).indexOf(S));if(!K?.product_id)return;let F=H.recommendation_request_id;d.has(F)||(clearTimeout(f.get(F)),_(D,H)),e.productClicked({recommendation_request_id:H.recommendation_request_id,interaction:T(y??S,"hpRecoInteraction")??"navigate",products:[K]})};return document.addEventListener("click",v,!0),()=>{document.removeEventListener("click",v,!0);for(let h of f.values())clearTimeout(h);f.clear(),x()}}var ae=new Set(["home","list","product","search","basket","checkout","order_confirmation","login","register","account","info","blog","other"]),yr=new Set(["products"]),$t=e=>e==null;function Je(e,t=new WeakSet){if(e instanceof Date)return e.toJSON();if(e===null||typeof e!="object"||t.has(e))return e;t.add(e);let n=Array.isArray(e)?e.map(r=>Je(r,t)):Object.fromEntries(Object.entries(e).filter(([,r])=>!$t(r)).map(([r,o])=>[r,Je(o,t)]));return t.delete(e),n}function Xe(e){if(!e)return e;let t=Je(e);for(let n of yr)n in e&&$t(e[n])&&(t[n]=e[n]);return t}function Vt(){try{let e=performance.getEntriesByType("navigation")[0];return e?["navigate","reload","back_forward","prerender"].includes(e.type)?e.type:"navigate":null}catch{return null}}var Sr=new Set(["","unknown","undefined","null","none","nil","nan","anonymous","guest","0","-1"]);function B(e){if(e==null)return null;let t=String(e).trim();return Sr.has(t.toLowerCase())?null:t}function Z(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}var jt="1.10";function U(e){if(typeof document>"u")return null;try{let t=document.cookie.split(";").map(n=>n.trim()).find(n=>n.startsWith(`${e}=`));return t&&decodeURIComponent(t.slice(e.length+1))||null}catch{return null}}var wr={gclid:"gclid",fbclid:"fbclid",ttclid:"ttclid"},_r=/[a-z0-9._%+-]+@[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]{2,}/i,br=/\d(?:[ -]?\d){12,}/,xr=new Set(["gclid","fbclid","ttclid","msclkid"]);function qt(e,t=""){if(typeof e!="string"||e==="")return!1;let n=[e];try{let o=decodeURIComponent(e.replace(/\+/g," "));o!==e&&n.push(o)}catch{}let r=!xr.has(String(t).toLowerCase());return n.some(o=>_r.test(o)||r&&br.test(o))}function Ir(e){if(!e)return null;let t=new URLSearchParams(e),n={},r={utm_source:"source",utm_medium:"medium",utm_campaign:"name",utm_term:"term",utm_content:"content"};for(let[o,s]of[...Object.entries(r),...Object.entries(wr)]){let i=t.get(o);i&&!qt(i,o)&&(n[s]=i)}return Object.keys(n).length?n:null}var Ar=["email","e-mail","password","pass","pwd","token","auth","code","uid","customer_id","postcode","zip","phone","applyCode"],kr=["email","password","passwd","pwd","token","secret","auth","session","phone","postcode","zip","address","birth","card","cvv","iban","ssn","customerid","userid"],vr=["tel","uid","dob"],Er=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","gclid","fbclid","ttclid","msclkid"],Cr=new Set(Ar.map(e=>e.toLowerCase()));function Pr(e){let t=String(e).toLowerCase();if(Cr.has(t))return!0;let n=t.replace(/[-_.]/g,"");return kr.some(o=>n.includes(o))?!0:String(e).replace(/([a-z0-9])([A-Z])/g,"$1 $2").toLowerCase().split(/[^a-z0-9]+/).some(o=>vr.includes(o))}function Ye(e){return(e??[]).map(t=>{try{return new RegExp(`^${t}$`,"i")}catch{return{test:n=>n.toLowerCase()===String(t).toLowerCase()}}})}function Ft(e,t={}){if(!e)return null;try{let n=new URLSearchParams(e),r=t.queryParamAllowlist?Ye([...t.queryParamAllowlist,...Er]):null,o=Ye(t.queryParamBlocklist),s=Ye(t.sensitiveParamAllowlist);for(let a of new Set(n.keys()))(Pr(a)&&!s.some(d=>d.test(a))||n.getAll(a).some(d=>qt(d,a))||(r?!r.some(d=>d.test(a)):o.some(d=>d.test(a))))&&n.delete(a);let i=n.toString();return i?`?${i}`:""}catch{return""}}function Lr(e,t,n={}){if(!e)return null;try{let r=new URL(e);return t&&r.origin===t?r.origin+r.pathname+(Ft(r.search,n)??""):r.origin+r.pathname}catch{return null}}function Ze(e){if(typeof e!="string")return null;let t=e.trim().toUpperCase();return/^[A-Z]{3}$/.test(t)?t:null}function Bt(e){if(typeof e!="string")return null;let t=e.trim().toUpperCase();return/^[A-Z]{2}$/.test(t)?t:null}function Or(e){if(typeof e!="string")return null;let t=e.trim().replace(/_/g,"-");if(!/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(t))return null;try{return Intl.getCanonicalLocales(t)[0]}catch{return null}}function M(e,t){if(typeof e=="function")try{let n=e();if(n!=null)return n}catch{}return t}function Nr(e){return{property_id:String(e.propertyId),channel:e.channel??null,market:e.market??null,environment:e.environment??null}}function Tr(e){let t=typeof window<"u"?window.location:null,n=t?Ft(t.search,e):null;return{url:t?t.origin+t.pathname+(n??""):e.pageUrl??null,path:t?t.pathname:e.pagePath??null,search:n??null,referrer:typeof document<"u"?Lr(document.referrer,t?.origin,e):null,title:typeof document<"u"&&document.title||null,type:e.pageType??null}}function Ht(e,t={}){let n=typeof window<"u"?window.location:null,r={library:{name:e.libraryName,version:e.libraryVersion},spec:{version:jt},source:Nr(e),session_id:M(e.getSessionId,null),render_id:M(e.getRenderId,null),identity_trust:B(M(e.getAnonymousId,null))?"device":"none",page:Tr(e),campaign:n?Ir(n.search):null,device:{user_agent:typeof navigator<"u"?navigator.userAgent:null,type:null,viewport_width:typeof window<"u"?window.innerWidth:null,viewport_height:typeof window<"u"?window.innerHeight:null},currency:Ze(M(e.getCurrency,e.currency)),locale:Or(M(e.getLocale,e.locale)),timezone:(()=>{try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return null}})(),country:Bt(e.country),ship_to_country:Bt(M(e.getShipToCountry,e.shipToCountry)),ip:null,bot:null,consent:e.getConsent?.()??null,experiments:M(e.getExperiments,null),audiences:M(e.getAudiences,null),decisions:M(e.getDecisions,null),recommendations:M(e.getRecommendations,null),extensions:M(e.getExtensions,null)};return Kt(r,t)}function Kt(e,t){if(!t)return e;let n={...e};for(let[r,o]of Object.entries(t))o&&typeof o=="object"&&!Array.isArray(o)&&e[r]&&typeof e[r]=="object"&&!Array.isArray(e[r])?n[r]=Kt(e[r],o):o!==void 0&&(n[r]=o);return n}var re=Object.freeze({consent:"thg_hp_consent",retry:"thg_hp_retry",sessionSignals:"thg_hp_session_signals",identified:"thg_hp_identified",previousPage:"thg_hp_prev_page",decisions:"thg_hp_decisions",settings:"thg_hp_settings",held:"thg_hp_held",carried:"thg_hp_carried",recommendationContext:"thg_hp_recommendation_context",anonymousId:"thg_hp_anonymous_id",session:"thg_hp_session",preview:"thg_hp_preview",debug:"thg_hp_debug"}),Rr={retry:"cdp.retry",sessionSignals:"cdp_session_signals",identified:"cdp_identified",previousPage:"cdp_prev_page_message_id"},Qe={...re},Gt=!1;function Ie(e){let t={...re};for(let[n,r]of Object.entries(e??{})){if(!(n in re))throw new Error(`storageNames: unknown kind "${n}"`);if(typeof r!="string"||!/^[\w.-]+$/.test(r))throw new Error(`storageNames.${n} must be a non-empty cookie-safe name`);t[n]=r}Qe=t}function O(e){return Qe[e]}var Dr="_",Wt=e=>e==null||e===""?Dr:String(e);function q(e,t,n){let r=`${e}:${Wt(t)}`;return n==null||n===""?r:`${r}:${n}`}function zt(e,t){return`${e}:${Wt(t)}:`}function Ae(){if(!Gt){Gt=!0;for(let e of[()=>localStorage,()=>sessionStorage]){let t;try{if(t=e(),!t)continue;for(let n of Object.keys(t))for(let[r,o]of Object.entries(Rr)){if(n!==o&&!n.startsWith(`${o}:`))continue;let s=Qe[r]+n.slice(o.length);t.getItem(s)===null&&t.setItem(s,t.getItem(n)),t.removeItem(n)}}catch{}}}}var tt=1800*1e3,ke=()=>{try{return typeof sessionStorage>"u"?null:sessionStorage}catch{return null}},et=()=>{try{return typeof localStorage>"u"?null:localStorage}catch{return null}};function Jt(e,t=()=>Date.now()){return{read(){try{let n=ke()?.getItem(e),r=n?JSON.parse(n):null;return!r||t()-r.savedAt>tt?(r&&ke()?.removeItem(e),{items:[],anonymousId:null}):{items:Array.isArray(r.items)?r.items:[],anonymousId:typeof r.anonymousId=="string"?r.anonymousId:null}}catch{return{items:[],anonymousId:null}}},write(n,r=null){try{n.length===0?ke()?.removeItem(e):ke()?.setItem(e,JSON.stringify({savedAt:t(),items:n,anonymousId:r}))}catch{}}}}var Mr=400*24*60*60;function Q(e,t,n,r){if(typeof document>"u")return!1;try{let o=typeof location<"u"&&location.protocol==="https:"?"; Secure":"",s=r?`; Domain=${r}`:"",i=n===null?"":`; Max-Age=${n}`;return document.cookie=`${e}=${encodeURIComponent(t)}; Path=/${i}; SameSite=Lax`+s+o,!0}catch{return!1}}function Ur({name:e,domain:t=null,legacyKey:n,read:r,mint:o,mayStore:s,carried:i=null}){let a=null;try{a=et()?.getItem(n)||null}catch{a=null}let u=r(e)??a??i,d=!1,p=()=>{if(!(d||u===null||!s())&&(d=Q(e,u,Mr,t),!(!d||a===null)))try{et()?.removeItem(n),a=null}catch{}};return{get(){return u??(u=o()),p(),u},peek:()=>u,persist:p,forget(){Q(e,"",0,t),u=null,d=!1;try{et()?.removeItem(n)}catch{}a=null}}}function Xt(e,{carried:t=null,mayStore:n,orOwn:r=!1}){let o=e.anonymousIdSource,s=()=>Ur({name:O("anonymousId"),domain:e.consentCookieDomain??null,legacyKey:q(O("anonymousId"),e.propertyId),read:U,mint:Z,mayStore:n,carried:t});if(o?.source==="hp_cookie"){let u=s();return e.getAnonymousId=()=>u.get(),u}if(o?.source==="site_cookie"&&o.cookie){let u=o.cookie;e.getAnonymousId=()=>U(u)}if(!r)return null;let i=e.getAnonymousId,a=s();return e.getAnonymousId=()=>B(M(i,null))??a.get(),a}var $r=200;function Yt(e,t,n=()=>Date.now()){let r=()=>q(O("carried"),e),o=!1;function s(){try{let i=JSON.parse(sessionStorage.getItem(r())??"null");return!i||!(n()-i.savedAt<=tt)?[]:Array.isArray(i.calls)?i.calls:[]}catch{return[]}}return{take(){let i=s();try{sessionStorage.removeItem(r())}catch{}return i},save(i){try{return o||!t()?!1:(sessionStorage.setItem(r(),JSON.stringify({savedAt:n(),calls:[...s(),...i].slice(-$r)})),!0)}catch{return!1}},close(){o=!0}}}var Fs=re.consent,ve=Object.freeze({visitor:"visitor",host:"host"}),Zt=()=>({basis:ve.host}),Vr="hp-consent",oe=["analytics","marketing","personalisation","ad_user_data","ad_personalization"],Qt={analytics:"a",marketing:"m",personalisation:"p",ad_user_data:"aud",ad_personalization:"adp"},X=()=>typeof window<"u",en=()=>X()&&typeof window.cookieStore?.addEventListener=="function",jr=e=>e===!0?!0:e===!1?!1:null;function ce(e){if(!e||typeof e!="object")return null;let t={};for(let n of oe)t[n]=jr(e[n]);return t}function rt(e,t){return!e||!t?e===t:oe.every(n=>e[n]===t[n])}function ot(e){if(!e)return"";let t=[];for(let n of oe){let r=e[n];(r===!0||r===!1)&&t.push(`${Qt[n]}=${r?1:0}`)}return t.join("&")}function tn(e){return{[Vr]:ot(ce(e))}}function ye(e){let t=ce({});if(e==null)return t;let n=String(e).trim();if(n.length>=2&&n.startsWith('"')&&n.endsWith('"')&&(n=n.slice(1,-1)),n.includes("%"))try{n=decodeURIComponent(n.replace(/\+/g," "))}catch{return t}let r=Object.fromEntries(Object.entries(Qt).map(([s,i])=>[i,s])),o=new Set;for(let s of n.split("&")){let i=s.indexOf("=");if(i<=0)continue;let a=r[s.slice(0,i).trim()],u={1:!0,0:!1}[s.slice(i+1).trim()]??null;!a||u===null||o.has(a)||(t[a]===null?t[a]=u:t[a]!==u&&(t[a]=null,o.add(a)))}return t}function st(e,t){if(!t)return e;let n=r=>{if(!r)return r;let o={...r};for(let s of oe)s!=="analytics"&&t[s]!==!0&&(o[s]=null);return o};return{name:e.name,basis:e.basis,read:()=>n(e.read()),subscribe:r=>e.subscribe(o=>r(n(o))),get observesChanges(){return e.observesChanges}}}function it(e,t={}){return{...e,ad_user_data:t.ad_user_data??e.marketing,ad_personalization:t.ad_personalization??e.marketing}}function Ee(e,t){let n=!0;return{notify:()=>{if(n)try{let o=e();o&&t(o)}catch{}},stop:()=>n=!1}}var Br={analytics:["analytics","measure_content_performance","audience_measurement"],marketing:["marketing","targeting","advertising","select_personalized_ads","create_ads_profile"],personalisation:["personalisation","personalization","select_personalized_content","create_content_profile"]};function qr(e,t){if(!t?.length)return null;let n=!1;for(let[r,o]of Object.entries(e)){let s=String(o?.id??r).toLowerCase();if(t.some(a=>{let u=String(a).toLowerCase();return s===u||s.startsWith(`${u}-`)})&&(n=!0,o?.enabled===!0))return!0}return n?!1:null}function nn(e={}){let t={...Br,...e.purposes};function n(){let r=X()?window.Didomi:void 0;if(!r?.getCurrentUserStatus||r.shouldUserStatusBeCollected?.()!==!1||r.isUserStatusPartial?.()===!0)return null;let o=r.getCurrentUserStatus()?.purposes;if(!o||typeof o!="object")return null;let s=i=>qr(o,t[i]);return it({analytics:s("analytics"),marketing:s("marketing"),personalisation:s("personalisation")},{ad_user_data:s("ad_user_data"),ad_personalization:s("ad_personalization")})}return{name:"didomi",read:n,observesChanges:!0,subscribe(r){if(!X())return()=>{};let o=Ee(n,r),s={event:"consent.changed",listener:o.notify};return window.didomiEventListeners=window.didomiEventListeners||[],window.didomiEventListeners.push(s),window.didomiOnReady=window.didomiOnReady||[],window.didomiOnReady.push(i=>{o.notify(),(i??window.Didomi)?.on?.("consent.changed",o.notify)}),window.Didomi?.on?.("consent.changed",o.notify),o.stop}}}function nt(e){let t={};if(!e)return t;for(let n of String(e).split("&")){let r=n.indexOf("=");if(r<=0)continue;let o=n.slice(r+1).replace(/\+/g," ");try{o=decodeURIComponent(o)}catch{}t[n.slice(0,r)]=o}return t}function rn(e={}){let t={analytics:"C0002",personalisation:"C0003",marketing:"C0004",...e.groups},n=e.impliedConsent===!0;function r(){let i=X()?window.OnetrustActiveGroups:void 0;if(typeof i=="string"&&i.length){let d=new Set(i.split(",").map(p=>p.trim()).filter(Boolean));return p=>d.has(p)}let a=nt(U("OptanonConsent"));if(!a.groups)return null;let u={};for(let d of a.groups.split(",")){let[p,f]=d.split(":");p?.trim()&&(u[p.trim()]=f==="1"?!0:f==="0"?!1:null)}return d=>u[d]??null}function o(){if(U("OptanonAlertBoxClosed"))return!0;try{if(X()&&window.OneTrust?.IsAlertBoxClosed?.()===!0)return!0}catch{}return Number.parseInt(nt(U("OptanonConsent")).interactionCount,10)>0?!0:n&&X()&&(!!window.OneTrust||typeof window.OnetrustActiveGroups=="string")}function s(){if(!o())return null;let i=r();if(!i)return null;let a=f=>t[f]?i(t[f]):null,u=it({analytics:a("analytics"),marketing:a("marketing"),personalisation:a("personalisation")},{ad_user_data:a("ad_user_data"),ad_personalization:a("ad_personalization")}),d=nt(U("OptanonConsent"));return(typeof navigator<"u"&&navigator.globalPrivacyControl===!0||d.isGpcEnabled==="1")&&(u.marketing=!1,u.ad_user_data=!1,u.ad_personalization=!1),u}return{name:"onetrust",read:s,observesChanges:!0,subscribe(i){if(!X())return()=>{};let a=Ee(s,i),u=!1,d=()=>{u||!window.OneTrust?.OnConsentChanged||(u=!0,window.OneTrust.OnConsentChanged(a.notify))};window.addEventListener("OneTrustGroupsUpdated",a.notify);let p=window.OptanonWrapper;return window.OptanonWrapper=function(...m){try{typeof p=="function"&&p.apply(this,m)}finally{d(),a.notify()}},d(),()=>{a.stop(),window.removeEventListener("OneTrustGroupsUpdated",a.notify)}}}}function Fr(e){let t=String(e).split(",").map(n=>n.trim()).filter(Boolean);return t.length?new Set(t):null}function on(e){let{name:t,categories:n,parse:r=Fr,refusedValue:o,source:s="native",changeEvent:i="thg_hp_consent_change"}=e??{};if(typeof t!="string"||t==="")throw new Error("cookieConsent: `name` (the cookie your banner writes) is required");if(!n||typeof n.analytics!="string")throw new Error("cookieConsent: `categories.analytics` (the category id that grants analytics) is required");function a(){let u=U(t);if(u==null)return null;let d;if(o!==void 0&&u===o)d=new Set;else try{d=r(u)}catch{d=null}if(!(d instanceof Set))return null;let p=f=>n[f]?d.has(n[f]):null;return it({analytics:p("analytics"),marketing:p("marketing"),personalisation:p("personalisation")},{ad_user_data:p("ad_user_data"),ad_personalization:p("ad_personalization")})}return{name:s,read:a,get observesChanges(){return en()},subscribe(u){if(!X())return()=>{};let d=Ee(a,u),p=f=>{[...f.changed??[],...f.deleted??[]].some(b=>b?.name===t)&&d.notify()};return window.addEventListener(i,d.notify),window.cookieStore?.addEventListener?.("change",p),()=>{d.stop(),window.removeEventListener(i,d.notify),window.cookieStore?.removeEventListener?.("change",p)}}}}function sn(e={}){let t=e.name??O("consent"),n=()=>{let r=U(t);if(r==null)return null;let o=ye(r);return o.analytics==null?null:o};return{name:"forwarded",read:n,get observesChanges(){return en()},subscribe(r){if(!X())return()=>{};let o=Ee(n,r),s=i=>{[...i.changed??[],...i.deleted??[]].some(u=>u?.name===t)&&o.notify()};return window.cookieStore?.addEventListener?.("change",s),()=>{o.stop(),window.cookieStore?.removeEventListener?.("change",s)}}}}function an(e){let t=ce(e);return{name:"static",basis:ve.host,read:()=>t?{...t}:null,observesChanges:!0,subscribe:()=>()=>{}}}function cn(e){if(e==null)return null;let t=String(e).split("/").map(n=>n.trim()).filter(Boolean).join("/");return t===""?null:t}function Hr(e){let t=e.item_category;if(t!=null&&String(t).includes("/"))return cn(t);let n=[t,e.item_category2,e.item_category3,e.item_category4,e.item_category5].map(r=>r==null?"":String(r).trim()).filter(Boolean);return cn(n.join("/"))}var Kr=e=>{if(e==null||typeof e=="object")return null;let t=String(e).trim();return t===""?null:t};function Gr(...e){for(let t of e){if(typeof t=="number"&&Number.isFinite(t))return String(t);if(typeof t=="string"&&t.trim()!=="")return t.trim()}return null}var Ce=e=>Gr(e.item_group_id,e.item_id);function Wr(e,t=Ce){if(!e)return null;let n=a=>a==null||a===""||Number.isNaN(Number(a))?null:Number(a),r=a=>{let u=n(a);return u==null?null:Math.round(u*1e6)/1e6},o=a=>{let u=r(a);return u!=null&&u>=0?u:null},s=a=>{let u=n(a);return Number.isInteger(u)&&u>=0?u:null},i=Kr;return{product_id:i(t(e)),sku:i(e.item_id),name:i(e.item_name),brand:i(e.item_brand),variant:i(e.item_variant)??i(e.variant),category:Hr(e),price:r(e.price),discount:o(e.discount),quantity:s(e.quantity),coupon:i(e.coupon),position:s(e.index)!=null?s(e.index)+1:null,url:i(e.url)??i(e.item_url)??null,image_url:i(e.image_url)??i(e.image_link)??null,displayed_sku:i(e.displayed_sku)??i(e.displayed_variant_sku)}}function at(e,t=Ce){return(e??[]).map(n=>Wr(n,t)).filter(n=>n?.product_id)}var zr=1e3,Jr={shipping:2,payment:3},Xr=new Set(["password","google","apple","facebook","magic_link","sso","passkey","other"]),$=e=>{if(e==null||typeof e=="object")return null;let t=String(e).trim();return t===""?null:t},Yr=e=>{if(e==null||e===""||typeof e=="object")return null;let t=Number(e);return Number.isFinite(t)&&t>=0?t:null},Pe=e=>e!==null&&typeof e=="object";function Zr(e){return Array.isArray(e.items)?e.items.filter(Pe):null}function un(e){let t=$(e)?.toLowerCase().replace(/[\s-]+/g,"_");return t?Xr.has(t)?t:t==="email"||t==="email_password"?"password":"other":null}function Qr({ecommerce:e,items:t}){let n=s=>({promotion_id:$(s.promotion_id),name:$(s.promotion_name),creative:$(s.creative_name),position:$(s.creative_slot)}),r=new Set,o=[];for(let s of[n(e),...t.map(n)])!s.promotion_id||r.has(s.promotion_id)||(r.add(s.promotion_id),o.push(s));return o}var ln=({ecommerce:e,items:t})=>$(e.item_list_id)??$(e.item_list_name)??$(t[0]?.item_list_id)??$(t[0]?.item_list_name),se=(e,t)=>t.products?.length?{event:e,properties:t}:null,eo=new Set(["product","products","item","items"]);function to({ecommerce:e}){let t=$(e.item_id),n=$(e.content_type)?.toLowerCase();return!t||n&&!eo.has(n)?null:{event:"Product Shared",properties:{share_via:$(e.method),products:[{product_id:t}]}}}function no(e){let t=o=>se("Product List Viewed",{list_id:ln(o),products:o.products}),n=o=>{let s=$(o.ecommerce.search_term);return s?{event:"Products Searched",properties:{query:s,...o.products.length?{products:o.products}:{}}}:null},r=o=>s=>s.promotions.map(i=>({event:o,properties:i}));return{view_item:o=>se("Product Viewed",{currency:o.currency,value:o.value,products:o.products.slice(0,1)}),view_item_list:t,view_search_results:o=>n(o)??t(o),search:n,select_item:o=>se("Product Clicked",{list_id:ln(o),products:o.products.slice(0,1)}),add_to_cart:o=>se("Product Added",{cart_id:$(o.ecommerce.cart_id),products:o.products}),remove_from_cart:o=>se("Product Removed",{cart_id:$(o.ecommerce.cart_id),products:o.products}),view_cart:o=>({event:"Cart Viewed",properties:{cart_id:$(o.ecommerce.cart_id),value:o.value,currency:o.currency,coupon:$(o.ecommerce.coupon),...o.itemsListed?{products:o.products}:{}}}),begin_checkout:o=>se("Checkout Started",{value:o.value,currency:o.currency,coupon:$(o.ecommerce.coupon),products:o.products}),add_shipping_info:o=>({event:"Checkout Step Completed",properties:{step:e.shipping,step_name:"shipping",shipping_method:$(o.ecommerce.shipping_tier)}}),add_payment_info:o=>({event:"Payment Info Entered",properties:{step:e.payment,step_name:"payment",payment_method:$(o.ecommerce.payment_type)}}),view_promotion:r("Promotion Viewed"),select_promotion:r("Promotion Clicked"),add_to_wishlist:o=>se("Product Added to Wishlist",{products:o.products}),share:to,login:o=>({event:"Signed In",properties:{method:un(o.ecommerce.method)}}),sign_up:o=>({event:"Signed Up",properties:{method:un(o.ecommerce.method)}})}}var ro=e=>e.event;function oo(e,t){if(!Pe(e))return null;if(typeof e.length=="number"&&e[0]==="event"&&typeof e[1]=="string"){let r=Pe(e[2])?e[2]:{};return{name:e[1],entry:r,ecommerce:r}}let n=$(t(e));return n?{name:n,entry:e,ecommerce:Pe(e.ecommerce)?e.ecommerce:e}:null}function ct(e){return Array.isArray(e)?`[${e.map(ct).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${ct(e[t])}`).join(",")}}`:JSON.stringify(e??null)}function dn(e,t={}){if(!e||typeof e.track!="function")throw new Error("connectDataLayer: analytics must have a track(event, properties, options) method");let n=t.dataLayer??(typeof window<"u"?window.dataLayer=window.dataLayer||[]:null);if(!Array.isArray(n))return()=>{};let r=no({...Jr,...t.steps}),o=t.map??{},s=t.eventName??ro,i=t.productId??Ce,a=new Set(["purchase","refund",...t.ignore??[]]),u=t.dedupeMs??zr,d=new Map,p=!0;function f(S,y,w){if(u<=0)return!1;let L=`${S}|${w??""}|${ct(y)}`,D=Date.now();for(let[H,K]of d)D-K>u&&d.delete(H);return d.has(L)?!0:(d.set(L,D),!1)}let m=null,b=new Set;function g(S,y){if(S!=="Product Viewed")return!1;let w=typeof location>"u"?"":location.pathname;w!==m&&(m=w,b.clear());let L=y.products?.[0]?.product_id;return L?b.has(L)?!0:(b.add(L),!1):!1}function _({event:S,properties:y={}},w){if(!S||f(S,y,w)||g(S,y))return;let L=w?{context:{currency:w}}:void 0;if(S==="Signed Out"&&typeof e.signedOut=="function"){e.signedOut(L);return}e.track(S,y,L)}function x(S){let y=t.transform?t.transform(S):S,w=oo(y,s);if(!w||a.has(w.name))return;let L=Object.hasOwn(o,w.name),D=L?o[w.name]:r[w.name];if(typeof D!="function")return;let H=Zr(w.ecommerce),K=H??[],F={...w,items:K,itemsListed:H!==null,products:at(K,i),currency:Ze(w.ecommerce.currency),value:Yr(w.ecommerce.value)};F.promotions=Qr(F);let I=L?D(y,{...F,toProducts:G=>at(G,i)}):D(F);for(let G of[I].flat())G&&_(G,F.currency)}function v(S){if(p)try{x(S)}catch(y){try{t.onError?.(y,S)}catch{}}}for(let S of n.slice())v(S);let h=n.push;function E(...S){let y=h.apply(this,S);for(let w of S)v(w);return y}return n.push=E,()=>{p=!1,n.push===E&&(n.push=h)}}var so="hp_debug",io=256;function ao(){try{return new URLSearchParams(location.search).get(so)}catch{return null}}function fn(e,t){if(typeof location>"u")return!1;try{let n=ao();if(n!==null){let o=n.trim();o===""||o.length>io?sessionStorage.removeItem(t):sessionStorage.setItem(t,o)}let r=sessionStorage.getItem(t);return!!(e&&r&&Nt(r)===e)}catch{return!1}}var co=4320*60*60,uo=100,lo=1e3,fo=2e3,pn=500,Le=e=>(e?.unref?.(),e);function po(e,t){if(t?.analytics===!0&&e.analytics===!1)return"withdrawn";let n=oe.map(r=>e[r]).filter(r=>r!==null);return n.length&&n.every(Boolean)?"accept_all":n.length&&n.every(r=>!r)?"reject_all":"custom"}function mo(e,t,n=O("consent")){if(!(typeof document>"u"))try{let r=typeof location<"u"&&location.protocol==="https:"?"; Secure":"",o=t?`; Domain=${t}`:"";document.cookie=`${n}=${ot(e)}; Path=/; Max-Age=${co}; SameSite=Lax`+o+r}catch{}}function ho(e){let t=ye(U(e));return oe.some(n=>t[n]!==null)?t:null}function mn(e){let{adapter:t,timeoutMs:n=5e3,cookieDomain:r=null,cookieName:o=O("consent"),release:s,announce:i}=e,a=e.discard??(()=>{}),u="pending",d=null,p=null,f=null,m=null,b=!1,g=null,_=[],x=R=>{u=R,e.onStatus?.(R)};function v(){try{return ce(t.read())}catch{return null}}function h(){p&&clearInterval(p),f&&clearTimeout(f),p=null,f=null}let E=()=>typeof window<"u"&&typeof document<"u",S=()=>{try{return typeof t.subscribe=="function"&&t.observesChanges===!0}catch{return!1}};function y(){m&&clearInterval(m),m=null}function w(){b||m||d===null||S()||E()&&document.visibilityState==="hidden"||(m=Le(setInterval(()=>F(v()),fo)))}let L=()=>u==="pending"||u==="unknown",D=e.store??null,H=()=>{let R=_.splice(0);D?.write([]);for(let j of R)s(j)},K=()=>{let R=_.splice(0);D?.write([]);for(let j of R)a(j)};function F(R){let j=ce(R);if(!j||j.analytics===null)return;let c=d===null,l=c?ho(o):d;if(!c&&rt(l,j))return;h(),d=j,w(),mo(j,r,o);let k=!rt(l,j);if(j.analytics){x("granted"),H(),k&&i({action:po(j,l),state:j,withdrawal:!1});return}K(),l?.analytics===!0&&i({action:"withdrawn",state:j,withdrawal:!0}),x("denied")}function I(){f=null,u==="pending"&&(x("unknown"),p&&clearInterval(p),p=Le(setInterval(()=>F(v()),lo)))}function G(R){L()&&(D||R?.type!=="visibilitychange"&&!R?.persisted&&K())}let we=R=>{document.visibilityState==="hidden"?(y(),G(R)):w()},me=R=>{y(),G(R)},he=()=>w();return{get status(){return u},get state(){return d?{...d}:null},stamp(){return d?{...d,source:t.name??null,basis:t.basis??ve.visitor}:null},admits(){return u==="granted"},hold(R){return!L()||_.length>=pn?(a(R),!1):(_.push(R),D?.write(_),!0)},onHidden:G,start(){b=!1;try{g=t.subscribe?.(R=>F(R))??null}catch(R){e.debug&&console.warn("[hp] consent adapter subscribe failed",R)}E()&&(window.addEventListener("pagehide",me),window.addEventListener("pageshow",he),document.addEventListener("visibilitychange",we)),D&&_.length===0&&_.push(...D.read().slice(-pn)),F(v()),!d&&(p=Le(setInterval(()=>F(v()),uo)),f=Le(setTimeout(I,n)))},stop(){b=!0,h(),y(),typeof g=="function"&&g(),E()&&(window.removeEventListener("pagehide",me),window.removeEventListener("pageshow",he),document.removeEventListener("visibilitychange",we))}}}var go=["recommendation","search_rank","banner","offer","layout","copy","price","other"],Oe=e=>typeof e=="string"&&e!=="";function hn(e){return(Array.isArray(e?.decisions)?e.decisions:[]).filter(t=>Oe(t?.slot)&&Oe(t.decision_id)&&t.holdout!==!0).map(({decision_id:t,slot:n,arm:r,kind:o})=>({decision_id:t,slot:n,...Oe(r)&&{arm:r},...go.includes(o)&&{kind:o}}))}function gn({key:e,mayStore:t}){let n,r=()=>{if(n)return n;try{let s=JSON.parse(sessionStorage.getItem(e));n=Array.isArray(s)?s.filter(i=>Oe(i?.slot)):[]}catch{n=[]}return n},o=()=>{try{n?.length&&t()&&sessionStorage.setItem(e,JSON.stringify(n))}catch{}};return{persist:o,current:()=>r().length?n.map(s=>({...s})):null,remember(s){if(!s.length)return;let i=new Set(s.map(a=>a.slot));n=[...r().filter(a=>!i.has(a.slot)),...s],o()},forget(){n=[];try{sessionStorage.removeItem(e)}catch{}}}}function yo(e){let t=2166136261,n=String(e);for(let r=0;r<n.length;r+=1)t^=n.charCodeAt(r),t=t+((t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24))>>>0;return t^=t>>>16,t=Math.imul(t,2246822507)>>>0,t^=t>>>13,t=Math.imul(t,3266489909)>>>0,t^=t>>>16,t>>>0}function Ne(e,t,n=1e4){return yo(`${t}\0${e}`)%n}function ut(e){let t=o=>Math.min(Math.max(Number(o)||0,0),1),n=t(e.share),r=1-n;return{holdout_share:n,personalised_share:e.ramp==null?r:Math.min(t(e.ramp),r)}}function lt(e,t){if(!e||typeof e.salt!="string")return{holdout:!1,bucket:null,group:"personalised"};let n=e.ramp!=null;if(t==null||t==="")return{holdout:!1,bucket:null,group:n?"excluded":"personalised"};let{holdout_share:r,personalised_share:o}=ut(e),s=Ne(t,e.salt),i=u=>Math.round(u*1e4),a=r>0&&s<i(r)?"holdout":!n||s<i(r+o)?"personalised":"excluded";return{holdout:a==="holdout",bucket:s,group:a}}function yn(e,t){return typeof e!="string"||e===""||t==null||t===""?null:Ne(t,`bandit-vs-default:${e}`)<1e4/2?"default":"bandit"}var So="hp_preview",wo=256;function Sn(e,t=null){if(typeof location>"u")return U(e);let n=null;try{n=new URLSearchParams(location.search).get(So)}catch{n=null}if(n===null)return U(e);let r=n.trim();return r===""||r.length>wo?(Q(e,"",0,t),null):(Q(e,r,null,t),r)}var _o=40,bo=128,xo=5,Io=60,_n=e=>typeof e=="string"&&e!==""&&e.length<=bo&&!e.includes(","),wn=e=>Array.isArray(e)?e.filter(_n).slice(0,_o):[],Ao=e=>Array.isArray(e)?e.filter(t=>typeof t=="string").map(t=>t.replaceAll(","," ").trim()).filter(t=>t!=="").slice(0,xo):[],ko=e=>Object.entries(e&&typeof e=="object"?e:{}).filter(([t,n])=>_n(t)&&Number.isFinite(n)&&n>=1).sort(([,t],[,n])=>n-t).slice(0,Io).map(([t,n])=>`${t}:${Math.floor(n)}`);function dt(e){let t={},n={viewed:wn(e?.recentProducts),basket:wn(e?.basketProducts),q:Ao(e?.recentSearches),impressed:ko(e?.impressions)};for(let[r,o]of Object.entries(n))o.length>0&&(t[r]=o.join(","));return t}var vo=1800*1e3;function bn(e,t=Date.now()){try{localStorage.setItem(O("recommendationContext"),JSON.stringify({context:e,until:t+vo}))}catch{}}function xn(){try{localStorage.removeItem(O("recommendationContext"))}catch{}}var Eo="hp_sample",In=1e4;function An(e,t){return!(typeof t=="number"&&t<1)||e==null||e===""?!0:Ne(e,Eo,In)<Math.round(t*In)}function ft(e){return e.trim().toLowerCase().replace(/\s*\/\s*/g,"/").replace(/^\/+|\/+$/g,"")}var Co=2,Po=12;function Lo(e,t=Co){return e.split("/").slice(0,t).join("/")}function ue(e,t){if(typeof t!="string"||!t.trim())return null;let n=Lo(ft(t));return n?(e.unshift(n),e.length=Math.min(e.length,Po),n):null}var ie=()=>O("sessionSignals"),le=null,pt=()=>le??ie();function kn(){try{return le?localStorage:sessionStorage}catch{return}}function vn(e,t){let n=t==null?null:String(t),r=e?q(ie(),n,e):null;if(r===le)return;let o=le===null?Oo():null;if(le=r,!!le)try{let s=pt(),i=zt(ie(),n);for(let a of Object.keys(localStorage))a.startsWith(i)&&a!==s&&localStorage.removeItem(a);o&&!localStorage.getItem(s)&&(localStorage.setItem(s,JSON.stringify(o)),sessionStorage.removeItem(ie()))}catch{}}function Oo(){try{let e=sessionStorage.getItem(ie());if(!e)return null;let t=JSON.parse(e);return t&&(t.productViews>0||t.listViews>0||t.basketValue!=null||t.recentProducts?.length||t.recentBrowse?.length)?t:null}catch{return null}}function Te(){return{productViews:0,listViews:0,cartAdds:0,searches:0,recentProducts:[],recentCategories:[],recentBrowse:[],basketProducts:[],basketValue:null,recentSearches:[],impressions:{},browsingCategory:null,startedAt:0}}function mt(){let e=kn();if(!e)return Te();try{let t=e.getItem(pt());return t?{...Te(),...JSON.parse(t)}:{...Te(),startedAt:Date.now()}}catch{return{...Te(),startedAt:Date.now()}}}function ht(e){let t=kn();if(t)try{t.setItem(pt(),JSON.stringify(e))}catch{}}function En(){return mt()}function Cn(){for(let e of[()=>sessionStorage,()=>localStorage])try{let t=e();t.removeItem(ie());for(let n of Object.keys(t))n.startsWith(`${ie()}:`)&&t.removeItem(n)}catch{}}var Pn=20,Ln=200,No=new Set(["Product Viewed","Product Clicked","Product Added","Product Added to Wishlist"]),To=new Set(["Product Clicked","Product Added to Wishlist"]);function On(e,t){let n=mt();n.startedAt||(n.startedAt=Date.now());let r=Array.isArray(t?.products)?t.products:[];switch(e){case"Product Viewed":n.productViews+=1,ue(n.recentBrowse,n.browsingCategory);break;case"Product List Viewed":n.listViews+=1;{let i=t?.list_id;typeof i=="string"&&i.includes("/")&&!i.startsWith("cdp-")&&(n.browsingCategory=ue(n.recentBrowse,i)??n.browsingCategory)}break;case"Product List Filtered":{let i=t?.change,a=typeof t?.list_id=="string"?t.list_id:"",u=typeof i?.value=="string"?i.value:"";u&&i?.action!=="removed"&&(n.browsingCategory=ue(n.recentBrowse,`${a}/${u}`)??n.browsingCategory);break}case"Product Added":{n.cartAdds+=1,ue(n.recentBrowse,n.browsingCategory);for(let i of r){let a=i.product_id;typeof a!="string"||!a||(n.basketProducts=[a,...n.basketProducts.filter(u=>u!==a)].slice(0,Pn))}break}case"Product Removed":{for(let i of r){let a=i.product_id;typeof a=="string"&&a&&(n.basketProducts=n.basketProducts.filter(u=>u!==a))}break}case"Products Searched":{n.searches+=1;let i=t?.query;if(typeof i=="string"&&i.trim()){let a=i.trim().toLowerCase().slice(0,64);n.recentSearches=[a,...n.recentSearches.filter(u=>u!==a)].slice(0,5)}break}case"Product Clicked":case"Product Added to Wishlist":break;case"Cart Updated":{let i=t?.value;return n.basketValue=typeof i=="number"&&Number.isFinite(i)&&i>=0?i:null,ht(n),n}default:return n}let o=No.has(e);if(o)for(let i of r)typeof i.product_id=="string"&&i.product_id&&delete n.impressions[i.product_id];else{for(let a of r){let u=a.product_id;typeof u!="string"||!u||(n.impressions[u]=(n.impressions[u]??0)+1)}let i=Object.keys(n.impressions);if(i.length>Ln){let a={};for(let u of i.sort((d,p)=>n.impressions[p]-n.impressions[d]).slice(0,Ln))a[u]=n.impressions[u];n.impressions=a}}let s=new Set;for(let i of r){let a=i.product_id;if(o&&typeof a=="string"&&a&&(n.recentProducts=[a,...n.recentProducts.filter(u=>u!==a)].slice(0,Pn)),!To.has(e)&&typeof i.category=="string"&&i.category){let u=ft(i.category);u&&!s.has(u)&&(s.add(u),ue(n.recentCategories,u))}}return ht(n),n}var Nn=1800*1e3;function Tn({name:e,domain:t=null,mayStore:n,now:r=()=>Date.now(),mint:o=Z}){let s=null,i=0,a=()=>{s!==null&&n()&&Q(e,s,Nn/1e3,t)};return{get(){let u=r(),d=U(e);return d?s=d:(s===null||u-i>=Nn)&&(s=o()),i=u,a(),s},persist:a,forget(){Q(e,"",0,t),s=null}}}var J=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Rn=e=>Array.isArray(e)&&e.every(t=>typeof t=="string"),yt=Object.freeze({enabled:!1});function Dn(e,t){let n=e?.track;return J(n)?(Object.hasOwn(n,t)?n[t]:n.__default)?.enabled!==!1:!0}function Ro(e,t){return`${de(e)}/v1/projects/${encodeURIComponent(t)}/settings`}var Do="https://hp.thgingenuity.com";function de(e){return e==null||e===""?Do:Mn(e)}function Mn(e){if(typeof e!="string")return null;let t=e.trim().replace(/\/+$/,"");return/^https?:\/\/[^/\s]+$/i.test(t)?t:/^[a-z0-9.-]+(:\d+)?$/i.test(t)?`https://${t}`:null}function Mo(e){if(!J(e))return null;let t=Object.fromEntries(Object.entries(e).filter(([,n])=>typeof n=="boolean"));return Object.keys(t).length?t:null}var gt=e=>typeof e=="number"&&e>=0&&e<=1,Uo=/^[0-9a-f]{64}$/;function $o(e){return J(e)?{enabled:e.enabled===!0,tokenSha256:typeof e.tokenSha256=="string"&&Uo.test(e.tokenSha256)?e.tokenSha256:null}:{enabled:!1,tokenSha256:null}}function Vo(e){if(!J(e))return null;let t=Object.fromEntries(Object.entries(e).filter(([,n])=>J(n)&>(n.share)&&(n.ramp==null||gt(n.ramp))&&typeof n.salt=="string"&&n.salt!=="").map(([n,r])=>[n,{share:r.share,ramp:r.ramp??null,salt:r.salt,...Rn(r.readoutSlots)&&r.readoutSlots.length>0&&{readoutSlots:r.readoutSlots}}]));return Object.keys(t).length?t:null}var jo=/^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,128}$/;function Bo(e){return J(e)?e.source==="hp_cookie"?{source:"hp_cookie"}:e.source!=="site_cookie"?null:e.cookie==null?{source:"site_cookie",cookie:null}:typeof e.cookie=="string"&&jo.test(e.cookie)?{source:"site_cookie",cookie:e.cookie}:null:null}function St(e){let t=J(e)?e.hp:null;return!J(t)||t.enabled!==!0?yt:{enabled:!0,propertyId:typeof t.propertyId=="string"&&t.propertyId!==""?t.propertyId:null,environment:typeof t.environment=="string"?t.environment:null,consentPurposes:Mo(t.consent?.purposes),queryParamAllowlist:Rn(t.queryParamAllowlist)?t.queryParamAllowlist:null,anonymousId:Bo(t.anonymousId),apiHost:Mn(t.apiHost),holdouts:Vo(t.holdouts),sampleRate:gt(t.sampleRate)?t.sampleRate:1,debug:$o(t.debug),plan:J(e.plan)&&J(e.plan.track)?{track:e.plan.track}:null}}async function Un(e,t,n=globalThis.fetch){try{let r=await n(Ro(e,t),{credentials:"omit",headers:{accept:"application/json"}});return r.ok?St(await r.json()):null}catch{return null}}var Re=e=>q(O("settings"),e);function $n(e){try{let t=localStorage.getItem(Re(e));if(!t)return null;let n=JSON.parse(t);return n?.enabled===!0?n:null}catch{return null}}function Vn(e,t){try{t?.enabled===!0?localStorage.setItem(Re(e),JSON.stringify(t)):localStorage.removeItem(Re(e))}catch{}}function wt(e){try{localStorage.removeItem(Re(e))}catch{}}var qo={flushAt:20,flushIntervalMs:1e4,maxQueue:500,maxRetryAgeMs:1440*60*1e3,maxRetries:5,maxPersistedBatches:20,maxPersistedBytes:1024*1024,storageKey:re.retry,beaconSafeBytes:56*1024,maxBatchEvents:100,maxBatchBytes:500*1024},_t=()=>Date.now(),fe=e=>typeof TextEncoder<"u"?new TextEncoder().encode(e).length:Buffer.byteLength(e),bt=()=>typeof window<"u"&&typeof document<"u";function Fo(){try{return!bt()||!window.localStorage?null:(window.localStorage.getItem("thg_hp_probe"),window.localStorage)}catch{return null}}var A,xt,jn,De,It,At,Bn,Se,kt,Me,qn,vt,Fn,Ue,$e=class{constructor(t){Lt(this,A);this.o={...qo,...t},this.queue=[],this.timer=null,this.storage=Fo(),this.inflight=null,this.sending=new Set,this.replayAllowed=!this.o.deferReplay,this.draining=null,bt()&&(window.addEventListener("pagehide",()=>this.flush({beacon:!0})),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),N(this,A,Ue).call(this))}replayPersisted(){return this.replayAllowed=!0,N(this,A,Ue).call(this)}clearPersisted(){if(this.replayAllowed=!1,!!this.storage)try{this.storage.removeItem(this.o.storageKey)}catch{}}enqueue(t){let n;try{n=JSON.parse(JSON.stringify(t))}catch(r){this.o.onError?.(`event ${t?.messageId??""} cannot be serialised; dropped`,r);return}this.queue.push(n),this.queue.length>this.o.maxQueue&&this.queue.splice(0,this.queue.length-this.o.maxQueue),this.queue.length>=this.o.flushAt?this.flush():N(this,A,xt).call(this)}flush(t={}){if(N(this,A,jn).call(this),this.queue.length===0)return Promise.resolve();if(t.beacon&&bt()&&typeof navigator<"u"&&navigator.sendBeacon)return N(this,A,Bn).call(this),Promise.resolve();if(this.inflight)return this.inflight;let n=N(this,A,At).call(this,this.queue,this.o.maxBatchEvents,this.o.maxBatchBytes).map(r=>{let o=N(this,A,vt).call(this,r);return o&&this.sending.add(o),{batch:r,id:o}});return this.queue=[],this.inflight=(async()=>{for(let{batch:r,id:o}of n)await N(this,A,Se).call(this,r,0,o)})().finally(()=>{this.inflight=null,this.queue.length>=this.o.flushAt?this.flush():this.queue.length&&N(this,A,xt).call(this)}),this.inflight}};A=new WeakSet,xt=function(){this.timer||(this.timer=setTimeout(()=>this.flush(),this.o.flushIntervalMs),this.timer?.unref?.())},jn=function(){this.timer&&(clearTimeout(this.timer),this.timer=null)},De=function(t){return JSON.stringify({batch:t,writeKey:this.o.writeKey,sentAt:new Date().toISOString()})},It=function(){return`${this.o.apiHost.replace(/\/$/,"")}/v1/batch`},At=function(t,n,r){let o=fe(N(this,A,De).call(this,[])),s=[],i=[],a=o;for(let u of t){let d=fe(JSON.stringify(u))+1;i.length&&(i.length>=n||a+d>r)&&(s.push(i),i=[],a=o),i.push(u),a+=d}return i.length&&s.push(i),s},Bn=function(){let t=this.queue;this.queue=[];let n=N(this,A,It).call(this);for(let r of N(this,A,At).call(this,t,this.o.maxBatchEvents,this.o.beaconSafeBytes)){let o=N(this,A,De).call(this,r);if(fe(o)<=this.o.beaconSafeBytes){let s=!1;try{s=navigator.sendBeacon(n,new Blob([o],{type:"text/plain"}))}catch{s=!1}if(s)continue}N(this,A,Se).call(this,r,0,N(this,A,vt).call(this,r))}},Se=async function(t,n,r=null){r&&this.sending.add(r);let o=N(this,A,De).call(this,t);try{let s=await fetch(N(this,A,It).call(this),{method:"POST",headers:{"Content-Type":"text/plain"},body:o,keepalive:fe(o)<=this.o.beaconSafeBytes});if(s.ok)return N(this,A,kt).call(this,r),N(this,A,Ue).call(this),!0;if(!(s.status===429||s.status>=500))return N(this,A,kt).call(this,r),this.o.onError?.(`CDP ingest rejected batch (${s.status}); dropping ${t.length} events`),!0;throw new Error(`retryable ${s.status}`)}catch(s){if(n>=this.o.maxRetries){r&&this.sending.delete(r);let a=r?"kept for the next page":"dropped";return this.o.onError?.(`CDP ingest unreachable after ${n} attempts; ${a}: ${t.length} events`,s),!1}let i=Math.min(1e3*2**n,3e4)*(.5+Math.random());return await new Promise(a=>setTimeout(a,i)),N(this,A,Se).call(this,t,n+1,r)}},kt=function(t){t&&(this.sending.delete(t),N(this,A,Fn).call(this,t))},Me=function(){let t=JSON.parse(this.storage.getItem(this.o.storageKey)||"[]");return Array.isArray(t)?t:[]},qn=function(t,n){let r=i=>fe(JSON.stringify(i))+1,o=[...t,n].slice(-this.o.maxPersistedBatches),s=o.reduce((i,a)=>i+r(a),2);for(;o.length>1&&s>this.o.maxPersistedBytes;)s-=r(o[0]),o=o.slice(1);for(;o.length;)try{return this.storage.setItem(this.o.storageKey,JSON.stringify(o)),!0}catch{o=o.slice(1)}return!1},vt=function(t){if(!this.storage||this.o.mayPersist&&!this.o.mayPersist())return null;try{let n={at:_t(),id:`${_t()}-${Math.random().toString(36).slice(2)}`,batch:t};return fe(JSON.stringify(n))+3>this.o.maxPersistedBytes?null:N(this,A,qn).call(this,N(this,A,Me).call(this),n)?n.id:null}catch{return null}},Fn=function(t){if(!(!t||!this.storage))try{let n=N(this,A,Me).call(this).filter(r=>r.id!==t);n.length?this.storage.setItem(this.o.storageKey,JSON.stringify(n)):this.storage.removeItem(this.o.storageKey)}catch{}},Ue=function(){if(!this.storage||!this.replayAllowed)return this.draining??Promise.resolve();if(this.draining)return this.draining;let t;try{let r=N(this,A,Me).call(this);t=r.filter(o=>Array.isArray(o?.batch)&&_t()-o.at<this.o.maxRetryAgeMs).map(o=>o.id?o:{...o,id:`${o.at}-${Math.random().toString(36).slice(2)}`}),(t.length!==r.length||t.some((o,s)=>o!==r[s]))&&(t.length?this.storage.setItem(this.o.storageKey,JSON.stringify(t)):this.storage.removeItem(this.o.storageKey))}catch{return Promise.resolve()}let n=t.filter(r=>!this.sending.has(r.id));return n.length?(this.draining=(async()=>{for(let r of n)if(!this.replayAllowed||!await N(this,A,Se).call(this,r.batch,0,r.id))return})().finally(()=>{this.draining=null}),this.draining):Promise.resolve()};var Hn="3.18.0",Kn="@thg-agentic/hyper-personalisation-web";var Ho=new Set(["audiences","decisions","recommendations"]);var Ko=Symbol.for("@thg-agentic/hyper-personalisation-web.replay"),Ve=class extends Error{};function Go(e){let t={...e??{}},n=Array.isArray(t.products)?t.products:null;if(t.item_count==null&&n){let r=n.map(o=>o?.quantity);r.every(Number.isInteger)&&(t.item_count=r.reduce((o,s)=>o+s,0))}return t}var Wo="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";function zo(e){if(/^[0-9a-fA-F]{64}$/.test(e))return e.toLowerCase();if(!/^[A-Za-z0-9+/_-]{43}=?$/.test(e))return null;try{let t=atob(e.replace(/-/g,"+").replace(/_/g,"/").padEnd(44,"="));return t.length!==32?null:Array.from(t,n=>n.charCodeAt(0).toString(16).padStart(2,"0")).join("")}catch{return null}}function je(e){if(typeof e!="string")return null;let t=zo(e.trim());return t===Wo?null:t}var Gn=!1;function Jo(e){!e||Gn||(Gn=!0,console.warn("[hp] mintAnonymousId is ignored since 3.6: where device ids come from is set per source in the console (the site\u2019s own cookie, or Hyper-Personalisation\u2019s)."))}function Wn(e){return e&&typeof e.context=="object"&&e.context!==null?e.context:e}function zn(e){let t=de(e?.apiHost);if(!t)throw new Error("createAnalytics: apiHost, when given, must be a host or an http(s) origin");if(!e?.propertyId)throw new Error("createAnalytics: propertyId is required");let n={libraryName:Kn,libraryVersion:Hn,writeKey:"web",queryParamBlocklist:[],debug:!1,validate:null,consentTimeoutMs:5e3,...e,apiHost:t};Ie(n.storageNames),Ae();let r=!!n.consent,o=null;n.getConsent=()=>o?o.stamp():Zt();let s=()=>!r||o?.status==="granted",i=r&&n.holdAcrossPages?Jt(q(O("held"),n.propertyId)):null,a=i?.read()??{items:[],anonymousId:null};n.mintAnonymousId!==void 0&&Jo(n.debug);let u=Xt(n,{carried:a.anonymousId,mayStore:s,orOwn:!0}),d=Tn({name:O("session"),domain:n.consentCookieDomain??null,mayStore:s}),p=n.getSessionId;n.getSessionId=()=>B(M(p,null))??d.get();let f=Sn(O("preview"),n.consentCookieDomain??null),m=n.transport??new $e({apiHost:n.apiHost,writeKey:n.writeKey,storageKey:q(O("retry"),n.propertyId),mayPersist:r?()=>o?.status==="granted":void 0,deferReplay:r,onError:(c,l)=>n.debug&&console.warn("[hp]",c,l??"")}),b=q(O("previousPage"),n.propertyId),g=null,_=()=>{if(g)return g;try{return sessionStorage.getItem(b)||null}catch{return null}},x=c=>{if(g=c||null,!(r&&o.status!=="granted"))try{sessionStorage.setItem(b,c)}catch{}},v=gn({key:q(O("decisions"),n.propertyId),mayStore:s});n.getDecisions??(n.getDecisions=v.current);let h=B(M(n.getUserId,null)),E=null;function S(c,l,k=!1){try{return y(c,l,k)}catch(P){if(P instanceof Ve)throw P;return console.warn("[hp] the event was not sent",P),null}}function y(c,l,k){let P=E;E=null;let C={messageId:P?.messageId??Z(),timestamp:P?.timestamp??new Date().toISOString(),anonymousId:B(M(n.getAnonymousId,null)),userId:h??null,...c,context:Ht(n,Wn(l))};if(C.properties&&(C.properties=Xe(C.properties)),!k&&!An(C.anonymousId,n.sampleRate))return n.debug&&console.debug("[hp] not sent: outside the sample",C),null;if(n.beforeSend){let W;try{W=n.beforeSend(C)}catch(sr){return console.warn("[hp] beforeSend failed; the event was not sent",sr),null}if(!W)return null;let{type:_e,event:Ct,messageId:rr,timestamp:or}=C;C={...W,type:_e,messageId:rr,timestamp:or,...Ct?{event:Ct}:{}}}if(n.validate){let W=n.validate(C,{producer:"web"});if(!W.valid){let _e=`[hp] invalid ${C.event??C.type}:
|
|
2
|
-
${
|
|
3
|
-
`)}`;if(n.throwOnInvalid)throw new Ve(_e);console.error(_e)}}return n.debug&&console.debug("[hp]",C.event??C.type,C),F(C,k),C}function w(c){if(c.type==="track"&&K(c),n.onEvent)try{n.onEvent(c)}catch(l){console.warn("[hp] onEvent observer failed",l)}}let L=()=>r&&o?.state?.personalisation===!0,D=()=>vn(M(n.getSessionId,null),n.propertyId),H=!1;function K(c){try{L()?(D(),bn(dt(On(c.event,c.properties))),H=!0):(xn(),H&&Cn(),H=!1)}catch(l){n.debug&&console.warn("[hp] the visit was not recorded",l)}}function F(c,l){!r||l||o.admits()?(w(c),m.enqueue(c)):o.hold(c)}let I=(c,l,k)=>Dn(n.plan,c)?S({type:"track",event:c,properties:l??{}},k):null,G=new Set,we=(c,l,k)=>c==="audiences"?{audiences:l?null:k.audiences??[]}:c==="decisions"?{decisions:(k.decisions??[]).map(P=>({slot:P.slot,decision_id:P.decision_id??null,arm:P.arm??null,...P.variant&&{variant:P.variant}}))}:{recommendation_request_id:k.recommendation_request_id??null};r&&(o=mn({adapter:n.consent,timeoutMs:n.consentTimeoutMs,cookieDomain:n.consentCookieDomain,debug:n.debug,store:i&&{read:()=>a.items,write:c=>i.write(c,u?.peek()??null)},release(c){c.context&&(c.context.consent=o.stamp()),w(c),m.enqueue(c)},onStatus(c){if(c==="granted"&&(u?.persist(),d.persist(),g&&x(g),v.persist(),m.replayPersisted?.()),c==="denied"){m.clearPersisted?.(),u?.forget(),d.forget();try{sessionStorage.removeItem(b)}catch{}v.forget()}try{n.onConsentStatus?.(c)}catch(l){console.warn("[hp] onConsentStatus observer failed",l)}},announce({action:c,state:l,withdrawal:k}){S({type:"track",event:"Consent Updated",properties:{action:c,state:l}},void 0,k),k&&m.flush()}}),o.start());let me=()=>n.holdout?ut(n.holdout):null,he=()=>lt(n.holdout,B(M(n.getAnonymousId,null))).group;function R(c,l){if(!Array.isArray(c?.decisions))return c;let k=n.holdout?.readoutSlots??[],P=B(M(n.getAnonymousId,null));return{...c,decisions:c.decisions.map(C=>{let W=C?.variant==="bandit"||C?.variant==="default"?C.variant:!l&&C?.holdout!==!0&&k.includes(C?.slot)?yn(C.slot,P):null;return W?W==="default"?{...C,variant:W,decision_id:null}:{...C,variant:W}:C})}}function j(c,l){if(f)return null;let k=c?.exposure,P=k?.holdout===!0,C=R(c,P);return v.remember(hn(C)),!k?.token||G.has(k.token)?null:(G.add(k.token),I("Personalisation Exposed",{exposure_token:k.token,surface:k.surface,holdout:P,holdout_share:k.holdout_share??me()?.holdout_share??null,personalised_share:k.personalised_share??me()?.personalised_share??null,...we(k.surface,P,C)},l))}return{track:I,page(c={},l){let k=n.pageType??null;c.type&&(ae.has(c.type)?(k=c.type,n.pageType=c.type):n.debug&&console.warn(`[hp] ignoring unknown page.type "${c.type}". Allowed: ${[...ae].join(", ")}`));let P=S({type:"page",properties:{name:c.name??null,navigation_trigger:c.navigation_trigger??Vt(),previous_message_id:_()}},{page:{type:k},...Wn(l)??{}});return P&&x(P.messageId),P},identify(c,l){let{userId:k,...P}=c&&typeof c=="object"?c:{};if(k!==void 0&&(h=B(k)),typeof P.email_sha256=="string"){let C=je(P.email_sha256);C?P.email_sha256=C:delete P.email_sha256}return S({type:"identify",traits:P},l)},reset(){h=null,x("")},productViewed:(c,l)=>I("Product Viewed",c,l),productListViewed:(c,l)=>I("Product List Viewed",c,l),recommendationsViewed:(c,l)=>I("Recommendations Viewed",c,l),personalisationExposed:(c,l)=>I("Personalisation Exposed",c,l),recordExposure:j,surfaceViewed({surface:c}={},l){if(!Ho.has(c)||!n.holdout||c!=="decisions"&&!L())return null;let k=B(M(n.getAnonymousId,null)),P=lt(n.holdout,k).group;if(P==="excluded")return null;let C=Mt({sessionId:be(M(n.getSessionId,null),k),surface:c});return C?j({exposure:{token:C,surface:c,holdout:P==="holdout"}},l):null},inHoldout:()=>he()!=="personalised",personalisationGroup:he,productClicked:(c,l)=>I("Product Clicked",c,l),productListFiltered:(c,l)=>I("Product List Filtered",c,l),productsSearched:(c,l)=>I("Products Searched",c,l),productAdded:(c,l)=>I("Product Added",c,l),productRemoved:(c,l)=>I("Product Removed",c,l),cartUpdated:(c,l)=>I("Cart Updated",Go(c),l),identityLinked:(c,l)=>I("Identity Linked",c,l),slotEngaged:(c,l)=>f?null:I("Slot Engaged",c,l),productVariantSelected:(c,l)=>I("Product Variant Selected",c,l),cartViewed:(c,l)=>I("Cart Viewed",c,l),productAddedToWishlist:(c,l)=>I("Product Added to Wishlist",c,l),productRemovedFromWishlist:(c,l)=>I("Product Removed from Wishlist",c,l),wishlistProductAddedToCart:(c,l)=>I("Wishlist Product Added to Cart",c,l),productShared:(c,l)=>I("Product Shared",c,l),cartShared:(c,l)=>I("Cart Shared",c,l),couponEntered:(c,l)=>I("Coupon Entered",c,l),couponApplied:(c,l)=>I("Coupon Applied",c,l),couponDenied:(c,l)=>I("Coupon Denied",c,l),couponRemoved:(c,l)=>I("Coupon Removed",c,l),checkoutStarted:(c,l)=>I("Checkout Started",c,l),promotionViewed:(c,l)=>I("Promotion Viewed",c,l),promotionClicked:(c,l)=>I("Promotion Clicked",c,l),checkoutStepViewed:(c,l)=>I("Checkout Step Viewed",c,l),checkoutStepCompleted:(c,l)=>I("Checkout Step Completed",c,l),paymentInfoEntered:(c,l)=>I("Payment Info Entered",c,l),checkoutFailed:(c,l)=>I("Checkout Failed",c,l),signedIn:(c,l)=>I("Signed In",c,l),signedUp:(c,l)=>I("Signed Up",c,l),signedOut(c){let l=I("Signed Out",{},c);return h=null,l},newsletterSubscribed:(c,l)=>I("Newsletter Subscribed",c,l),newsletterUnsubscribed:(c,l)=>I("Newsletter Unsubscribed",c,l),backInStockSubscribed:(c,l)=>I("Back In Stock Subscribed",c,l),productReviewed:(c,l)=>I("Product Reviewed",c,l),consentUpdated:(c,l)=>I("Consent Updated",c,l),flush:c=>m.flush(c),recommendationContext(){if(!L())return{};try{return D(),dt(En())}catch{return{}}},get consent(){return o?o.state:null},servingHeaders:()=>tn(o?.state??null),get lastPageMessageId(){return _()},get anonymousId(){return B(M(n.getAnonymousId,null))},[Ko](c,l,k){if(typeof this[c]=="function"){E=typeof k?.messageId=="string"&&typeof k?.timestamp=="string"?{messageId:k.messageId,timestamp:k.timestamp}:null;try{this[c](...Array.isArray(l)?l:[])}finally{E=null}}}}}var Et=Symbol.for("@thg-agentic/hyper-personalisation-web.bootstrap"),Xo=Symbol.for("@thg-agentic/hyper-personalisation-web.drain"),Yo=Symbol.for("@thg-agentic/hyper-personalisation-web.carry"),Jn=Symbol.for("@thg-agentic/hyper-personalisation-web.replay"),Zo=Symbol.for("@thg-agentic/hyper-personalisation-web.assignment"),Qo=Symbol.for("@thg-agentic/hyper-personalisation-web.recommendation-context"),Fe=e=>e?()=>U(e):()=>null,Zn={didomi:e=>nn({purposes:e.purposes}),onetrust:e=>rn({groups:e.groups,impliedConsent:e.impliedConsent}),cookie:e=>on({name:e.name,categories:e.categories,refusedValue:e.refusedValue,source:e.source,changeEvent:e.changeEvent}),forwarded:e=>sn({name:e.name}),static:e=>{if(!e.state||typeof e.state!="object")throw new Error('consent.state is required for provider "static"');return an(e.state)}},Xn=Object.keys(Zn),pe=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Be=e=>typeof e=="string"&&e!=="",es=e=>Array.isArray(e)&&e.every(t=>typeof t=="string"),Qn=e=>pe(e)&&typeof e.read=="function";function ts(e,t={}){let n=[];if(!pe(e))return["config must be { writeKey, apiHost? }"];Be(e.writeKey)||n.push("writeKey is required"),de(e.apiHost)||n.push("apiHost, when given, must be a host or an http(s) origin"),t.propertyId!==void 0&&!Be(t.propertyId)&&n.push("propertyId must be a string");let{consent:r}=t;if(!Qn(r)&&!(pe(r)&&Xn.includes(r.provider))&&n.push(`consent must be a consent adapter or { provider: ${Xn.join(" | ")} }`),t.autocapture!==void 0&&t.autocapture!==!1&&!pe(t.autocapture)&&n.push("autocapture must be false or { promotions, productLists, recommendations }"),t.beforeSend!==void 0&&typeof t.beforeSend!="function"&&n.push("beforeSend must be a function"),t.identityCookies!==void 0)if(!pe(t.identityCookies))n.push("identityCookies must be an object");else for(let o of["anonymousId","sessionId"]){let s=t.identityCookies[o];s!==void 0&&!Be(s)&&n.push(`identityCookies.${o} must be a cookie name`)}for(let o of["getCurrency","getShipToCountry","getLocale"])t[o]!==void 0&&typeof t[o]!="function"&&n.push(`${o} must be a function`);return t.dataLayer!==void 0&&(pe(t.dataLayer)?(t.dataLayer.name!==void 0&&!Be(t.dataLayer.name)&&n.push("dataLayer.name must be a string"),t.dataLayer.ignore!==void 0&&!es(t.dataLayer.ignore)&&n.push("dataLayer.ignore must be an array of strings")):n.push("dataLayer must be an object")),n}function ns(e,t,n){if(Qn(e))return st(e,t.consentPurposes);let{provider:r,...o}=e,s=n?.consent?.[r]??Zn[r];return st(s(o),t.consentPurposes)}var er=(e,t)=>`${e??""}|${t??""}`;function rs(e,t){let n=B(t?.userId),r=je(t?.emailSha256);if(!n&&!r)return null;try{let o=q(O("identified"),e);if(sessionStorage.getItem(o)===er(n,r))return null}catch{}return{...n?{userId:n}:{},...r?{email_sha256:r}:{}}}async function os({writeKey:e,apiHost:t},n){if(n.settings!==void 0)return{settings:St(n.settings),revalidated:null};let r=Un(t,e),o=$n(e);if(o)return{settings:o,revalidated:r};let s=await r,i=s??yt;return{settings:i,revalidated:Promise.resolve(i),unreachable:s===null}}function tr(e,t={}){let n=typeof window<"u"?window:null;if(n?.[Et])return n[Et];let r=Yt(e?.writeKey,()=>{let{analytics:s}=ye(U(O("consent")));return s===!0||s===null&&t.holdAcrossPages===!0}),o=is(e,t,r);if(n){let s=t.extend?.getSessionId??Fe(t.identityCookies?.sessionId),i=Fe(t.identityCookies?.anonymousId);n[Zo]=({slot:a,arm:u}={})=>Dt({sessionId:be(s(),i()),slot:a,arm:u}),n[Et]=o,n[Yo]=r,n[Xo]?.(o)}return o}function ss(){let e=globalThis.performance?.now?.();return new Date(Date.now()-(Number.isFinite(e)?e:0)).toISOString()}function Yn(e,t){if(t===!1)return()=>{};let n={promotions:!0,productLists:!0,recommendations:!0,exposures:!0,...t},r=s=>s&&typeof s=="object"?s:{},o=[];try{n.promotions&&o.push(We(e,r(n.promotions))),n.productLists&&o.push(Ge(e,r(n.productLists))),n.recommendations&&o.push(ze(e,r(n.recommendations))),n.exposures&&o.push(Ke(e,r(n.exposures)))}catch(s){console.warn("[hp] autocapture not started:",s?.message??s)}return()=>{for(let s of o)s()}}function qe(e){return e.close(),null}async function is(e,t,n){try{let r=ts(e,t);if(r.length)return console.warn(`[hp] not started, the configuration is invalid: ${r.join("; ")}`),qe(n);Ie(t.storageNames),Ae();let{writeKey:o}=e,[{settings:s,revalidated:i,unreachable:a},u]=await Promise.all([os(e,t),Promise.resolve(t.identity).catch(()=>null)]),d=i?s:null,p=!1,f=()=>p&&d&&Vn(o,d);if(i?.then(y=>{y&&(d=y,y.enabled?f():wt(o))}),a)return null;if(!s.enabled)return t.debug&&console.info("[hp] off: the settings for this write key do not enable it"),qe(n);let m=s.propertyId??t.propertyId;if(!m)return console.warn("[hp] not started: this write key covers several properties, so propertyId is required"),qe(n);let{preset:b}=t,g=rs(m,u),_=t.debug===!0||s.debug?.enabled===!0||fn(s.debug?.tokenSha256??null,O("debug")),x=B(u?.userId),v=zn({apiHost:s.apiHost??de(e.apiHost),writeKey:o,propertyId:m,pageType:ae.has(t.pageType)?t.pageType:void 0,environment:s.environment,queryParamAllowlist:s.queryParamAllowlist??void 0,plan:s.plan,sampleRate:s.sampleRate??1,holdout:s.holdouts?.[m],getCurrency:t.getCurrency,getShipToCountry:t.getShipToCountry,getLocale:t.getLocale,consent:ns(t.consent,s,b),consentCookieDomain:t.consentCookieDomain,storageNames:t.storageNames,holdAcrossPages:t.holdAcrossPages===!0,mintAnonymousId:t.mintAnonymousId,debug:_,anonymousIdSource:s.anonymousId??void 0,getAnonymousId:Fe(t.identityCookies?.anonymousId),getSessionId:t.identityCookies?.sessionId?Fe(t.identityCookies.sessionId):void 0,getUserId:()=>x,beforeSend:t.beforeSend,...t.extend,onConsentStatus(y){if(y==="granted"&&(p=!0,f()),y==="denied"){wt(o);try{sessionStorage.removeItem(q(O("identified"),m))}catch{}}t.extend?.onConsentStatus?.(y)},onEvent(y){if(y.type==="identify"){let w=er(B(y.userId),je(y.traits?.email_sha256));try{sessionStorage.setItem(q(O("identified"),m),w)}catch{}}t.extend?.onEvent?.(y)}});typeof window<"u"&&(window[Qo]=()=>v.recommendationContext());for(let[y,w,L]of n.take())try{v[Jn](y,w,L)}catch(D){console.warn(`[hp] carried ${y} failed`,D)}v[Jn]("page",[t.pageType?{type:t.pageType}:{}],{messageId:Z(),timestamp:ss()});let h=Yn(v,t.autocapture),E=v.page;v.page=(...y)=>{let w=E(...y);return h(),h=Yn(v,t.autocapture),w};let S=typeof window<"u"?window:null;if(t.dataLayer&&S){let y=t.dataLayer.name??"dataLayer";S[y]=S[y]||[],dn(v,{...b,dataLayer:S[y],ignore:[...b?.ignore??[],...t.dataLayer.ignore??[]],onError:w=>_&&console.warn("[hp] dataLayer mapping failed",w)})}return g&&v.identify(g),v}catch(r){return console.warn("[hp] not started:",r?.message??r),qe(n)}}function nr(e){e.initialized=!0,tr(e._config,e._options).then(t=>{let n=e.splice(0);if(!t){for(let r of e.methods??[])e[r]=()=>e;return}e.client=t;for(let[r,o]of Object.entries(t))typeof o=="function"&&(e[r]=o);for(let[r,...o]of n)try{t[r]?.(...o)}catch(s){console.warn(`[hp] queued ${r} failed`,s)}})}var V=window.hp=window.hp||[];V.servingHeaders??(V.servingHeaders=()=>({}));V.inHoldout??(V.inHoldout=()=>!0);V.personalisationGroup??(V.personalisationGroup=()=>"excluded");V.recommendationContext??(V.recommendationContext=()=>({}));V.initialized||(V._config?nr(V):V.load=(e,t)=>{V._config=e,V._options=t??{},nr(V)});})();
|
|
1
|
+
(()=>{var $t=e=>{throw TypeError(e)};var Sr=(e,t,n)=>t.has(e)||$t("Cannot "+n);var Ut=(e,t,n)=>t.has(e)?$t("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n);var N=(e,t,n)=>(Sr(e,t,"access private method"),n);var we=[];for(let e=2;we.length<64;e++)we.every(t=>e%t!==0)&&we.push(e);var Vt=e=>(e-Math.floor(e))*2**32>>>0,wr=we.slice(0,8).map(e=>Vt(Math.sqrt(e))),_r=we.map(e=>Vt(Math.cbrt(e))),z=(e,t)=>e>>>t|e<<32-t;function Bt(e){let t=new TextEncoder().encode(String(e)),n=new Uint32Array((t.length+8>>6)*16+16);t.forEach((i,s)=>{n[s>>2]|=i<<24-s%4*8}),n[t.length>>2]|=128<<24-t.length%4*8,n[n.length-2]=Math.floor(t.length/2**29),n[n.length-1]=t.length*8;let r=Uint32Array.from(wr),o=new Uint32Array(64);for(let i=0;i<n.length;i+=16){for(let S=0;S<64;S++){if(S<16){o[S]=n[i+S];continue}let b=o[S-15],I=o[S-2],k=z(b,7)^z(b,18)^b>>>3,h=z(I,17)^z(I,19)^I>>>10;o[S]=o[S-16]+k+o[S-7]+h}let[s,a,u,d,p,f,y,x]=r;for(let S=0;S<64;S++){let b=z(p,6)^z(p,11)^z(p,25),I=x+b+(p&f^~p&y)+_r[S]+o[S]|0,h=(z(s,2)^z(s,13)^z(s,22))+(s&a^s&u^a&u)|0;[x,y,f,p,d,u,a,s]=[y,f,p,d+I|0,u,a,s,I+h|0]}[s,a,u,d,p,f,y,x].forEach((S,b)=>{r[b]+=S})}return Array.from(r,i=>i.toString(16).padStart(8,"0")).join("")}var Ye=(e,t)=>e<<t|e>>>32-t;function jt(e){let t=new Uint32Array((e.length+8>>6)*16+16);e.forEach((i,s)=>{t[s>>2]|=i<<24-s%4*8}),t[e.length>>2]|=128<<24-e.length%4*8,t[t.length-2]=Math.floor(e.length/2**29),t[t.length-1]=e.length*8;let n=Uint32Array.of(1732584193,4023233417,2562383102,271733878,3285377520),r=new Uint32Array(80);for(let i=0;i<t.length;i+=16){for(let f=0;f<80;f++)r[f]=f<16?t[i+f]:Ye(r[f-3]^r[f-8]^r[f-14]^r[f-16],1);let[s,a,u,d,p]=n;for(let f=0;f<80;f++){let y,x;f<20?(y=a&u|~a&d,x=1518500249):f<40?(y=a^u^d,x=1859775393):f<60?(y=a&u|a&d|u&d,x=2400959708):(y=a^u^d,x=3395469782);let S=Ye(s,5)+y+p+x+r[f]|0;[p,d,u,a,s]=[d,u,Ye(a,30),s,S]}[s,a,u,d,p].forEach((f,y)=>{n[y]+=f})}let o=new Uint8Array(20);return n.forEach((i,s)=>{o[s*4]=i>>>24,o[s*4+1]=i>>>16,o[s*4+2]=i>>>8,o[s*4+3]=i}),o}var br="e28e8ada-5b76-4209-bba9-79b823a0af75",xr=e=>Array.from(e,t=>t.toString(16).padStart(2,"0")).join("");function qt(e,t){let n=Uint8Array.from(e.replace(/-/g,"").match(/../g),a=>Number.parseInt(a,16)),r=new TextEncoder().encode(t),o=new Uint8Array(16+r.length);o.set(n),o.set(r,16);let i=jt(o).slice(0,16);i[6]=i[6]&15|80,i[8]=i[8]&63|128;let s=xr(i);return`${s.slice(0,8)}-${s.slice(8,12)}-${s.slice(12,16)}-${s.slice(16,20)}-${s.slice(20)}`}var re=(e,t)=>typeof e=="string"&&e!==""&&e.length<=t?e:null;function ke(e,t){return re(e,64)??re(t,256)}function Ft({sessionId:e,slot:t,arm:n}={}){let r=re(e,256);return!r||!re(t,256)||!re(n,256)?null:qt(br,`${r}:${t}:${n}`)}var Ir="4b1f2c7e-9a3d-4e58-b6c1-2d7f0a9e8c53";function Ht({sessionId:e,surface:t}={}){let n=re(e,256);return!n||!re(t,64)?null:qt(Ir,`${n}:${t}`)}var Ar=Array.from({length:11},(e,t)=>t/10);function vr(e,t){if(!e.isIntersecting)return!1;let{intersectionRect:n,boundingClientRect:r,rootBounds:o}=e;if(!n||!r)return!0;let i=n.width*n.height,s=r.width*r.height,a=o?Math.min(s,o.width*o.height):s;return a>0&&i/a>=t}function X(e){let{selector:t,onViewable:n,visibleRatio:r=.5,dwellMs:o=1e3,root:i=document}=e,s=new Map,a=new WeakSet,u=new Set,d=h=>{s.has(h)||s.set(h,setTimeout(()=>{s.delete(h),n(h)&&(f?.unobserve(h),u.delete(h))},o))},p=h=>{clearTimeout(s.get(h)),s.delete(h)},f=typeof IntersectionObserver>"u"?null:new IntersectionObserver(h=>{for(let v of h){let w=v.target;if(!vr(v,r)){u.delete(w),p(w);continue}u.add(w),y()&&d(w)}},{threshold:Ar}),y=()=>typeof document>"u"||document.visibilityState!=="hidden",x=()=>{if(y())for(let h of u)d(h);else for(let h of u)p(h)};document.addEventListener("visibilitychange",x);let S=()=>{for(let h of i.querySelectorAll(t))a.has(h)||(a.add(h),f?.observe(h))},b=typeof MutationObserver>"u"?null:new MutationObserver(()=>S()),I=()=>{S(),b?.observe(i===document?document.body:i,{childList:!0,subtree:!0})},k=i===document&&!document.body;return k?document.addEventListener("DOMContentLoaded",I,{once:!0}):I(),()=>{k&&document.removeEventListener("DOMContentLoaded",I),document.removeEventListener("visibilitychange",x),f?.disconnect(),b?.disconnect();for(let h of s.values())clearTimeout(h);s.clear(),u.clear()}}function Xe(e,t={}){if(typeof window>"u"||typeof document>"u")return()=>{};let{selector:n="[data-hp-exposure]",visibleRatio:r=.5,dwellMs:o=1e3,root:i=document}=t;return X({selector:n,root:i,visibleRatio:r,dwellMs:o,onViewable(s){let a=s.dataset?.hpExposure?.trim();return a&&e.surfaceViewed({surface:a}),!0}})}var Kt=e=>{try{return new URL(e,document.baseURI).toString()}catch{return e}};function Ze(e,t={}){if(typeof window>"u"||typeof document>"u")return()=>{};let{selector:n="[data-hp-list]",tileSelector:r="[data-hp-product]",visibleRatio:o=.5,dwellMs:i=1e3,root:s=document}=t,a=new Set,u=S=>S.closest?.(n)??null,d=S=>{let b=S.dataset.hpList?.trim();return b?{list_id:b,category:S.dataset.hpListCategory?.trim()||null}:null},p=S=>Array.from(S.querySelectorAll(r)).filter(b=>u(b)===S),f=(S,b)=>{try{let I=JSON.parse(S.dataset.hpProduct??"");return!I||typeof I!="object"||!I.product_id?null:{...I,position:I.position??b+1,...I.url?{url:Kt(I.url)}:{},...I.image_url?{image_url:Kt(I.image_url)}:{}}}catch{return null}},y=X({selector:`${n} ${r}`,root:s,visibleRatio:o,dwellMs:i,onViewable(S){let b=u(S);if(!b||b.dataset.hpListView==="manual")return!0;let I=d(b);if(!I||a.has(I.list_id))return!0;let k=new Set,h=p(b).map(f).filter(v=>!v||k.has(v.product_id)?!1:(k.add(v.product_id),!0));return h.length===0||(a.add(I.list_id),e.productListViewed({...I,products:h})),!0}}),x=S=>{let I=S.target?.closest?.("a[href]")?.closest(r);if(!I)return;let k=u(I),h=f(I,k?p(k).indexOf(I):0);h&&e.productClicked({list_id:k?d(k)?.list_id??null:null,products:[h]})};return document.addEventListener("click",x,!0),()=>{document.removeEventListener("click",x,!0),y()}}function T(e,...t){let n=e.dataset??{};for(let r of t){let o=n[r];if(typeof o=="string"&&o.trim()!=="")return o.trim()}return null}function Ce(e,...t){let n=T(e,...t);if(n===null)return null;let r=Number(n);return Number.isFinite(r)?Math.trunc(r):null}function Er(e,t){let n=T(e,"hpPromotion");return n?{promotion_id:n,name:T(e,"hpPromotionName"),creative:T(e,"hpPromotionCreative"),position:T(e,"hpPromotionSlot")??String(t+1)}:null}function Qe(e,t={}){if(typeof window>"u"||typeof document>"u")return()=>{};let{selector:n="[data-hp-promotion]",resolve:r=Er,visibleRatio:o=.5,dwellMs:i=1e3,root:s=document}=t,a=new Set,u=f=>{let y=Array.from(s.querySelectorAll(n)),x=y.indexOf(f);try{return r(f,x<0?y.length:x)}catch{return null}},d=X({selector:n,root:s,visibleRatio:o,dwellMs:i,onViewable(f){let y=u(f);return!y||a.has(y.promotion_id)?!1:(a.add(y.promotion_id),e.promotionViewed(y),!0)}}),p=f=>{let y=f.target;if(!y||typeof y.closest!="function")return;let x=y.closest(n),S=y.closest("a[href]");if(!x||!S||!x.contains(S)&&!S.contains(x))return;let b=u(x);b&&e.promotionClicked({...b,href:S.getAttribute("href")})};return document.addEventListener("click",p,!0),()=>{document.removeEventListener("click",p,!0),d()}}function kr(e){let t=T(e,"hpRecoRequest"),n=T(e,"hpRecoPlacement"),r=T(e,"hpRecoStrategy");if(!t||!n||!r)return null;let o=T(e,"hpRecoHoldout");return{recommendation_request_id:t,placement:n,strategy:r,recipe:T(e,"hpRecoRecipe"),algorithm_version:T(e,"hpRecoAlgorithmVersion"),experiment:T(e,"hpRecoExperiment"),variant:T(e,"hpRecoVariant"),holdout:o===null?null:o==="true",assignment_bucket:Ce(e,"hpRecoBucket"),assignment_unit_kind:T(e,"hpRecoUnitKind"),candidate_set_fingerprint:T(e,"hpRecoFingerprint"),candidate_set_size:Ce(e,"hpRecoCandidates"),ranked_by:T(e,"hpRecoRankedBy"),served_at:T(e,"hpRecoServedAt")}}function Cr(e,t){let n=T(e,"hpRecoProduct");if(!n)return null;let r=T(e,"hpRecoPrice");return{product_id:n,sku:T(e,"hpRecoSku"),name:T(e,"hpRecoName"),brand:T(e,"hpRecoBrand"),price:r===null||Number.isNaN(Number(r))?null:Number(r),position:Ce(e,"hpRecoPosition")??t+1}}function et(e,t={}){if(typeof window>"u"||typeof document>"u")return()=>{};let{selector:n="[data-hp-reco-request]",tileSelector:r="[data-hp-reco-product]",resolveRail:o=kr,resolveTile:i=Cr,visibleRatio:s=.5,dwellMs:a=1e3,root:u=document}=t,d=new Set,p=new Map,f=new Map,y=(h,...v)=>{try{return h(...v)}catch{return null}},x=h=>typeof h.closest=="function"?h.closest(n):null,S=h=>Array.from(h.querySelectorAll(r)),b=(h,v)=>{if(f.delete(v.recommendation_request_id),d.has(v.recommendation_request_id))return;let w=S(h).map((_,P)=>y(i,_,P)).filter(_=>_?.product_id);if(w.length===0)return;d.add(v.recommendation_request_id);let g=[...p.get(v.recommendation_request_id)?.values()??[]].sort((_,P)=>_.position-P.position);e.recommendationsViewed({...v,products:w,viewable:g})},I=X({selector:`${n} ${r}`,root:u,visibleRatio:s,dwellMs:a,onViewable(h){let v=x(h);if(!v)return!0;let w=y(o,v);if(!w)return!0;let g=w.recommendation_request_id,_=y(i,h,S(v).indexOf(h));if(_?.product_id){p.has(g)||p.set(g,new Map);let P=p.get(g);P.has(_.position)||P.set(_.position,{position:_.position,product_id:_.product_id,viewable_at:new Date().toISOString()})}return!d.has(g)&&!f.has(g)&&f.set(g,setTimeout(()=>b(v,w),0)),!0}}),k=h=>{let v=h.target;if(!v||typeof v.closest!="function")return;let w=v.closest(r);if(!w)return;let g=v.closest("[data-hp-reco-interaction]"),_=v.closest("a[href]"),P=_&&(w.contains(_)||_.contains(w));if(!(g&&w.contains(g))&&!P)return;let M=x(w);if(!M)return;let H=y(o,M);if(!H)return;let K=y(i,w,S(M).indexOf(w));if(!K?.product_id)return;let j=H.recommendation_request_id;d.has(j)||(clearTimeout(f.get(j)),b(M,H)),e.productClicked({recommendation_request_id:H.recommendation_request_id,interaction:T(g??w,"hpRecoInteraction")??"navigate",products:[K]})};return document.addEventListener("click",k,!0),()=>{document.removeEventListener("click",k,!0);for(let h of f.values())clearTimeout(h);f.clear(),I()}}var fe=new Set(["home","list","product","search","basket","checkout","order_confirmation","login","register","account","info","blog","other"]),Pr=new Set(["products"]),Gt=e=>e==null;function tt(e,t=new WeakSet){if(e instanceof Date)return e.toJSON();if(e===null||typeof e!="object"||t.has(e))return e;t.add(e);let n=Array.isArray(e)?e.map(r=>tt(r,t)):Object.fromEntries(Object.entries(e).filter(([,r])=>!Gt(r)).map(([r,o])=>[r,tt(o,t)]));return t.delete(e),n}function nt(e){if(!e)return e;let t=tt(e);for(let n of Pr)n in e&&Gt(e[n])&&(t[n]=e[n]);return t}function zt(){try{let e=performance.getEntriesByType("navigation")[0];return e?["navigate","reload","back_forward","prerender"].includes(e.type)?e.type:"navigate":null}catch{return null}}var Or=new Set(["","unknown","undefined","null","none","nil","nan","anonymous","guest","0","-1"]);function q(e){if(e==null)return null;let t=String(e).trim();return Or.has(t.toLowerCase())?null:t}function Z(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}var Wt="1.12";function D(e){if(typeof document>"u")return null;try{let t=document.cookie.split(";").map(n=>n.trim()).find(n=>n.startsWith(`${e}=`));return t&&decodeURIComponent(t.slice(e.length+1))||null}catch{return null}}var Lr={gclid:"gclid",fbclid:"fbclid",ttclid:"ttclid"},Nr=/[a-z0-9._%+-]+@[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]{2,}/i,Tr=/\d(?:[ -]?\d){12,}/,Rr=new Set(["gclid","fbclid","ttclid","msclkid"]);function Yt(e,t=""){if(typeof e!="string"||e==="")return!1;let n=[e];try{let o=decodeURIComponent(e.replace(/\+/g," "));o!==e&&n.push(o)}catch{}let r=!Rr.has(String(t).toLowerCase());return n.some(o=>Nr.test(o)||r&&Tr.test(o))}function Mr(e){if(!e)return null;let t=new URLSearchParams(e),n={},r={utm_source:"source",utm_medium:"medium",utm_campaign:"name",utm_term:"term",utm_content:"content"};for(let[o,i]of[...Object.entries(r),...Object.entries(Lr)]){let s=t.get(o);s&&!Yt(s,o)&&(n[i]=s)}return Object.keys(n).length?n:null}var Dr=["email","e-mail","password","pass","pwd","token","auth","code","uid","customer_id","postcode","zip","phone","applyCode"],$r=["email","password","passwd","pwd","token","secret","auth","session","phone","postcode","zip","address","birth","card","cvv","iban","ssn","customerid","userid"],Ur=["tel","uid","dob"],Vr=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","gclid","fbclid","ttclid","msclkid"],Br=new Set(Dr.map(e=>e.toLowerCase()));function jr(e){let t=String(e).toLowerCase();if(Br.has(t))return!0;let n=t.replace(/[-_.]/g,"");return $r.some(o=>n.includes(o))?!0:String(e).replace(/([a-z0-9])([A-Z])/g,"$1 $2").toLowerCase().split(/[^a-z0-9]+/).some(o=>Ur.includes(o))}function rt(e){return(e??[]).map(t=>{try{return new RegExp(`^${t}$`,"i")}catch{return{test:n=>n.toLowerCase()===String(t).toLowerCase()}}})}function Xt(e,t={}){if(!e)return null;try{let n=new URLSearchParams(e),r=t.queryParamAllowlist?rt([...t.queryParamAllowlist,...Vr]):null,o=rt(t.queryParamBlocklist),i=rt(t.sensitiveParamAllowlist);for(let a of new Set(n.keys()))(jr(a)&&!i.some(d=>d.test(a))||n.getAll(a).some(d=>Yt(d,a))||(r?!r.some(d=>d.test(a)):o.some(d=>d.test(a))))&&n.delete(a);let s=n.toString();return s?`?${s}`:""}catch{return""}}function qr(e,t,n={}){if(!e)return null;try{let r=new URL(e);return t&&r.origin===t?r.origin+r.pathname+(Xt(r.search,n)??""):r.origin+r.pathname}catch{return null}}function ot(e){if(typeof e!="string")return null;let t=e.trim().toUpperCase();return/^[A-Z]{3}$/.test(t)?t:null}function Jt(e){if(typeof e!="string")return null;let t=e.trim().toUpperCase();return/^[A-Z]{2}$/.test(t)?t:null}function Fr(e){if(typeof e!="string")return null;let t=e.trim().replace(/_/g,"-");if(!/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(t))return null;try{return Intl.getCanonicalLocales(t)[0]}catch{return null}}function R(e,t){if(typeof e=="function")try{let n=e();if(n!=null)return n}catch{}return t}function Hr(e){return{property_id:String(e.propertyId),channel:e.channel??null,market:e.market??null,environment:e.environment??null}}function Kr(e){let t=typeof window<"u"?window.location:null,n=t?Xt(t.search,e):null;return{url:t?t.origin+t.pathname+(n??""):e.pageUrl??null,path:t?t.pathname:e.pagePath??null,search:n??null,referrer:typeof document<"u"?qr(document.referrer,t?.origin,e):null,title:typeof document<"u"&&document.title||null,type:e.pageType??null}}function Zt(e,t={}){let n=typeof window<"u"?window.location:null,r={library:{name:e.libraryName,version:e.libraryVersion},spec:{version:Wt},source:Hr(e),session_id:R(e.getSessionId,null),render_id:R(e.getRenderId,null),identity_trust:q(R(e.getAnonymousId,null))?"device":"none",page:Kr(e),campaign:n?Mr(n.search):null,device:{user_agent:typeof navigator<"u"?navigator.userAgent:null,type:null,viewport_width:typeof window<"u"?window.innerWidth:null,viewport_height:typeof window<"u"?window.innerHeight:null},currency:ot(R(e.getCurrency,e.currency)),locale:Fr(R(e.getLocale,e.locale)),timezone:(()=>{try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return null}})(),country:Jt(e.country),ship_to_country:Jt(R(e.getShipToCountry,e.shipToCountry)),ip:null,bot:null,consent:e.getConsent?.()??null,experiments:R(e.getExperiments,null),audiences:R(e.getAudiences,null),decisions:R(e.getDecisions,null),recommendations:R(e.getRecommendations,null),extensions:R(e.getExtensions,null)};return Qt(r,t)}function Qt(e,t){if(!t)return e;let n={...e};for(let[r,o]of Object.entries(t))o&&typeof o=="object"&&!Array.isArray(o)&&e[r]&&typeof e[r]=="object"&&!Array.isArray(e[r])?n[r]=Qt(e[r],o):o!==void 0&&(n[r]=o);return n}var ie=Object.freeze({consent:"thg_hp_consent",retry:"thg_hp_retry",sessionSignals:"thg_hp_session_signals",identified:"thg_hp_identified",previousPage:"thg_hp_prev_page",decisions:"thg_hp_decisions",settings:"thg_hp_settings",held:"thg_hp_held",carried:"thg_hp_carried",recommendationContext:"thg_hp_recommendation_context",anonymousId:"thg_hp_anonymous_id",session:"thg_hp_session",preview:"thg_hp_preview",debug:"thg_hp_debug"}),Gr={retry:"cdp.retry",sessionSignals:"cdp_session_signals",identified:"cdp_identified",previousPage:"cdp_prev_page_message_id"},st={...ie},en=!1;function Pe(e){let t={...ie};for(let[n,r]of Object.entries(e??{})){if(!(n in ie))throw new Error(`storageNames: unknown kind "${n}"`);if(typeof r!="string"||!/^[\w.-]+$/.test(r))throw new Error(`storageNames.${n} must be a non-empty cookie-safe name`);t[n]=r}st=t}function L(e){return st[e]}var zr="_",tn=e=>e==null||e===""?zr:String(e);function F(e,t,n){let r=`${e}:${tn(t)}`;return n==null||n===""?r:`${r}:${n}`}function nn(e,t){return`${e}:${tn(t)}:`}function Oe(){if(!en){en=!0;for(let e of[()=>localStorage,()=>sessionStorage]){let t;try{if(t=e(),!t)continue;for(let n of Object.keys(t))for(let[r,o]of Object.entries(Gr)){if(n!==o&&!n.startsWith(`${o}:`))continue;let i=st[r]+n.slice(o.length);t.getItem(i)===null&&t.setItem(i,t.getItem(n)),t.removeItem(n)}}catch{}}}}var at=1800*1e3,Le=()=>{try{return typeof sessionStorage>"u"?null:sessionStorage}catch{return null}},it=()=>{try{return typeof localStorage>"u"?null:localStorage}catch{return null}};function rn(e,t=()=>Date.now()){return{read(){try{let n=Le()?.getItem(e),r=n?JSON.parse(n):null;return!r||t()-r.savedAt>at?(r&&Le()?.removeItem(e),{items:[],anonymousId:null}):{items:Array.isArray(r.items)?r.items:[],anonymousId:typeof r.anonymousId=="string"?r.anonymousId:null}}catch{return{items:[],anonymousId:null}}},write(n,r=null){try{n.length===0?Le()?.removeItem(e):Le()?.setItem(e,JSON.stringify({savedAt:t(),items:n,anonymousId:r}))}catch{}}}}var Wr=400*24*60*60;function Q(e,t,n,r){if(typeof document>"u")return!1;try{let o=typeof location<"u"&&location.protocol==="https:"?"; Secure":"",i=r?`; Domain=${r}`:"",s=n===null?"":`; Max-Age=${n}`;return document.cookie=`${e}=${encodeURIComponent(t)}; Path=/${s}; SameSite=Lax`+i+o,!0}catch{return!1}}function Jr({name:e,domain:t=null,legacyKey:n,read:r,mint:o,mayStore:i,carried:s=null}){let a=null;try{a=it()?.getItem(n)||null}catch{a=null}let u=r(e)??a??s,d=!1,p=()=>{if(!(d||u===null||!i())&&(d=Q(e,u,Wr,t),!(!d||a===null)))try{it()?.removeItem(n),a=null}catch{}};return{get(){return u??(u=o()),p(),u},peek:()=>u,persist:p,forget(){Q(e,"",0,t),u=null,d=!1;try{it()?.removeItem(n)}catch{}a=null}}}function on(e,{carried:t=null,mayStore:n,orOwn:r=!1}){let o=e.anonymousIdSource,i=()=>Jr({name:L("anonymousId"),domain:e.consentCookieDomain??null,legacyKey:F(L("anonymousId"),e.propertyId),read:D,mint:Z,mayStore:n,carried:t});if(o?.source==="hp_cookie"){let u=i();return e.getAnonymousId=()=>u.get(),u}if(o?.source==="site_cookie"&&o.cookie){let u=o.cookie;e.getAnonymousId=()=>D(u)}if(!r)return null;let s=e.getAnonymousId,a=i();return e.getAnonymousId=()=>q(R(s,null))??a.get(),a}var Yr=200;function sn(e,t,n=()=>Date.now()){let r=()=>F(L("carried"),e),o=!1;function i(){try{let s=JSON.parse(sessionStorage.getItem(r())??"null");return!s||!(n()-s.savedAt<=at)?[]:Array.isArray(s.calls)?s.calls:[]}catch{return[]}}return{take(){let s=i();try{sessionStorage.removeItem(r())}catch{}return s},save(s){try{return o||!t()?!1:(sessionStorage.setItem(r(),JSON.stringify({savedAt:n(),calls:[...i(),...s].slice(-Yr)})),!0)}catch{return!1}},close(){o=!0}}}var si=ie.consent,Ne=Object.freeze({visitor:"visitor",host:"host"}),an=()=>({basis:Ne.host}),Xr="hp-consent",ae=["analytics","marketing","personalisation","ad_user_data","ad_personalization"],cn={analytics:"a",marketing:"m",personalisation:"p",ad_user_data:"aud",ad_personalization:"adp"},Y=()=>typeof window<"u",un=()=>Y()&&typeof window.cookieStore?.addEventListener=="function",Zr=e=>e===!0?!0:e===!1?!1:null;function pe(e){if(!e||typeof e!="object")return null;let t={};for(let n of ae)t[n]=Zr(e[n]);return t}function ut(e,t){return!e||!t?e===t:ae.every(n=>e[n]===t[n])}function lt(e){if(!e)return"";let t=[];for(let n of ae){let r=e[n];(r===!0||r===!1)&&t.push(`${cn[n]}=${r?1:0}`)}return t.join("&")}function ln(e){return{[Xr]:lt(pe(e))}}function _e(e){let t=pe({});if(e==null)return t;let n=String(e).trim();if(n.length>=2&&n.startsWith('"')&&n.endsWith('"')&&(n=n.slice(1,-1)),n.includes("%"))try{n=decodeURIComponent(n.replace(/\+/g," "))}catch{return t}let r=Object.fromEntries(Object.entries(cn).map(([i,s])=>[s,i])),o=new Set;for(let i of n.split("&")){let s=i.indexOf("=");if(s<=0)continue;let a=r[i.slice(0,s).trim()],u={1:!0,0:!1}[i.slice(s+1).trim()]??null;!a||u===null||o.has(a)||(t[a]===null?t[a]=u:t[a]!==u&&(t[a]=null,o.add(a)))}return t}function dt(e,t){if(!t)return e;let n=r=>{if(!r)return r;let o={...r};for(let i of ae)i!=="analytics"&&t[i]!==!0&&(o[i]=null);return o};return{name:e.name,basis:e.basis,read:()=>n(e.read()),subscribe:r=>e.subscribe(o=>r(n(o))),get observesChanges(){return e.observesChanges}}}function ft(e,t={}){return{...e,ad_user_data:t.ad_user_data??e.marketing,ad_personalization:t.ad_personalization??e.marketing}}function Te(e,t){let n=!0;return{notify:()=>{if(n)try{let o=e();o&&t(o)}catch{}},stop:()=>n=!1}}var Qr={analytics:["analytics","measure_content_performance","audience_measurement"],marketing:["marketing","targeting","advertising","select_personalized_ads","create_ads_profile"],personalisation:["personalisation","personalization","select_personalized_content","create_content_profile"]};function eo(e,t){if(!t?.length)return null;let n=!1;for(let[r,o]of Object.entries(e)){let i=String(o?.id??r).toLowerCase();if(t.some(a=>{let u=String(a).toLowerCase();return i===u||i.startsWith(`${u}-`)})&&(n=!0,o?.enabled===!0))return!0}return n?!1:null}function dn(e={}){let t={...Qr,...e.purposes};function n(){let r=Y()?window.Didomi:void 0;if(!r?.getCurrentUserStatus||r.shouldUserStatusBeCollected?.()!==!1||r.isUserStatusPartial?.()===!0)return null;let o=r.getCurrentUserStatus()?.purposes;if(!o||typeof o!="object")return null;let i=s=>eo(o,t[s]);return ft({analytics:i("analytics"),marketing:i("marketing"),personalisation:i("personalisation")},{ad_user_data:i("ad_user_data"),ad_personalization:i("ad_personalization")})}return{name:"didomi",read:n,observesChanges:!0,subscribe(r){if(!Y())return()=>{};let o=Te(n,r),i={event:"consent.changed",listener:o.notify};return window.didomiEventListeners=window.didomiEventListeners||[],window.didomiEventListeners.push(i),window.didomiOnReady=window.didomiOnReady||[],window.didomiOnReady.push(s=>{o.notify(),(s??window.Didomi)?.on?.("consent.changed",o.notify)}),window.Didomi?.on?.("consent.changed",o.notify),o.stop}}}function ct(e){let t={};if(!e)return t;for(let n of String(e).split("&")){let r=n.indexOf("=");if(r<=0)continue;let o=n.slice(r+1).replace(/\+/g," ");try{o=decodeURIComponent(o)}catch{}t[n.slice(0,r)]=o}return t}function fn(e={}){let t={analytics:"C0002",personalisation:"C0003",marketing:"C0004",...e.groups},n=e.impliedConsent===!0;function r(){let s=Y()?window.OnetrustActiveGroups:void 0;if(typeof s=="string"&&s.length){let d=new Set(s.split(",").map(p=>p.trim()).filter(Boolean));return p=>d.has(p)}let a=ct(D("OptanonConsent"));if(!a.groups)return null;let u={};for(let d of a.groups.split(",")){let[p,f]=d.split(":");p?.trim()&&(u[p.trim()]=f==="1"?!0:f==="0"?!1:null)}return d=>u[d]??null}function o(){if(D("OptanonAlertBoxClosed"))return!0;try{if(Y()&&window.OneTrust?.IsAlertBoxClosed?.()===!0)return!0}catch{}return Number.parseInt(ct(D("OptanonConsent")).interactionCount,10)>0?!0:n&&Y()&&(!!window.OneTrust||typeof window.OnetrustActiveGroups=="string")}function i(){if(!o())return null;let s=r();if(!s)return null;let a=f=>t[f]?s(t[f]):null,u=ft({analytics:a("analytics"),marketing:a("marketing"),personalisation:a("personalisation")},{ad_user_data:a("ad_user_data"),ad_personalization:a("ad_personalization")}),d=ct(D("OptanonConsent"));return(typeof navigator<"u"&&navigator.globalPrivacyControl===!0||d.isGpcEnabled==="1")&&(u.marketing=!1,u.ad_user_data=!1,u.ad_personalization=!1),u}return{name:"onetrust",read:i,observesChanges:!0,subscribe(s){if(!Y())return()=>{};let a=Te(i,s),u=!1,d=()=>{u||!window.OneTrust?.OnConsentChanged||(u=!0,window.OneTrust.OnConsentChanged(a.notify))};window.addEventListener("OneTrustGroupsUpdated",a.notify);let p=window.OptanonWrapper;return window.OptanonWrapper=function(...y){try{typeof p=="function"&&p.apply(this,y)}finally{d(),a.notify()}},d(),()=>{a.stop(),window.removeEventListener("OneTrustGroupsUpdated",a.notify)}}}}function to(e){let t=String(e).split(",").map(n=>n.trim()).filter(Boolean);return t.length?new Set(t):null}function pn(e){let{name:t,categories:n,parse:r=to,refusedValue:o,source:i="native",changeEvent:s="thg_hp_consent_change"}=e??{};if(typeof t!="string"||t==="")throw new Error("cookieConsent: `name` (the cookie your banner writes) is required");if(!n||typeof n.analytics!="string")throw new Error("cookieConsent: `categories.analytics` (the category id that grants analytics) is required");function a(){let u=D(t);if(u==null)return null;let d;if(o!==void 0&&u===o)d=new Set;else try{d=r(u)}catch{d=null}if(!(d instanceof Set))return null;let p=f=>n[f]?d.has(n[f]):null;return ft({analytics:p("analytics"),marketing:p("marketing"),personalisation:p("personalisation")},{ad_user_data:p("ad_user_data"),ad_personalization:p("ad_personalization")})}return{name:i,read:a,get observesChanges(){return un()},subscribe(u){if(!Y())return()=>{};let d=Te(a,u),p=f=>{[...f.changed??[],...f.deleted??[]].some(x=>x?.name===t)&&d.notify()};return window.addEventListener(s,d.notify),window.cookieStore?.addEventListener?.("change",p),()=>{d.stop(),window.removeEventListener(s,d.notify),window.cookieStore?.removeEventListener?.("change",p)}}}}function mn(e={}){let t=e.name??L("consent"),n=()=>{let r=D(t);if(r==null)return null;let o=_e(r);return o.analytics==null?null:o};return{name:"forwarded",read:n,get observesChanges(){return un()},subscribe(r){if(!Y())return()=>{};let o=Te(n,r),i=s=>{[...s.changed??[],...s.deleted??[]].some(u=>u?.name===t)&&o.notify()};return window.cookieStore?.addEventListener?.("change",i),()=>{o.stop(),window.cookieStore?.removeEventListener?.("change",i)}}}}function hn(e){let t=pe(e);return{name:"static",basis:Ne.host,read:()=>t?{...t}:null,observesChanges:!0,subscribe:()=>()=>{}}}function gn(e){if(e==null)return null;let t=String(e).split("/").map(n=>n.trim()).filter(Boolean).join("/");return t===""?null:t}function no(e){let t=e.item_category;if(t!=null&&String(t).includes("/"))return gn(t);let n=[t,e.item_category2,e.item_category3,e.item_category4,e.item_category5].map(r=>r==null?"":String(r).trim()).filter(Boolean);return gn(n.join("/"))}var ro=e=>{if(e==null||typeof e=="object")return null;let t=String(e).trim();return t===""?null:t};function oo(...e){for(let t of e){if(typeof t=="number"&&Number.isFinite(t))return String(t);if(typeof t=="string"&&t.trim()!=="")return t.trim()}return null}var Re=e=>oo(e.item_group_id,e.item_id);function so(e,t=Re){if(!e)return null;let n=a=>a==null||a===""||Number.isNaN(Number(a))?null:Number(a),r=a=>{let u=n(a);return u==null?null:Math.round(u*1e6)/1e6},o=a=>{let u=r(a);return u!=null&&u>=0?u:null},i=a=>{let u=n(a);return Number.isInteger(u)&&u>=0?u:null},s=ro;return{product_id:s(t(e)),sku:s(e.item_id),name:s(e.item_name),brand:s(e.item_brand),variant:s(e.item_variant)??s(e.variant),category:no(e),price:r(e.price),discount:o(e.discount),quantity:i(e.quantity),coupon:s(e.coupon),position:i(e.index)!=null?i(e.index)+1:null,url:s(e.url)??s(e.item_url)??null,image_url:s(e.image_url)??s(e.image_link)??null,displayed_sku:s(e.displayed_sku)??s(e.displayed_variant_sku)}}function pt(e,t=Re){return(e??[]).map(n=>so(n,t)).filter(n=>n?.product_id)}var io=1e3,ao={shipping:2,payment:3},co=new Set(["password","google","apple","facebook","magic_link","sso","passkey","other"]),$=e=>{if(e==null||typeof e=="object")return null;let t=String(e).trim();return t===""?null:t},uo=e=>{if(e==null||e===""||typeof e=="object")return null;let t=Number(e);return Number.isFinite(t)&&t>=0?t:null},Me=e=>e!==null&&typeof e=="object";function lo(e){return Array.isArray(e.items)?e.items.filter(Me):null}function yn(e){let t=$(e)?.toLowerCase().replace(/[\s-]+/g,"_");return t?co.has(t)?t:t==="email"||t==="email_password"?"password":"other":null}function fo({ecommerce:e,items:t}){let n=i=>({promotion_id:$(i.promotion_id),name:$(i.promotion_name),creative:$(i.creative_name),position:$(i.creative_slot)}),r=new Set,o=[];for(let i of[n(e),...t.map(n)])!i.promotion_id||r.has(i.promotion_id)||(r.add(i.promotion_id),o.push(i));return o}var Sn=({ecommerce:e,items:t})=>$(e.item_list_id)??$(e.item_list_name)??$(t[0]?.item_list_id)??$(t[0]?.item_list_name),ce=(e,t)=>t.products?.length?{event:e,properties:t}:null,po=new Set(["product","products","item","items"]);function mo({ecommerce:e}){let t=$(e.item_id),n=$(e.content_type)?.toLowerCase();return!t||n&&!po.has(n)?null:{event:"Product Shared",properties:{share_via:$(e.method),products:[{product_id:t}]}}}function ho(e){let t=o=>ce("Product List Viewed",{list_id:Sn(o),products:o.products}),n=o=>{let i=$(o.ecommerce.search_term);return i?{event:"Products Searched",properties:{query:i,...o.products.length?{products:o.products}:{}}}:null},r=o=>i=>i.promotions.map(s=>({event:o,properties:s}));return{view_item:o=>ce("Product Viewed",{currency:o.currency,value:o.value,products:o.products.slice(0,1)}),view_item_list:t,view_search_results:o=>n(o)??t(o),search:n,select_item:o=>ce("Product Clicked",{list_id:Sn(o),products:o.products.slice(0,1)}),add_to_cart:o=>ce("Product Added",{cart_id:$(o.ecommerce.cart_id),products:o.products}),remove_from_cart:o=>ce("Product Removed",{cart_id:$(o.ecommerce.cart_id),products:o.products}),view_cart:o=>({event:"Cart Viewed",properties:{cart_id:$(o.ecommerce.cart_id),value:o.value,currency:o.currency,coupon:$(o.ecommerce.coupon),...o.itemsListed?{products:o.products}:{}}}),begin_checkout:o=>ce("Checkout Started",{value:o.value,currency:o.currency,coupon:$(o.ecommerce.coupon),products:o.products}),add_shipping_info:o=>({event:"Checkout Step Completed",properties:{step:e.shipping,step_name:"shipping",shipping_method:$(o.ecommerce.shipping_tier)}}),add_payment_info:o=>({event:"Payment Info Entered",properties:{step:e.payment,step_name:"payment",payment_method:$(o.ecommerce.payment_type)}}),view_promotion:r("Promotion Viewed"),select_promotion:r("Promotion Clicked"),add_to_wishlist:o=>ce("Product Added to Wishlist",{products:o.products}),share:mo,login:o=>({event:"Signed In",properties:{method:yn(o.ecommerce.method)}}),sign_up:o=>({event:"Signed Up",properties:{method:yn(o.ecommerce.method)}})}}var go=e=>e.event;function yo(e,t){if(!Me(e))return null;if(typeof e.length=="number"&&e[0]==="event"&&typeof e[1]=="string"){let r=Me(e[2])?e[2]:{};return{name:e[1],entry:r,ecommerce:r}}let n=$(t(e));return n?{name:n,entry:e,ecommerce:Me(e.ecommerce)?e.ecommerce:e}:null}function mt(e){return Array.isArray(e)?`[${e.map(mt).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${mt(e[t])}`).join(",")}}`:JSON.stringify(e??null)}function wn(e,t={}){if(!e||typeof e.track!="function")throw new Error("connectDataLayer: analytics must have a track(event, properties, options) method");let n=t.dataLayer??(typeof window<"u"?window.dataLayer=window.dataLayer||[]:null);if(!Array.isArray(n))return()=>{};let r=ho({...ao,...t.steps}),o=t.map??{},i=t.eventName??go,s=t.productId??Re,a=new Set(["purchase","refund",...t.ignore??[]]),u=t.dedupeMs??io,d=new Map,p=!0;function f(w,g,_){if(u<=0)return!1;let P=`${w}|${_??""}|${mt(g)}`,M=Date.now();for(let[H,K]of d)M-K>u&&d.delete(H);return d.has(P)?!0:(d.set(P,M),!1)}let y=null,x=new Set;function S(w,g){if(w!=="Product Viewed")return!1;let _=typeof location>"u"?"":location.pathname;_!==y&&(y=_,x.clear());let P=g.products?.[0]?.product_id;return P?x.has(P)?!0:(x.add(P),!1):!1}function b({event:w,properties:g={}},_){if(!w||f(w,g,_)||S(w,g))return;let P=_?{context:{currency:_}}:void 0;if(w==="Signed Out"&&typeof e.signedOut=="function"){e.signedOut(P);return}e.track(w,g,P)}function I(w){let g=t.transform?t.transform(w):w,_=yo(g,i);if(!_||a.has(_.name))return;let P=Object.hasOwn(o,_.name),M=P?o[_.name]:r[_.name];if(typeof M!="function")return;let H=lo(_.ecommerce),K=H??[],j={..._,items:K,itemsListed:H!==null,products:pt(K,s),currency:ot(_.ecommerce.currency),value:uo(_.ecommerce.value)};j.promotions=fo(j);let te=P?M(g,{...j,toProducts:G=>pt(G,s)}):M(j);for(let G of[te].flat())G&&b(G,j.currency)}function k(w){if(p)try{I(w)}catch(g){try{t.onError?.(g,w)}catch{}}}for(let w of n.slice())k(w);let h=n.push;function v(...w){let g=h.apply(this,w);for(let _ of w)k(_);return g}return n.push=v,()=>{p=!1,n.push===v&&(n.push=h)}}var So="hp_debug",wo=256;function _o(){try{return new URLSearchParams(location.search).get(So)}catch{return null}}function _n(e,t){if(typeof location>"u")return!1;try{let n=_o();if(n!==null){let o=n.trim();o===""||o.length>wo?sessionStorage.removeItem(t):sessionStorage.setItem(t,o)}let r=sessionStorage.getItem(t);return!!(e&&r&&Bt(r)===e)}catch{return!1}}var me=class extends Error{},bo={inHoldout:()=>!0,personalisationGroup:()=>"excluded",servingHeaders:()=>({}),recommendationContext:()=>({}),flush:()=>Promise.resolve(),reset:()=>{}};function ht(e,t){let n=bo[e]??(()=>null),r=o=>(console.warn(`[hp] ${String(e)} failed; nothing was sent`,o),n());return function(...i){try{let s=t.apply(this,i);return typeof s?.then=="function"?Promise.resolve(s).catch(r):s}catch(s){if(s instanceof me)throw s;return r(s)}}}function bn(e){for(let t of Reflect.ownKeys(e)){let{value:n}=Object.getOwnPropertyDescriptor(e,t)??{};typeof n=="function"&&(e[t]=ht(t,n))}return e}var be=Symbol("hp.identityOption");var xo=4320*60*60,Io=100,Ao=1e3,vo=2e3,xn=500,De=e=>(e?.unref?.(),e);function Eo(e,t){if(t?.analytics===!0&&e.analytics===!1)return"withdrawn";let n=ae.map(r=>e[r]).filter(r=>r!==null);return n.length&&n.every(Boolean)?"accept_all":n.length&&n.every(r=>!r)?"reject_all":"custom"}function ko(e,t,n=L("consent")){if(!(typeof document>"u"))try{let r=typeof location<"u"&&location.protocol==="https:"?"; Secure":"",o=t?`; Domain=${t}`:"";document.cookie=`${n}=${lt(e)}; Path=/; Max-Age=${xo}; SameSite=Lax`+o+r}catch{}}function Co(e){let t=_e(D(e));return ae.some(n=>t[n]!==null)?t:null}function In(e){let{adapter:t,timeoutMs:n=5e3,cookieDomain:r=null,cookieName:o=L("consent"),release:i,announce:s}=e,a=e.discard??(()=>{}),u="pending",d=null,p=null,f=null,y=null,x=!1,S=null,b=[],I=m=>{u=m,e.onStatus?.(m)};function k(){try{return pe(t.read())}catch{return null}}function h(){p&&clearInterval(p),f&&clearTimeout(f),p=null,f=null}let v=()=>typeof window<"u"&&typeof document<"u",w=()=>{try{return typeof t.subscribe=="function"&&t.observesChanges===!0}catch{return!1}};function g(){y&&clearInterval(y),y=null}function _(){x||y||d===null||w()||v()&&document.visibilityState==="hidden"||(y=De(setInterval(()=>j(k()),vo)))}let P=()=>u==="pending"||u==="unknown",M=e.store??null,H=()=>{let m=b.splice(0);M?.write([]);for(let B of m)i(B)},K=()=>{let m=b.splice(0);M?.write([]);for(let B of m)a(B)};function j(m){let B=pe(m);if(!B||B.analytics===null)return;let Ae=d===null,J=Ae?Co(o):d;if(!Ae&&ut(J,B))return;h(),d=B,_(),ko(B,r,o);let ve=!ut(J,B);if(B.analytics){I("granted"),H(),ve&&s({action:Eo(B,J),state:B,previous:J,withdrawal:!1});return}K(),J?.analytics===!0&&s({action:"withdrawn",state:B,previous:J,withdrawal:!0}),I("denied")}function te(){f=null,u==="pending"&&(I("unknown"),p&&clearInterval(p),p=De(setInterval(()=>j(k()),Ao)))}function G(m){P()&&(M||m?.type!=="visibilitychange"&&!m?.persisted&&K())}let ne=m=>{document.visibilityState==="hidden"?(g(),G(m)):_()},le=m=>{g(),G(m)},Ie=()=>_();return{get status(){return u},get state(){return d?{...d}:null},stamp(){return d?{...d,source:t.name??null,basis:t.basis??Ne.visitor}:null},admits(){return u==="granted"},hold(m){return!P()||b.length>=xn?(a(m),!1):(b.push(m),M?.write(b),!0)},onHidden:G,start(){x=!1;try{S=t.subscribe?.(m=>j(m))??null}catch(m){e.debug&&console.warn("[hp] consent adapter subscribe failed",m)}v()&&(window.addEventListener("pagehide",le),window.addEventListener("pageshow",Ie),document.addEventListener("visibilitychange",ne)),M&&b.length===0&&b.push(...M.read().slice(-xn)),j(k()),!d&&(p=De(setInterval(()=>j(k()),Io)),f=De(setTimeout(te,n)))},stop(){x=!0,h(),g(),typeof S=="function"&&S(),v()&&(window.removeEventListener("pagehide",le),window.removeEventListener("pageshow",Ie),document.removeEventListener("visibilitychange",ne))}}}var Po=["recommendation","search_rank","banner","offer","layout","copy","price","other"],$e=e=>typeof e=="string"&&e!=="";function An(e){return(Array.isArray(e?.decisions)?e.decisions:[]).filter(t=>$e(t?.slot)&&$e(t.decision_id)&&t.holdout!==!0).map(({decision_id:t,slot:n,arm:r,kind:o})=>({decision_id:t,slot:n,...$e(r)&&{arm:r},...Po.includes(o)&&{kind:o}}))}function vn({key:e,mayStore:t}){let n,r=()=>{if(n)return n;try{let i=JSON.parse(sessionStorage.getItem(e));n=Array.isArray(i)?i.filter(s=>$e(s?.slot)):[]}catch{n=[]}return n},o=()=>{try{n?.length&&t()&&sessionStorage.setItem(e,JSON.stringify(n))}catch{}};return{persist:o,current:()=>r().length?n.map(i=>({...i})):null,remember(i){if(!i.length)return;let s=new Set(i.map(a=>a.slot));n=[...r().filter(a=>!s.has(a.slot)),...i],o()},forget(){n=[];try{sessionStorage.removeItem(e)}catch{}}}}function Oo(e){let t=2166136261,n=String(e);for(let r=0;r<n.length;r+=1)t^=n.charCodeAt(r),t=t+((t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24))>>>0;return t^=t>>>16,t=Math.imul(t,2246822507)>>>0,t^=t>>>13,t=Math.imul(t,3266489909)>>>0,t^=t>>>16,t>>>0}function Ue(e,t,n=1e4){return Oo(`${t}\0${e}`)%n}function gt(e){let t=o=>Math.min(Math.max(Number(o)||0,0),1),n=t(e.share),r=1-n;return{holdout_share:n,personalised_share:e.ramp==null?r:Math.min(t(e.ramp),r)}}function yt(e,t){if(!e||typeof e.salt!="string")return{holdout:!1,bucket:null,group:"personalised"};let n=e.ramp!=null;if(t==null||t==="")return{holdout:!1,bucket:null,group:n?"excluded":"personalised"};let{holdout_share:r,personalised_share:o}=gt(e),i=Ue(t,e.salt),s=u=>Math.round(u*1e4),a=r>0&&i<s(r)?"holdout":!n||i<s(r+o)?"personalised":"excluded";return{holdout:a==="holdout",bucket:i,group:a}}function En(e,t){return typeof e!="string"||e===""||t==null||t===""?null:Ue(t,`bandit-vs-default:${e}`)<1e4/2?"default":"bandit"}var Lo="hp_preview",No=256;function kn(e,t=null){if(typeof location>"u")return D(e);let n=null;try{n=new URLSearchParams(location.search).get(Lo)}catch{n=null}if(n===null)return D(e);let r=n.trim();return r===""||r.length>No?(Q(e,"",0,t),null):(Q(e,r,null,t),r)}var To=40,Ro=128,Mo=5,Do=60,Pn=e=>typeof e=="string"&&e!==""&&e.length<=Ro&&!e.includes(","),Cn=e=>Array.isArray(e)?e.filter(Pn).slice(0,To):[],$o=e=>Array.isArray(e)?e.filter(t=>typeof t=="string").map(t=>t.replaceAll(","," ").trim()).filter(t=>t!=="").slice(0,Mo):[],Uo=e=>Object.entries(e&&typeof e=="object"?e:{}).filter(([t,n])=>Pn(t)&&Number.isFinite(n)&&n>=1).sort(([,t],[,n])=>n-t).slice(0,Do).map(([t,n])=>`${t}:${Math.floor(n)}`);function St(e){let t={},n={viewed:Cn(e?.recentProducts),basket:Cn(e?.basketProducts),q:$o(e?.recentSearches),impressed:Uo(e?.impressions)};for(let[r,o]of Object.entries(n))o.length>0&&(t[r]=o.join(","));return t}var Vo=1800*1e3;function On(e,t=Date.now()){try{localStorage.setItem(L("recommendationContext"),JSON.stringify({context:e,until:t+Vo}))}catch{}}function Ln(){try{localStorage.removeItem(L("recommendationContext"))}catch{}}var Bo="hp_sample",Nn=1e4;function Tn(e,t){return!(typeof t=="number"&&t<1)||e==null||e===""?!0:Ue(e,Bo,Nn)<Math.round(t*Nn)}function wt(e){return e.trim().toLowerCase().replace(/\s*\/\s*/g,"/").replace(/^\/+|\/+$/g,"")}var jo=2,qo=12;function Fo(e,t=jo){return e.split("/").slice(0,t).join("/")}function he(e,t){if(typeof t!="string"||!t.trim())return null;let n=Fo(wt(t));return n?(e.unshift(n),e.length=Math.min(e.length,qo),n):null}var ue=()=>L("sessionSignals"),ge=null,_t=()=>ge??ue();function Rn(){try{return ge?localStorage:sessionStorage}catch{return}}function Mn(e,t){let n=t==null?null:String(t),r=e?F(ue(),n,e):null;if(r===ge)return;let o=ge===null?Ho():null;if(ge=r,!!ge)try{let i=_t(),s=nn(ue(),n);for(let a of Object.keys(localStorage))a.startsWith(s)&&a!==i&&localStorage.removeItem(a);o&&!localStorage.getItem(i)&&(localStorage.setItem(i,JSON.stringify(o)),sessionStorage.removeItem(ue()))}catch{}}function Ho(){try{let e=sessionStorage.getItem(ue());if(!e)return null;let t=JSON.parse(e);return t&&(t.productViews>0||t.listViews>0||t.basketValue!=null||t.recentProducts?.length||t.recentBrowse?.length)?t:null}catch{return null}}function Ve(){return{productViews:0,listViews:0,cartAdds:0,searches:0,recentProducts:[],recentCategories:[],recentBrowse:[],basketProducts:[],basketValue:null,recentSearches:[],impressions:{},browsingCategory:null,startedAt:0}}function bt(){let e=Rn();if(!e)return Ve();try{let t=e.getItem(_t());return t?{...Ve(),...JSON.parse(t)}:{...Ve(),startedAt:Date.now()}}catch{return{...Ve(),startedAt:Date.now()}}}function xt(e){let t=Rn();if(t)try{t.setItem(_t(),JSON.stringify(e))}catch{}}function Dn(){return bt()}function $n(){for(let e of[()=>sessionStorage,()=>localStorage])try{let t=e();t.removeItem(ue());for(let n of Object.keys(t))n.startsWith(`${ue()}:`)&&t.removeItem(n)}catch{}}var Un=20,Vn=200,Ko=new Set(["Product Viewed","Product Clicked","Product Added","Product Added to Wishlist"]),Go=new Set(["Product Viewed","Product Added"]);function Bn(e,t){let n=bt();n.startedAt||(n.startedAt=Date.now());let r=Array.isArray(t?.products)?t.products:[];switch(e){case"Product Viewed":n.productViews+=1,he(n.recentBrowse,n.browsingCategory);break;case"Product List Viewed":{let s=t?.list_id;typeof s=="string"&&s.includes("/")&&!s.startsWith("cdp-")&&(n.listViews+=1,n.browsingCategory=he(n.recentBrowse,s)??n.browsingCategory)}break;case"Product List Filtered":{let s=t?.change,a=typeof t?.list_id=="string"?t.list_id:"",u=typeof s?.value=="string"?s.value:"";u&&s?.action!=="removed"&&(n.browsingCategory=he(n.recentBrowse,`${a}/${u}`)??n.browsingCategory);break}case"Product Added":{n.cartAdds+=1,he(n.recentBrowse,n.browsingCategory);for(let s of r){let a=s.product_id;typeof a!="string"||!a||(n.basketProducts=[a,...n.basketProducts.filter(u=>u!==a)].slice(0,Un))}break}case"Product Removed":{for(let s of r){let a=s.product_id;typeof a=="string"&&a&&(n.basketProducts=n.basketProducts.filter(u=>u!==a))}break}case"Products Searched":{n.searches+=1;let s=t?.query;if(typeof s=="string"&&s.trim()){let a=s.trim().toLowerCase().slice(0,64);n.recentSearches=[a,...n.recentSearches.filter(u=>u!==a)].slice(0,5)}break}case"Product Clicked":case"Product Added to Wishlist":break;case"Cart Updated":{let s=t?.value;return n.basketValue=typeof s=="number"&&Number.isFinite(s)&&s>=0?s:null,xt(n),n}default:return n}let o=Ko.has(e);if(o)for(let s of r)typeof s.product_id=="string"&&s.product_id&&delete n.impressions[s.product_id];else{for(let a of r){let u=a.product_id;typeof u!="string"||!u||(n.impressions[u]=(n.impressions[u]??0)+1)}let s=Object.keys(n.impressions);if(s.length>Vn){let a={};for(let u of s.sort((d,p)=>n.impressions[p]-n.impressions[d]).slice(0,Vn))a[u]=n.impressions[u];n.impressions=a}}let i=new Set;for(let s of r){let a=s.product_id;if(o&&typeof a=="string"&&a&&(n.recentProducts=[a,...n.recentProducts.filter(u=>u!==a)].slice(0,Un)),Go.has(e)&&typeof s.category=="string"&&s.category){let u=wt(s.category);u&&!i.has(u)&&(i.add(u),he(n.recentCategories,u))}}return xt(n),n}var jn=1800*1e3;function qn({name:e,domain:t=null,mayStore:n,now:r=()=>Date.now(),mint:o=Z}){let i=null,s=0,a=()=>{i!==null&&n()&&Q(e,i,jn/1e3,t)};return{get(){let u=r(),d=D(e);return d?i=d:(i===null||u-s>=jn)&&(i=o()),s=u,a(),i},persist:a,forget(){Q(e,"",0,t),i=null}}}var W=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Fn=e=>Array.isArray(e)&&e.every(t=>typeof t=="string"),At=Object.freeze({enabled:!1});function Hn(e,t){let n=e?.track;return W(n)?(Object.hasOwn(n,t)?n[t]:n.__default)?.enabled!==!1:!0}function zo(e,t){return`${ye(e)}/v1/projects/${encodeURIComponent(t)}/settings`}var Wo="https://hp.thgingenuity.com";function ye(e){return e==null||e===""?Wo:Kn(e)}function Kn(e){if(typeof e!="string")return null;let t=e.trim().replace(/\/+$/,"");return/^https?:\/\/[^/\s]+$/i.test(t)?t:/^[a-z0-9.-]+(:\d+)?$/i.test(t)?`https://${t}`:null}function Jo(e){if(!W(e))return null;let t=Object.fromEntries(Object.entries(e).filter(([,n])=>typeof n=="boolean"));return Object.keys(t).length?t:null}var It=e=>typeof e=="number"&&e>=0&&e<=1,Yo=/^[0-9a-f]{64}$/;function Xo(e){return W(e)?{enabled:e.enabled===!0,tokenSha256:typeof e.tokenSha256=="string"&&Yo.test(e.tokenSha256)?e.tokenSha256:null}:{enabled:!1,tokenSha256:null}}function Zo(e){if(!W(e))return null;let t=Object.fromEntries(Object.entries(e).filter(([,n])=>W(n)&&It(n.share)&&(n.ramp==null||It(n.ramp))&&typeof n.salt=="string"&&n.salt!=="").map(([n,r])=>[n,{share:r.share,ramp:r.ramp??null,salt:r.salt,...Fn(r.readoutSlots)&&r.readoutSlots.length>0&&{readoutSlots:r.readoutSlots}}]));return Object.keys(t).length?t:null}var Qo=/^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,128}$/;function es(e){return W(e)?e.source==="hp_cookie"?{source:"hp_cookie"}:e.source!=="site_cookie"?null:e.cookie==null?{source:"site_cookie",cookie:null}:typeof e.cookie=="string"&&Qo.test(e.cookie)?{source:"site_cookie",cookie:e.cookie}:null:null}function vt(e){let t=W(e)?e.hp:null;return!W(t)||t.enabled!==!0?At:{enabled:!0,propertyId:typeof t.propertyId=="string"&&t.propertyId!==""?t.propertyId:null,environment:typeof t.environment=="string"?t.environment:null,consentPurposes:Jo(t.consent?.purposes),queryParamAllowlist:Fn(t.queryParamAllowlist)?t.queryParamAllowlist:null,anonymousId:es(t.anonymousId),apiHost:Kn(t.apiHost),holdouts:Zo(t.holdouts),sampleRate:It(t.sampleRate)?t.sampleRate:1,debug:Xo(t.debug),plan:W(e.plan)&&W(e.plan.track)?{track:e.plan.track}:null}}async function Gn(e,t,n=globalThis.fetch){try{let r=await n(zo(e,t),{credentials:"omit",headers:{accept:"application/json"}});return r.ok?vt(await r.json()):null}catch{return null}}var Be=e=>F(L("settings"),e);function zn(e){try{let t=localStorage.getItem(Be(e));if(!t)return null;let n=JSON.parse(t);return n?.enabled===!0?n:null}catch{return null}}function Wn(e,t){try{t?.enabled===!0?localStorage.setItem(Be(e),JSON.stringify(t)):localStorage.removeItem(Be(e))}catch{}}function Et(e){try{localStorage.removeItem(Be(e))}catch{}}var ts={flushAt:20,flushIntervalMs:1e4,maxQueue:500,maxRetryAgeMs:1440*60*1e3,maxRetries:5,maxPersistedBatches:20,maxPersistedBytes:1024*1024,storageKey:ie.retry,beaconSafeBytes:56*1024,maxBatchEvents:100,maxBatchBytes:500*1024},je=()=>Date.now(),Jn=3e4,Tt=55*1024;function ns(e,t=je()){if(typeof e!="string")return 0;let n=e.trim();if(/^\d+$/.test(n))return Number(n)*1e3;if(n==="")return 0;let r=Date.parse(n);return Number.isNaN(r)?0:Math.max(0,r-t)}var ee=e=>typeof TextEncoder<"u"?new TextEncoder().encode(e).length:Buffer.byteLength(e),kt=()=>typeof window<"u"&&typeof document<"u";function rs(){try{return!kt()||!window.localStorage?null:(window.localStorage.getItem("thg_hp_probe"),window.localStorage)}catch{return null}}var A,Ct,Yn,qe,Pt,Ot,Xn,xe,Lt,Fe,Zn,Nt,Qn,He,Ke=class{constructor(t){Ut(this,A);this.o={...ts,...t},this.queue=[],this.timer=null,this.storage=rs(),this.inflight=null,this.sending=new Set,this.replayAllowed=!this.o.deferReplay,this.draining=null,kt()&&(window.addEventListener("pagehide",()=>this.flush({beacon:!0})),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),N(this,A,He).call(this))}replayPersisted(){return this.replayAllowed=!0,N(this,A,He).call(this)}clearPersisted(){if(this.replayAllowed=!1,!!this.storage)try{this.storage.removeItem(this.o.storageKey)}catch{}}enqueue(t){let n;try{n=JSON.parse(JSON.stringify(t))}catch(r){this.o.onError?.(`event ${t?.messageId??""} cannot be serialised; dropped`,r);return}this.queue.push(n),this.queue.length>this.o.maxQueue&&this.queue.splice(0,this.queue.length-this.o.maxQueue),this.queue.length>=this.o.flushAt?this.flush():N(this,A,Ct).call(this)}flush(t={}){if(N(this,A,Yn).call(this),this.queue.length===0)return Promise.resolve();if(t.beacon&&kt()&&typeof navigator<"u"&&navigator.sendBeacon)return N(this,A,Xn).call(this),Promise.resolve();if(this.inflight)return this.inflight;let n=N(this,A,Ot).call(this,this.queue,this.o.maxBatchEvents,this.o.maxBatchBytes).map(r=>{let o=N(this,A,Nt).call(this,r);return o&&this.sending.add(o),{batch:r,id:o}});return this.queue=[],this.inflight=(async()=>{for(let{batch:r,id:o}of n)await N(this,A,xe).call(this,r,0,o)})().finally(()=>{this.inflight=null,this.queue.length>=this.o.flushAt?this.flush():this.queue.length&&N(this,A,Ct).call(this)}),this.inflight}};A=new WeakSet,Ct=function(){this.timer||(this.timer=setTimeout(()=>this.flush(),this.o.flushIntervalMs),this.timer?.unref?.())},Yn=function(){this.timer&&(clearTimeout(this.timer),this.timer=null)},qe=function(t){return JSON.stringify({batch:t,writeKey:this.o.writeKey,sentAt:new Date().toISOString()})},Pt=function(){return`${this.o.apiHost.replace(/\/$/,"")}/v1/batch`},Ot=function(t,n,r){let o=ee(N(this,A,qe).call(this,[])),i=[],s=[],a=o;for(let u of t){let d=ee(JSON.stringify(u))+1;s.length&&(s.length>=n||a+d>r)&&(i.push(s),s=[],a=o),s.push(u),a+=d}return s.length&&i.push(s),i},Xn=function(){let t=this.queue;this.queue=[];let n=N(this,A,Pt).call(this);for(let r of N(this,A,Ot).call(this,t,this.o.maxBatchEvents,this.o.beaconSafeBytes)){let o=N(this,A,qe).call(this,r);if(ee(o)<=this.o.beaconSafeBytes){let i=!1;try{i=navigator.sendBeacon(n,new Blob([o],{type:"text/plain"}))}catch{i=!1}if(i)continue}N(this,A,xe).call(this,r,0,N(this,A,Nt).call(this,r))}},xe=async function(t,n,r=null){r&&this.sending.add(r);let o=N(this,A,qe).call(this,t),i=0;try{let s=await fetch(N(this,A,Pt).call(this),{method:"POST",headers:{"Content-Type":"text/plain"},body:o,keepalive:ee(o)<=this.o.beaconSafeBytes});if(s.ok)return N(this,A,Lt).call(this,r),N(this,A,He).call(this),!0;let a=s.status===429||s.status>=500;if(a&&(i=ns(s.headers?.get?.("retry-after"))),!a)return N(this,A,Lt).call(this,r),this.o.onError?.(`CDP ingest rejected batch (${s.status}); dropping ${t.length} events`),!0;throw new Error(`retryable ${s.status}`)}catch(s){if(n>=this.o.maxRetries){r&&this.sending.delete(r);let u=r?"kept for the next page":"dropped";return this.o.onError?.(`CDP ingest unreachable after ${n} attempts; ${u}: ${t.length} events`,s),!1}let a=Math.max(Math.min(1e3*2**n,Jn)*(.5+Math.random()),Math.min(i,Jn));return await new Promise(u=>setTimeout(u,a)),N(this,A,xe).call(this,t,n+1,r)}},Lt=function(t){t&&(this.sending.delete(t),N(this,A,Qn).call(this,t))},Fe=function(){let t=JSON.parse(this.storage.getItem(this.o.storageKey)||"[]");return Array.isArray(t)?t:[]},Zn=function(t,n){let r=s=>ee(JSON.stringify(s))+1,o=[...t,n].slice(-this.o.maxPersistedBatches),i=o.reduce((s,a)=>s+r(a),2);for(;o.length>1&&i>this.o.maxPersistedBytes;)i-=r(o[0]),o=o.slice(1);for(;o.length;)try{return this.storage.setItem(this.o.storageKey,JSON.stringify(o)),!0}catch{o=o.slice(1)}return!1},Nt=function(t){if(!this.storage||this.o.mayPersist&&!this.o.mayPersist())return null;try{let n={at:je(),id:`${je()}-${Math.random().toString(36).slice(2)}`,batch:t};return ee(JSON.stringify(n))+3>this.o.maxPersistedBytes?null:N(this,A,Zn).call(this,N(this,A,Fe).call(this),n)?n.id:null}catch{return null}},Qn=function(t){if(!(!t||!this.storage))try{let n=N(this,A,Fe).call(this).filter(r=>r.id!==t);n.length?this.storage.setItem(this.o.storageKey,JSON.stringify(n)):this.storage.removeItem(this.o.storageKey)}catch{}},He=function(){if(!this.storage||!this.replayAllowed)return this.draining??Promise.resolve();if(this.draining)return this.draining;let t;try{let r=N(this,A,Fe).call(this);t=r.filter(o=>Array.isArray(o?.batch)&&je()-o.at<this.o.maxRetryAgeMs).map(o=>o.id?o:{...o,id:`${o.at}-${Math.random().toString(36).slice(2)}`}),(t.length!==r.length||t.some((o,i)=>o!==r[i]))&&(t.length?this.storage.setItem(this.o.storageKey,JSON.stringify(t)):this.storage.removeItem(this.o.storageKey))}catch{return Promise.resolve()}let n=t.filter(r=>!this.sending.has(r.id));return n.length?(this.draining=(async()=>{for(let r of n)if(!this.replayAllowed||!await N(this,A,xe).call(this,r.batch,0,r.id))return})().finally(()=>{this.draining=null}),this.draining):Promise.resolve()};var er="3.19.0",tr="@thg-agentic/hyper-personalisation-web";var os=new Set(["audiences","decisions","recommendations"]);var ss=Symbol.for("@thg-agentic/hyper-personalisation-web.replay");function is(e){let t={...e??{}},n=Array.isArray(t.products)?t.products:null;if(t.item_count==null&&n){let r=n.map(o=>o?.quantity);r.every(Number.isInteger)&&(t.item_count=r.reduce((o,i)=>o+i,0))}return t}var as="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";function cs(e){if(/^[0-9a-fA-F]{64}$/.test(e))return e.toLowerCase();if(!/^[A-Za-z0-9+/_-]{43}=?$/.test(e))return null;try{let t=atob(e.replace(/-/g,"+").replace(/_/g,"/").padEnd(44,"="));return t.length!==32?null:Array.from(t,n=>n.charCodeAt(0).toString(16).padStart(2,"0")).join("")}catch{return null}}function Ge(e){if(typeof e!="string")return null;let t=cs(e.trim());return t===as?null:t}var nr=!1,rr=!1;function us(){rr||(rr=!0,console.warn("[hp] no consent adapter: events are sent under the host's lawful basis (basis 'host'). Pass `consent` to send only once the visitor allows analytics."))}function ls(e){!e||nr||(nr=!0,console.warn("[hp] mintAnonymousId is ignored since 3.6: where device ids come from is set per source in the console (the site\u2019s own cookie, or Hyper-Personalisation\u2019s)."))}function ds(e){try{return ee(JSON.stringify(e))}catch{return 0}}function or(e){return e&&typeof e.context=="object"&&e.context!==null?e.context:e}function sr(e){let t=ye(e?.apiHost);if(!t)throw new Error("createAnalytics: apiHost, when given, must be a host or an http(s) origin");if(!e?.propertyId)throw new Error("createAnalytics: propertyId is required");let n={libraryName:tr,libraryVersion:er,writeKey:"web",queryParamBlocklist:[],debug:!1,validate:null,consentTimeoutMs:5e3,...e,apiHost:t};Pe(n.storageNames),Oe();let r=!!n.consent;r||us();let o=null;n.getConsent=()=>o?o.stamp():an();let i=()=>!r||o?.status==="granted",s=r&&n.holdAcrossPages?rn(F(L("held"),n.propertyId)):null,a=s?.read()??{items:[],anonymousId:null};n.mintAnonymousId!==void 0&&ls(n.debug);let u=on(n,{carried:a.anonymousId,mayStore:i,orOwn:!0}),d=qn({name:L("session"),domain:n.consentCookieDomain??null,mayStore:i}),p=n.getSessionId;n.getSessionId=()=>q(R(p,null))??d.get();let f=kn(L("preview"),n.consentCookieDomain??null),y=(c,l)=>n.debug&&console.warn("[hp]",c,l??""),x=n.transport??new Ke({apiHost:n.apiHost,writeKey:n.writeKey,storageKey:F(L("retry"),n.propertyId),mayPersist:r?()=>o?.status==="granted":void 0,deferReplay:r,onError:y}),S=F(L("previousPage"),n.propertyId),b=null,I=()=>{if(b)return b;try{return sessionStorage.getItem(S)||null}catch{return null}},k=c=>{if(b=c||null,!(r&&o.status!=="granted"))try{sessionStorage.setItem(S,c)}catch{}},h=vn({key:F(L("decisions"),n.propertyId),mayStore:i});n.getDecisions??(n.getDecisions=h.current);let v=q(R(n.getUserId,null)),w=null,g=null,_=null;function P(c,l,C=!1){try{return M(c,l,C)}catch(O){if(O instanceof me)throw O;return console.warn("[hp] the event was not sent",O),null}}function M(c,l,C){let O=_;_=null;let E={messageId:O?.messageId??Z(),timestamp:O?.timestamp??new Date().toISOString(),anonymousId:q(R(n.getAnonymousId,null)),userId:v??null,...c,context:Zt(n,or(l))};if(E.properties&&(E.properties=nt(E.properties)),!C&&!Tn(E.anonymousId,n.sampleRate))return n.debug&&console.debug("[hp] not sent: outside the sample",E),null;if(n.beforeSend){let de;try{de=n.beforeSend(E)}catch(yr){return console.warn("[hp] beforeSend failed; the event was not sent",yr),null}if(!de)return null;let{type:Ee,event:Dt,messageId:hr,timestamp:gr}=E;E={...de,type:Ee,messageId:hr,timestamp:gr,...Dt?{event:Dt}:{}}}if(n.validate){let de=n.validate(E,{producer:"web"});if(!de.valid){let Ee=`[hp] invalid ${E.event??E.type}:
|
|
2
|
+
${de.errors.join(`
|
|
3
|
+
`)}`;if(n.throwOnInvalid)throw new me(Ee);console.error(Ee)}}n.debug&&console.debug("[hp]",E.event??E.type,E);let U=ds(E);return U>Tt?(y(`${E.event??E.type} not sent: ${U} bytes, over the ${Tt}-byte limit for one event`),null):(Ie(E,C),E)}function H(c){if(c.type==="track"&&G(c),n.onEvent)try{n.onEvent(c)}catch(l){console.warn("[hp] onEvent observer failed",l)}}let K=()=>r&&o?.state?.personalisation===!0,j=()=>Mn(R(n.getSessionId,null),n.propertyId),te=!1;function G(c){try{K()?(j(),On(St(Bn(c.event,c.properties))),te=!0):ne()}catch(l){n.debug&&console.warn("[hp] the visit was not recorded",l)}}function ne(c=!1){Ln(),(c||te)&&$n(),te=!1}function le(){d.forget(),h.forget(),B.clear(),u?.forget(),ne(!0)}function Ie(c,l){!r||l||o.admits()?(H(c),x.enqueue(c)):o.hold(c)}let m=(c,l,C)=>Hn(n.plan,c)?P({type:"track",event:c,properties:l??{}},C):null,B=new Set,Ae=(c,l,C)=>c==="audiences"?{audiences:l?null:C.audiences??[]}:c==="decisions"?{decisions:(C.decisions??[]).map(O=>({slot:O.slot,decision_id:O.decision_id??null,arm:O.arm??null,...O.variant&&{variant:O.variant}}))}:{recommendation_request_id:C.recommendation_request_id??null};r&&(o=In({adapter:n.consent,timeoutMs:n.consentTimeoutMs,cookieDomain:n.consentCookieDomain,debug:n.debug,store:s&&{read:()=>a.items,write:c=>s.write(c,u?.peek()??null)},release(c){c.context&&(c.context.consent=o.stamp()),H(c),x.enqueue(c)},onStatus(c){if(c==="granted"&&(u?.persist(),d.persist(),b&&k(b),h.persist(),x.replayPersisted?.()),c==="denied"){x.clearPersisted?.(),u?.forget(),d.forget();try{sessionStorage.removeItem(S)}catch{}h.forget(),ne(!0)}try{n.onConsentStatus?.(c)}catch(l){console.warn("[hp] onConsentStatus observer failed",l)}},announce({action:c,state:l,previous:C,withdrawal:O}){l?.personalisation!==!0&&ne(C?.personalisation===!0),P({type:"track",event:"Consent Updated",properties:{action:c,state:l}},void 0,O),O&&x.flush()}}),o.start());let J=()=>n.holdout?gt(n.holdout):null,ve=()=>yt(n.holdout,q(R(n.getAnonymousId,null))).group;function mr(c,l){if(!Array.isArray(c?.decisions))return c;let C=n.holdout?.readoutSlots??[],O=q(R(n.getAnonymousId,null));return{...c,decisions:c.decisions.map(E=>{let U=E?.variant==="bandit"||E?.variant==="default"?E.variant:!l&&E?.holdout!==!0&&C.includes(E?.slot)?En(E.slot,O):null;return U?U==="default"?{...E,variant:U,decision_id:null}:{...E,variant:U}:E})}}function Mt(c,l){if(f)return null;let C=c?.exposure,O=C?.holdout===!0,E=mr(c,O);return h.remember(An(E)),!C?.token||B.has(C.token)?null:(B.add(C.token),m("Personalisation Exposed",{exposure_token:C.token,surface:C.surface,holdout:O,holdout_share:C.holdout_share??J()?.holdout_share??null,personalised_share:C.personalised_share??J()?.personalised_share??null,...Ae(C.surface,O,E)},l))}return bn({track:m,page(c,l){c??(c={});let C=n.pageType??null;c.type&&(fe.has(c.type)?(C=c.type,n.pageType=c.type):n.debug&&console.warn(`[hp] ignoring unknown page.type "${c.type}". Allowed: ${[...fe].join(", ")}`));let O=P({type:"page",properties:{name:c.name??null,navigation_trigger:c.navigation_trigger??zt(),previous_message_id:I()}},{page:{type:C},...or(l)??{}});return O&&k(O.messageId),O},identify(c,l,C){let{userId:O,...E}=c&&typeof c=="object"?c:{};if(O!==void 0){let U=q(O);C===be?g=U:w&&U&&U!==w?le():n.debug&&g&&U&&U!==g&&console.warn("[hp] identify names another userId than the identity option; using the new one"),v=U,U&&C!==be&&(w=U)}if(typeof E.email_sha256=="string"){let U=Ge(E.email_sha256);U?E.email_sha256=U:delete E.email_sha256}return P({type:"identify",traits:E},l)},reset(){v&&le(),v=null,w=null,g=null,k("")},productViewed:(c,l)=>m("Product Viewed",c,l),productListViewed:(c,l)=>m("Product List Viewed",c,l),recommendationsViewed:(c,l)=>m("Recommendations Viewed",c,l),personalisationExposed:(c,l)=>m("Personalisation Exposed",c,l),recordExposure:Mt,surfaceViewed({surface:c}={},l){if(!os.has(c)||!n.holdout||c!=="decisions"&&!K())return null;let C=q(R(n.getAnonymousId,null)),O=yt(n.holdout,C).group;if(O==="excluded")return null;let E=Ht({sessionId:ke(R(n.getSessionId,null),C),surface:c});return E?Mt({exposure:{token:E,surface:c,holdout:O==="holdout"}},l):null},inHoldout:()=>ve()!=="personalised",personalisationGroup:ve,productClicked:(c,l)=>m("Product Clicked",c,l),productListFiltered:(c,l)=>m("Product List Filtered",c,l),productsSearched:(c,l)=>m("Products Searched",c,l),searchSuggestionsViewed:(c,l)=>m("Search Suggestions Viewed",c,l),productAdded:(c,l)=>m("Product Added",c,l),productRemoved:(c,l)=>m("Product Removed",c,l),cartUpdated:(c,l)=>m("Cart Updated",is(c),l),identityLinked:(c,l)=>m("Identity Linked",c,l),slotEngaged:(c,l)=>f?null:m("Slot Engaged",c,l),productVariantSelected:(c,l)=>m("Product Variant Selected",c,l),cartViewed:(c,l)=>m("Cart Viewed",c,l),productAddedToWishlist:(c,l)=>m("Product Added to Wishlist",c,l),productRemovedFromWishlist:(c,l)=>m("Product Removed from Wishlist",c,l),wishlistProductAddedToCart:(c,l)=>m("Wishlist Product Added to Cart",c,l),productShared:(c,l)=>m("Product Shared",c,l),cartShared:(c,l)=>m("Cart Shared",c,l),couponEntered:(c,l)=>m("Coupon Entered",c,l),couponApplied:(c,l)=>m("Coupon Applied",c,l),couponDenied:(c,l)=>m("Coupon Denied",c,l),couponRemoved:(c,l)=>m("Coupon Removed",c,l),checkoutStarted:(c,l)=>m("Checkout Started",c,l),promotionViewed:(c,l)=>m("Promotion Viewed",c,l),promotionClicked:(c,l)=>m("Promotion Clicked",c,l),checkoutStepViewed:(c,l)=>m("Checkout Step Viewed",c,l),checkoutStepCompleted:(c,l)=>m("Checkout Step Completed",c,l),paymentInfoEntered:(c,l)=>m("Payment Info Entered",c,l),checkoutFailed:(c,l)=>m("Checkout Failed",c,l),signedIn:(c,l)=>m("Signed In",c,l),signedUp:(c,l)=>m("Signed Up",c,l),signedOut(c){let l=m("Signed Out",{},c);return v&&le(),v=null,w=null,g=null,l},newsletterSubscribed:(c,l)=>m("Newsletter Subscribed",c,l),newsletterUnsubscribed:(c,l)=>m("Newsletter Unsubscribed",c,l),backInStockSubscribed:(c,l)=>m("Back In Stock Subscribed",c,l),productReviewed:(c,l)=>m("Product Reviewed",c,l),consentUpdated:(c,l)=>m("Consent Updated",c,l),flush:c=>x.flush(c),recommendationContext(){if(!K())return{};try{return j(),St(Dn())}catch{return{}}},get consent(){return o?o.state:null},servingHeaders:()=>ln(o?.state??null),get lastPageMessageId(){return I()},get anonymousId(){return q(R(n.getAnonymousId,null))},[ss](c,l,C){if(typeof this[c]=="function"){_=typeof C?.messageId=="string"&&typeof C?.timestamp=="string"?{messageId:C.messageId,timestamp:C.timestamp}:null;try{this[c](...Array.isArray(l)?l:[])}finally{_=null}}}})}var Rt=Symbol.for("@thg-agentic/hyper-personalisation-web.bootstrap"),fs=Symbol.for("@thg-agentic/hyper-personalisation-web.drain"),ps=Symbol.for("@thg-agentic/hyper-personalisation-web.carry"),ir=Symbol.for("@thg-agentic/hyper-personalisation-web.replay"),ms=Symbol.for("@thg-agentic/hyper-personalisation-web.assignment"),hs=Symbol.for("@thg-agentic/hyper-personalisation-web.recommendation-context"),Je=e=>e?()=>D(e):()=>null,ur={didomi:e=>dn({purposes:e.purposes}),onetrust:e=>fn({groups:e.groups,impliedConsent:e.impliedConsent}),cookie:e=>pn({name:e.name,categories:e.categories,refusedValue:e.refusedValue,source:e.source,changeEvent:e.changeEvent}),forwarded:e=>mn({name:e.name}),static:e=>{if(!e.state||typeof e.state!="object")throw new Error('consent.state is required for provider "static"');return hn(e.state)}},ar=Object.keys(ur),Se=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),ze=e=>typeof e=="string"&&e!=="",gs=e=>Array.isArray(e)&&e.every(t=>typeof t=="string"),lr=e=>Se(e)&&typeof e.read=="function";function ys(e,t={}){let n=[];if(!Se(e))return["config must be { writeKey, apiHost? }"];ze(e.writeKey)||n.push("writeKey is required"),ye(e.apiHost)||n.push("apiHost, when given, must be a host or an http(s) origin"),t.propertyId!==void 0&&!ze(t.propertyId)&&n.push("propertyId must be a string");let{consent:r}=t;if(!lr(r)&&!(Se(r)&&ar.includes(r.provider))&&n.push(`consent must be a consent adapter or { provider: ${ar.join(" | ")} }`),t.autocapture!==void 0&&t.autocapture!==!1&&!Se(t.autocapture)&&n.push("autocapture must be false or { promotions, productLists, recommendations }"),t.beforeSend!==void 0&&typeof t.beforeSend!="function"&&n.push("beforeSend must be a function"),t.identityCookies!==void 0)if(!Se(t.identityCookies))n.push("identityCookies must be an object");else for(let o of["anonymousId","sessionId"]){let i=t.identityCookies[o];i!==void 0&&!ze(i)&&n.push(`identityCookies.${o} must be a cookie name`)}for(let o of["getCurrency","getShipToCountry","getLocale"])t[o]!==void 0&&typeof t[o]!="function"&&n.push(`${o} must be a function`);return t.dataLayer!==void 0&&(Se(t.dataLayer)?(t.dataLayer.name!==void 0&&!ze(t.dataLayer.name)&&n.push("dataLayer.name must be a string"),t.dataLayer.ignore!==void 0&&!gs(t.dataLayer.ignore)&&n.push("dataLayer.ignore must be an array of strings")):n.push("dataLayer must be an object")),n}function Ss(e,t,n){if(lr(e))return dt(e,t.consentPurposes);let{provider:r,...o}=e,i=n?.consent?.[r]??ur[r];return dt(i(o),t.consentPurposes)}var dr=(e,t)=>`${e??""}|${t??""}`;function ws(e,t){let n=q(t?.userId),r=Ge(t?.emailSha256);if(!n&&!r)return null;try{let o=F(L("identified"),e);if(sessionStorage.getItem(o)===dr(n,r))return null}catch{}return{...n?{userId:n}:{},...r?{email_sha256:r}:{}}}async function _s({writeKey:e,apiHost:t},n){if(n.settings!==void 0)return{settings:vt(n.settings),revalidated:null};let r=Gn(t,e),o=zn(e);if(o)return{settings:o,revalidated:r};let i=await r,s=i??At;return{settings:s,revalidated:Promise.resolve(s),unreachable:i===null}}function fr(e,t){let n=t??{},r=typeof window<"u"?window:null;if(r?.[Rt])return r[Rt];let o=sn(e?.writeKey,()=>{let{analytics:s}=_e(D(L("consent")));return s===!0||s===null&&n.holdAcrossPages===!0}),i=xs(e,n,o);if(r){let s=n.extend?.getSessionId??Je(n.identityCookies?.sessionId),a=Je(n.identityCookies?.anonymousId);r[ms]=({slot:u,arm:d}={})=>Ft({sessionId:ke(s(),a()),slot:u,arm:d}),r[Rt]=i,r[ps]=o,r[fs]?.(i)}return i}function bs(){let e=globalThis.performance?.now?.();return new Date(Date.now()-(Number.isFinite(e)?e:0)).toISOString()}function cr(e,t){if(t===!1)return()=>{};let n={promotions:!0,productLists:!0,recommendations:!0,exposures:!0,...t},r=i=>i&&typeof i=="object"?i:{},o=[];try{n.promotions&&o.push(Qe(e,r(n.promotions))),n.productLists&&o.push(Ze(e,r(n.productLists))),n.recommendations&&o.push(et(e,r(n.recommendations))),n.exposures&&o.push(Xe(e,r(n.exposures)))}catch(i){console.warn("[hp] autocapture not started:",i?.message??i)}return()=>{for(let i of o)i()}}function We(e){return e.close(),null}async function xs(e,t,n){try{let r=ys(e,t);if(r.length)return console.warn(`[hp] not started, the configuration is invalid: ${r.join("; ")}`),We(n);Pe(t.storageNames),Oe();let{writeKey:o}=e,[{settings:i,revalidated:s,unreachable:a},u]=await Promise.all([_s(e,t),Promise.resolve(t.identity).catch(()=>null)]),d=s?i:null,p=!1,f=()=>p&&d&&Wn(o,d);if(s?.then(g=>{g&&(d=g,g.enabled?f():Et(o))}),a)return null;if(!i.enabled)return t.debug&&console.info("[hp] off: the settings for this write key do not enable it"),We(n);let y=i.propertyId??t.propertyId;if(!y)return console.warn("[hp] not started: this write key covers several properties, so propertyId is required"),We(n);let{preset:x}=t,S=ws(y,u),b=t.debug===!0||i.debug?.enabled===!0||_n(i.debug?.tokenSha256??null,L("debug")),I=q(u?.userId),k=sr({apiHost:i.apiHost??ye(e.apiHost),writeKey:o,propertyId:y,pageType:fe.has(t.pageType)?t.pageType:void 0,environment:i.environment,queryParamAllowlist:i.queryParamAllowlist??void 0,plan:i.plan,sampleRate:i.sampleRate??1,holdout:i.holdouts?.[y],getCurrency:t.getCurrency,getShipToCountry:t.getShipToCountry,getLocale:t.getLocale,consent:Ss(t.consent,i,x),consentCookieDomain:t.consentCookieDomain,storageNames:t.storageNames,holdAcrossPages:t.holdAcrossPages===!0,mintAnonymousId:t.mintAnonymousId,debug:b,anonymousIdSource:i.anonymousId??void 0,getAnonymousId:Je(t.identityCookies?.anonymousId),getSessionId:t.identityCookies?.sessionId?Je(t.identityCookies.sessionId):void 0,getUserId:()=>I,beforeSend:t.beforeSend,...t.extend,onConsentStatus(g){if(g==="granted"&&(p=!0,f()),g==="denied"){Et(o);try{sessionStorage.removeItem(F(L("identified"),y))}catch{}}t.extend?.onConsentStatus?.(g)},onEvent(g){if(g.type==="identify"){let _=dr(q(g.userId),Ge(g.traits?.email_sha256));try{sessionStorage.setItem(F(L("identified"),y),_)}catch{}}t.extend?.onEvent?.(g)}});typeof window<"u"&&(window[hs]=()=>k.recommendationContext());for(let[g,_,P]of n.take())try{k[ir](g,_,P)}catch(M){console.warn(`[hp] carried ${g} failed`,M)}k[ir]("page",[t.pageType?{type:t.pageType}:{}],{messageId:Z(),timestamp:bs()});let h=cr(k,t.autocapture),v=k.page;k.page=ht("page",(...g)=>{let _=v(...g);return h(),h=cr(k,t.autocapture),_});let w=typeof window<"u"?window:null;if(t.dataLayer&&w){let g=t.dataLayer.name??"dataLayer";w[g]=w[g]||[],wn(k,{...x,dataLayer:w[g],ignore:[...x?.ignore??[],...t.dataLayer.ignore??[]],onError:_=>b&&console.warn("[hp] dataLayer mapping failed",_)})}return S&&k.identify(S,void 0,be),k}catch(r){return console.warn("[hp] not started:",r?.message??r),We(n)}}function pr(e){e.initialized=!0,fr(e._config,e._options).then(t=>{let n=e.splice(0);if(!t){for(let r of e.methods??[])e[r]=()=>e;return}e.client=t;for(let[r,o]of Object.entries(t))typeof o=="function"&&(e[r]=o);for(let[r,...o]of n)try{t[r]?.(...o)}catch(i){console.warn(`[hp] queued ${r} failed`,i)}})}var V=window.hp=window.hp||[];V.servingHeaders??(V.servingHeaders=()=>({}));V.inHoldout??(V.inHoldout=()=>!0);V.personalisationGroup??(V.personalisationGroup=()=>"excluded");V.recommendationContext??(V.recommendationContext=()=>({}));V.initialized||(V._config?pr(V):V.load=(e,t)=>{V._config=e,V._options=t??{},pr(V)});})();
|
package/dist/snippet.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{(()=>{let e=window.hp=window.hp||[];if(!(e.initialized||e.invoked)){e.invoked=!0,e.methods=["track","page","identify","reset","flush","productViewed","productListViewed","recommendationsViewed","personalisationExposed","recordExposure","surfaceViewed","productClicked","productListFiltered","productsSearched","productAdded","productRemoved","cartUpdated","identityLinked","slotEngaged","productVariantSelected","cartViewed","productAddedToWishlist","productRemovedFromWishlist","wishlistProductAddedToCart","productShared","cartShared","couponEntered","couponApplied","couponDenied","couponRemoved","checkoutStarted","promotionViewed","promotionClicked","checkoutStepViewed","checkoutStepCompleted","paymentInfoEntered","checkoutFailed","signedIn","signedUp","signedOut","newsletterSubscribed","newsletterUnsubscribed","backInStockSubscribed","productReviewed","consentUpdated"],e.factory=t=>(...o)=>(e.push([t,...o]),e);for(let t of e.methods)e[t]=e.factory(t);e.servingHeaders=()=>({}),e.inHoldout=()=>!0,e.personalisationGroup=()=>"excluded",e.recommendationContext=()=>({}),e.load=(t,o,r)=>{e._config=t,e._options=o||{};let d=document.createElement("script");d.async=!0,d.src=r;let i=document.getElementsByTagName("script")[0];i.parentNode.insertBefore(d,i)},e.SNIPPET_VERSION="1",e.load({writeKey:"hp_pk_live_REPLACE_ME",apiHost:"https://api.example.com"},{consent:{provider:"onetrust"},identityCookies:{sessionId:"session_id"}},"https://cdn.jsdelivr.net/npm/@thg-agentic/hyper-personalisation-web@3/dist/hp.min.js")}})();})();
|
|
1
|
+
(()=>{(()=>{let e=window.hp=window.hp||[];if(!(e.initialized||e.invoked)){e.invoked=!0,e.methods=["track","page","identify","reset","flush","productViewed","productListViewed","recommendationsViewed","personalisationExposed","recordExposure","surfaceViewed","productClicked","productListFiltered","productsSearched","searchSuggestionsViewed","productAdded","productRemoved","cartUpdated","identityLinked","slotEngaged","productVariantSelected","cartViewed","productAddedToWishlist","productRemovedFromWishlist","wishlistProductAddedToCart","productShared","cartShared","couponEntered","couponApplied","couponDenied","couponRemoved","checkoutStarted","promotionViewed","promotionClicked","checkoutStepViewed","checkoutStepCompleted","paymentInfoEntered","checkoutFailed","signedIn","signedUp","signedOut","newsletterSubscribed","newsletterUnsubscribed","backInStockSubscribed","productReviewed","consentUpdated"],e.factory=t=>(...o)=>(e.push([t,...o]),e);for(let t of e.methods)e[t]=e.factory(t);e.servingHeaders=()=>({}),e.inHoldout=()=>!0,e.personalisationGroup=()=>"excluded",e.recommendationContext=()=>({}),e.load=(t,o,r)=>{e._config=t,e._options=o||{};let d=document.createElement("script");d.async=!0,d.src=r;let i=document.getElementsByTagName("script")[0];i.parentNode.insertBefore(d,i)},e.SNIPPET_VERSION="1",e.load({writeKey:"hp_pk_live_REPLACE_ME",apiHost:"https://api.example.com"},{consent:{provider:"onetrust"},identityCookies:{sessionId:"session_id"}},"https://cdn.jsdelivr.net/npm/@thg-agentic/hyper-personalisation-web@3/dist/hp.min.js")}})();})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thg-agentic/hyper-personalisation-web",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.19.0",
|
|
4
4
|
"description": "Browser SDK for the THG Hyper-Personalisation event specification: event tracking from a write key and remote settings, strict CMP-agnostic consent, a GA4 dataLayer bridge and in-session audience evaluation. Dependency-free ESM on the Segment wire protocol, a CDN script (dist/hp.min.js), and an optional @segment/analytics-next plugin path.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"esbuild": "^0.28.2",
|
|
75
75
|
"jsdom": "^30.1.1",
|
|
76
76
|
"typescript": "^7.0.2",
|
|
77
|
-
"@thg-agentic/hyper-personalisation-spec": "1.
|
|
77
|
+
"@thg-agentic/hyper-personalisation-spec": "1.12.0"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"test": "node --test tests/*.test.mjs",
|
package/src/bootstrap.js
CHANGED
|
@@ -41,6 +41,8 @@ import {
|
|
|
41
41
|
import { getCookie } from './context.js';
|
|
42
42
|
import { connectDataLayer } from './datalayer.js';
|
|
43
43
|
import { sessionDebug } from './debug.js';
|
|
44
|
+
import { guard } from './guard.js';
|
|
45
|
+
import { IDENTITY_OPTION } from './identity-option.js';
|
|
44
46
|
import { createAnalytics, normaliseSha256, PAGE_TYPES } from './index.js';
|
|
45
47
|
import {
|
|
46
48
|
cachedSettings,
|
|
@@ -229,7 +231,8 @@ async function resolveSettings({ writeKey, apiHost }, options) {
|
|
|
229
231
|
* @param {import('../types/bootstrap').BootstrapOptions} options See types/bootstrap.d.ts.
|
|
230
232
|
* @returns {Promise<object|null>} the client, or null when it is off.
|
|
231
233
|
*/
|
|
232
|
-
export function bootstrap(config,
|
|
234
|
+
export function bootstrap(config, maybeOptions) {
|
|
235
|
+
const options = maybeOptions ?? {};
|
|
233
236
|
const host = typeof window !== 'undefined' ? window : null;
|
|
234
237
|
if (host?.[INSTANCE]) return host[INSTANCE];
|
|
235
238
|
// What `hp` queued on an earlier page that navigated before its client started. Carried only as
|
|
@@ -472,12 +475,12 @@ async function start(config, options, carry) {
|
|
|
472
475
|
let stopAutocapture = startAutocapture(client, options.autocapture);
|
|
473
476
|
// Impressions count once per page view, so a single-page app's next route reports afresh.
|
|
474
477
|
const page = client.page;
|
|
475
|
-
client.page = (...args) => {
|
|
478
|
+
client.page = guard('page', (...args) => {
|
|
476
479
|
const event = page(...args);
|
|
477
480
|
stopAutocapture();
|
|
478
481
|
stopAutocapture = startAutocapture(client, options.autocapture);
|
|
479
482
|
return event;
|
|
480
|
-
};
|
|
483
|
+
});
|
|
481
484
|
|
|
482
485
|
const host = typeof window !== 'undefined' ? window : null;
|
|
483
486
|
if (options.dataLayer && host) {
|
|
@@ -495,7 +498,7 @@ async function start(config, options, carry) {
|
|
|
495
498
|
});
|
|
496
499
|
}
|
|
497
500
|
|
|
498
|
-
if (identified) client.identify(identified);
|
|
501
|
+
if (identified) client.identify(identified, undefined, IDENTITY_OPTION);
|
|
499
502
|
return client;
|
|
500
503
|
} catch (err) {
|
|
501
504
|
console.warn('[hp] not started:', err?.message ?? err);
|
package/src/consent-gate.js
CHANGED
|
@@ -92,7 +92,7 @@ function previousFromCookie(name) {
|
|
|
92
92
|
* @param {string} [options.cookieName] Defaults to storageName('consent'), `thg_hp_consent`.
|
|
93
93
|
* @param {(item: any) => void} options.release A held item may now be sent.
|
|
94
94
|
* @param {(item: any) => void} [options.discard] A held item will never be sent.
|
|
95
|
-
* @param {(update: { action: string, state: object, withdrawal: boolean }) => void} options.announce
|
|
95
|
+
* @param {(update: { action: string, state: object, previous: object|null, withdrawal: boolean }) => void} options.announce
|
|
96
96
|
* Record a Consent Updated. Called only when analytics is granted, or for the withdrawal itself.
|
|
97
97
|
* @param {(status: string) => void} [options.onStatus]
|
|
98
98
|
* @param {boolean} [options.debug]
|
|
@@ -201,6 +201,7 @@ export function createConsentGate(options) {
|
|
|
201
201
|
announce({
|
|
202
202
|
action: consentAction(next, previous),
|
|
203
203
|
state: next,
|
|
204
|
+
previous,
|
|
204
205
|
withdrawal: false,
|
|
205
206
|
});
|
|
206
207
|
return;
|
|
@@ -210,7 +211,12 @@ export function createConsentGate(options) {
|
|
|
210
211
|
// A withdrawal is recorded, then nothing more: the raw table is where a refusal is proven to
|
|
211
212
|
// have been received, and the warehouse's consent filter keeps it out of every model.
|
|
212
213
|
if (previous?.analytics === true)
|
|
213
|
-
announce({
|
|
214
|
+
announce({
|
|
215
|
+
action: 'withdrawn',
|
|
216
|
+
state: next,
|
|
217
|
+
previous,
|
|
218
|
+
withdrawal: true,
|
|
219
|
+
});
|
|
214
220
|
setStatus('denied');
|
|
215
221
|
}
|
|
216
222
|
|
package/src/device-storage.js
CHANGED
|
@@ -154,8 +154,9 @@ export function createDeviceCookie({
|
|
|
154
154
|
/** Consent was granted: write what was minted before it. */
|
|
155
155
|
persist,
|
|
156
156
|
/**
|
|
157
|
-
* Consent was refused or withdrawn: the id must not outlive the
|
|
158
|
-
* so
|
|
157
|
+
* Consent was refused or withdrawn, or the user signed out: the id must not outlive the
|
|
158
|
+
* permission or the person it was kept under, so the next event starts a new device, as
|
|
159
|
+
* Segment's `analytics.reset()` does.
|
|
159
160
|
*/
|
|
160
161
|
forget() {
|
|
161
162
|
writeCookie(name, '', 0, domain);
|
package/src/guard.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracking never throws into the host. Every public method runs behind `guard`: a failure is logged
|
|
3
|
+
* and the method answers what a client that could not decide answers. That fails closed, as the CDN
|
|
4
|
+
* stub does before the client starts: no personalisation, no consent headers, nothing sent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** `throwOnInvalid`: the one failure a caller asked to have thrown at them. */
|
|
8
|
+
export class InvalidEventError extends Error {}
|
|
9
|
+
|
|
10
|
+
const FALLBACKS = {
|
|
11
|
+
inHoldout: () => true,
|
|
12
|
+
personalisationGroup: () => 'excluded',
|
|
13
|
+
servingHeaders: () => ({}),
|
|
14
|
+
recommendationContext: () => ({}),
|
|
15
|
+
flush: () => Promise.resolve(),
|
|
16
|
+
reset: () => undefined,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** `method` behind the guard, answering `name`'s fallback when it throws. */
|
|
20
|
+
export function guard(name, method) {
|
|
21
|
+
const fallback = FALLBACKS[name] ?? (() => null);
|
|
22
|
+
const failed = (err) => {
|
|
23
|
+
console.warn(`[hp] ${String(name)} failed; nothing was sent`, err);
|
|
24
|
+
return fallback();
|
|
25
|
+
};
|
|
26
|
+
return function guarded(...args) {
|
|
27
|
+
try {
|
|
28
|
+
const result = method.apply(this, args);
|
|
29
|
+
return typeof result?.then === 'function'
|
|
30
|
+
? Promise.resolve(result).catch(failed)
|
|
31
|
+
: result;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (err instanceof InvalidEventError) throw err;
|
|
34
|
+
return failed(err);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Every method of `client` behind the guard, in place. Getters are left as they are. */
|
|
40
|
+
export function guardMethods(client) {
|
|
41
|
+
for (const key of Reflect.ownKeys(client)) {
|
|
42
|
+
const { value } = Object.getOwnPropertyDescriptor(client, key) ?? {};
|
|
43
|
+
if (typeof value === 'function') client[key] = guard(key, value);
|
|
44
|
+
}
|
|
45
|
+
return client;
|
|
46
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks bootstrap's own identify, from its `identity` option, so it never counts as the earlier
|
|
3
|
+
* user a later host `identify` would be switching away from: the host may name the same person
|
|
4
|
+
* in another form.
|
|
5
|
+
*/
|
|
6
|
+
export const IDENTITY_OPTION = Symbol('hp.identityOption');
|
package/src/index.js
CHANGED
|
@@ -53,7 +53,9 @@ import {
|
|
|
53
53
|
holdoutAssignment,
|
|
54
54
|
holdoutShares,
|
|
55
55
|
} from './experiments.js';
|
|
56
|
+
import { guardMethods, InvalidEventError } from './guard.js';
|
|
56
57
|
import { emailSha256, sha256Hex } from './hash.js';
|
|
58
|
+
import { IDENTITY_OPTION } from './identity-option.js';
|
|
57
59
|
import { applyPreview } from './preview.js';
|
|
58
60
|
import {
|
|
59
61
|
forgetVisit,
|
|
@@ -82,7 +84,11 @@ import {
|
|
|
82
84
|
storageKey,
|
|
83
85
|
storageName,
|
|
84
86
|
} from './storage-scope.js';
|
|
85
|
-
import {
|
|
87
|
+
import {
|
|
88
|
+
MAX_EVENT_BYTES,
|
|
89
|
+
Transport,
|
|
90
|
+
byteLength as utf8Bytes,
|
|
91
|
+
} from './transport.js';
|
|
86
92
|
import { LIB, VERSION } from './version.js';
|
|
87
93
|
|
|
88
94
|
/** The surfaces a `Personalisation Exposed` names, as the public API's exposures do. */
|
|
@@ -104,9 +110,6 @@ export { emailSha256, getCookie };
|
|
|
104
110
|
/** How a call made before the SDK started is replayed with the messageId and time it was made at. */
|
|
105
111
|
const REPLAY = Symbol.for('@thg-agentic/hyper-personalisation-web.replay');
|
|
106
112
|
|
|
107
|
-
/** `throwOnInvalid`: the one failure a caller asked to have thrown at them. */
|
|
108
|
-
class InvalidEventError extends Error {}
|
|
109
|
-
|
|
110
113
|
/** SHA-256 of a string as lowercase hex. Synchronous since 3.9 (sha256Hex); kept async for callers. */
|
|
111
114
|
export async function sha256(value) {
|
|
112
115
|
return sha256Hex(String(value));
|
|
@@ -195,6 +198,19 @@ export { createSocialProof } from './social-proof.js';
|
|
|
195
198
|
export { VERSION };
|
|
196
199
|
|
|
197
200
|
let warnedMintAnonymousId = false;
|
|
201
|
+
let warnedHostBasis = false;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Without a consent adapter the SDK sends under the host's lawful basis: fail-open, so say it once.
|
|
205
|
+
* Requiring `consent` is a breaking change, planned for 4.0.
|
|
206
|
+
*/
|
|
207
|
+
function warnHostBasis() {
|
|
208
|
+
if (warnedHostBasis) return;
|
|
209
|
+
warnedHostBasis = true;
|
|
210
|
+
console.warn(
|
|
211
|
+
"[hp] no consent adapter: events are sent under the host's lawful basis (basis 'host'). Pass `consent` to send only once the visitor allows analytics.",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
198
214
|
|
|
199
215
|
/** 3.5's `mintAnonymousId` does nothing since 3.6: where device ids come from is the source's. */
|
|
200
216
|
function warnMintAnonymousId(debug) {
|
|
@@ -205,6 +221,15 @@ function warnMintAnonymousId(debug) {
|
|
|
205
221
|
);
|
|
206
222
|
}
|
|
207
223
|
|
|
224
|
+
/** UTF-8 bytes of the event as sent; 0 for one JSON cannot serialise, which delivery drops itself. */
|
|
225
|
+
function serialisedBytes(event) {
|
|
226
|
+
try {
|
|
227
|
+
return utf8Bytes(JSON.stringify(event));
|
|
228
|
+
} catch {
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
208
233
|
/** Per-call context overrides, given directly or analytics-next style as `{ context }`. */
|
|
209
234
|
function contextOverrides(options) {
|
|
210
235
|
if (
|
|
@@ -243,6 +268,7 @@ export function createAnalytics(options) {
|
|
|
243
268
|
// A consent adapter makes the SDK strict: nothing is sent or stored until analytics is granted.
|
|
244
269
|
// Without one the host has decided the lawful basis itself, and every event says so (spec 1.5).
|
|
245
270
|
const strict = Boolean(cfg.consent);
|
|
271
|
+
if (!strict) warnHostBasis();
|
|
246
272
|
let gate = null;
|
|
247
273
|
cfg.getConsent = () => (gate ? gate.stamp() : hostConsent());
|
|
248
274
|
const granted = () => !strict || gate?.status === 'granted';
|
|
@@ -276,6 +302,10 @@ export function createAnalytics(options) {
|
|
|
276
302
|
cfg.consentCookieDomain ?? null,
|
|
277
303
|
);
|
|
278
304
|
|
|
305
|
+
/** Delivery problems, logged with `debug`. */
|
|
306
|
+
const reportError = (message, err) =>
|
|
307
|
+
cfg.debug && console.warn('[hp]', message, err ?? '');
|
|
308
|
+
|
|
279
309
|
const transport =
|
|
280
310
|
cfg.transport ??
|
|
281
311
|
new Transport({
|
|
@@ -288,7 +318,7 @@ export function createAnalytics(options) {
|
|
|
288
318
|
// grant (see onStatus below, which replays on a grant and clears on a refusal).
|
|
289
319
|
mayPersist: strict ? () => gate?.status === 'granted' : undefined,
|
|
290
320
|
deferReplay: strict,
|
|
291
|
-
onError:
|
|
321
|
+
onError: reportError,
|
|
292
322
|
});
|
|
293
323
|
|
|
294
324
|
// The previous page view's messageId, carried across navigations in sessionStorage, per tenant.
|
|
@@ -323,6 +353,10 @@ export function createAnalytics(options) {
|
|
|
323
353
|
cfg.getDecisions ??= decisions.current;
|
|
324
354
|
|
|
325
355
|
let userId = realId(perRequest(cfg.getUserId, null));
|
|
356
|
+
/** The user the host's last `identify` named: a different one later on this page is a switch. */
|
|
357
|
+
let identifiedUserId = null;
|
|
358
|
+
/** The user bootstrap's `identity` option named, only to warn when the host names another form. */
|
|
359
|
+
let optionsUserId = null;
|
|
326
360
|
|
|
327
361
|
/** The call being replayed: its first event keeps the messageId and time it was made with. */
|
|
328
362
|
let replaying = null;
|
|
@@ -391,6 +425,16 @@ export function createAnalytics(options) {
|
|
|
391
425
|
|
|
392
426
|
if (cfg.debug) console.debug('[hp]', event.event ?? event.type, event);
|
|
393
427
|
|
|
428
|
+
// Too big to travel in a keepalive request on its own: it could never go at unload, and once
|
|
429
|
+
// persisted it would push every other batch out of the retry buffer.
|
|
430
|
+
const bytes = serialisedBytes(event);
|
|
431
|
+
if (bytes > MAX_EVENT_BYTES) {
|
|
432
|
+
reportError(
|
|
433
|
+
`${event.event ?? event.type} not sent: ${bytes} bytes, over the ${MAX_EVENT_BYTES}-byte limit for one event`,
|
|
434
|
+
);
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
394
438
|
deliver(event, force);
|
|
395
439
|
return event;
|
|
396
440
|
}
|
|
@@ -429,15 +473,36 @@ export function createAnalytics(options) {
|
|
|
429
473
|
visitKept = true;
|
|
430
474
|
} else {
|
|
431
475
|
// Not allowed, or withdrawn: what was kept under the permission goes with it.
|
|
432
|
-
|
|
433
|
-
if (visitKept) clearSessionSignals();
|
|
434
|
-
visitKept = false;
|
|
476
|
+
forgetKeptVisit();
|
|
435
477
|
}
|
|
436
478
|
} catch (err) {
|
|
437
479
|
if (cfg.debug) console.warn('[hp] the visit was not recorded', err);
|
|
438
480
|
}
|
|
439
481
|
}
|
|
440
482
|
|
|
483
|
+
/**
|
|
484
|
+
* What the visit built up for recommendations, kept only under personalisation consent. The
|
|
485
|
+
* session signals are cleared only when this SDK built them, unless `all`: a refusal or a sign-out.
|
|
486
|
+
*/
|
|
487
|
+
function forgetKeptVisit(all = false) {
|
|
488
|
+
forgetVisit();
|
|
489
|
+
if (all || visitKept) clearSessionSignals();
|
|
490
|
+
visitKept = false;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* A sign-out, or another user signing in: the SDK's session, the decisions in force, the visit
|
|
495
|
+
* and the SDK's own device id go with the user, as Segment's `reset()` starts a new anonymousId.
|
|
496
|
+
* A device id read from the site's own cookie stays: the SDK does not own it.
|
|
497
|
+
*/
|
|
498
|
+
function forgetVisitor() {
|
|
499
|
+
session.forget();
|
|
500
|
+
decisions.forget();
|
|
501
|
+
exposedTokens.clear();
|
|
502
|
+
device?.forget();
|
|
503
|
+
forgetKeptVisit(true);
|
|
504
|
+
}
|
|
505
|
+
|
|
441
506
|
function deliver(event, force) {
|
|
442
507
|
// The observer only sees what will be sent, never an event the visitor has not allowed.
|
|
443
508
|
if (!strict || force || gate.admits()) {
|
|
@@ -509,6 +574,7 @@ export function createAnalytics(options) {
|
|
|
509
574
|
/* nothing was stored */
|
|
510
575
|
}
|
|
511
576
|
decisions.forget();
|
|
577
|
+
forgetKeptVisit(true);
|
|
512
578
|
}
|
|
513
579
|
try {
|
|
514
580
|
cfg.onConsentStatus?.(status);
|
|
@@ -516,7 +582,12 @@ export function createAnalytics(options) {
|
|
|
516
582
|
console.warn('[hp] onConsentStatus observer failed', err);
|
|
517
583
|
}
|
|
518
584
|
},
|
|
519
|
-
announce({ action, state, withdrawal }) {
|
|
585
|
+
announce({ action, state, previous, withdrawal }) {
|
|
586
|
+
// Withdrawn here, not only when the next event reaches recordVisit: a beforeSend that drops
|
|
587
|
+
// it must not keep the visit. A withdrawal on a later page, before it recorded anything,
|
|
588
|
+
// forgets the signals an earlier page kept under the permission.
|
|
589
|
+
if (state?.personalisation !== true)
|
|
590
|
+
forgetKeptVisit(previous?.personalisation === true);
|
|
520
591
|
emit(
|
|
521
592
|
{
|
|
522
593
|
type: 'track',
|
|
@@ -611,11 +682,12 @@ export function createAnalytics(options) {
|
|
|
611
682
|
);
|
|
612
683
|
}
|
|
613
684
|
|
|
614
|
-
return {
|
|
685
|
+
return guardMethods({
|
|
615
686
|
/** Escape hatch for events not yet in the core spec. Prefer a named method. */
|
|
616
687
|
track,
|
|
617
688
|
|
|
618
|
-
page(props
|
|
689
|
+
page(props, ctx) {
|
|
690
|
+
props ??= {};
|
|
619
691
|
// page.type is inherited by every later event on the page, so an unknown value would get
|
|
620
692
|
// them all rejected. Keep the last good type instead: stale beats invalid.
|
|
621
693
|
let pageType = cfg.pageType ?? null;
|
|
@@ -647,10 +719,31 @@ export function createAnalytics(options) {
|
|
|
647
719
|
return ev;
|
|
648
720
|
},
|
|
649
721
|
|
|
650
|
-
identify(traits, ctx) {
|
|
722
|
+
identify(traits, ctx, source) {
|
|
651
723
|
const { userId: id, ...rest } =
|
|
652
724
|
traits && typeof traits === 'object' ? traits : {};
|
|
653
|
-
if (id !== undefined)
|
|
725
|
+
if (id !== undefined) {
|
|
726
|
+
const next = realId(id);
|
|
727
|
+
if (source === IDENTITY_OPTION) {
|
|
728
|
+
optionsUserId = next;
|
|
729
|
+
} else if (identifiedUserId && next && next !== identifiedUserId) {
|
|
730
|
+
// The host named another user after an earlier one on this page: nothing the last one
|
|
731
|
+
// built up carries over. A host cookie or bootstrap's identity option never counts as
|
|
732
|
+
// the earlier user: either may name the same person in another form.
|
|
733
|
+
forgetVisitor();
|
|
734
|
+
} else if (
|
|
735
|
+
cfg.debug &&
|
|
736
|
+
optionsUserId &&
|
|
737
|
+
next &&
|
|
738
|
+
next !== optionsUserId
|
|
739
|
+
) {
|
|
740
|
+
console.warn(
|
|
741
|
+
'[hp] identify names another userId than the identity option; using the new one',
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
userId = next;
|
|
745
|
+
if (next && source !== IDENTITY_OPTION) identifiedUserId = next;
|
|
746
|
+
}
|
|
654
747
|
// A digest in hex or base64 is sent as hex; anything that is not one is not sent.
|
|
655
748
|
if (typeof rest.email_sha256 === 'string') {
|
|
656
749
|
const email = normaliseSha256(rest.email_sha256);
|
|
@@ -660,9 +753,16 @@ export function createAnalytics(options) {
|
|
|
660
753
|
return emit({ type: 'identify', traits: rest }, ctx);
|
|
661
754
|
},
|
|
662
755
|
|
|
663
|
-
/**
|
|
756
|
+
/**
|
|
757
|
+
* Clear the signed-in user. At a real sign-out (a user was signed in), also forget what the visit
|
|
758
|
+
* built up and the SDK's own device id, so the next person on a shared device inherits none of
|
|
759
|
+
* it. A device id from the site's own cookie stays: the site owns it.
|
|
760
|
+
*/
|
|
664
761
|
reset() {
|
|
762
|
+
if (userId) forgetVisitor();
|
|
665
763
|
userId = null;
|
|
764
|
+
identifiedUserId = null;
|
|
765
|
+
optionsUserId = null;
|
|
666
766
|
writeLastPageMessageId('');
|
|
667
767
|
},
|
|
668
768
|
|
|
@@ -728,7 +828,14 @@ export function createAnalytics(options) {
|
|
|
728
828
|
personalisationGroup,
|
|
729
829
|
productClicked: (p, c) => track('Product Clicked', p, c),
|
|
730
830
|
productListFiltered: (p, c) => track('Product List Filtered', p, c),
|
|
831
|
+
/** A submitted search and the results page it produced: the query, its results and `pagination`. */
|
|
731
832
|
productsSearched: (p, c) => track('Products Searched', p, c),
|
|
833
|
+
/**
|
|
834
|
+
* The search box's suggested products for a partly typed query, once seen: never
|
|
835
|
+
* productsSearched, which is a submitted search's results page. A click on one carries this
|
|
836
|
+
* set's `search_request_id`.
|
|
837
|
+
*/
|
|
838
|
+
searchSuggestionsViewed: (p, c) => track('Search Suggestions Viewed', p, c),
|
|
732
839
|
productAdded: (p, c) => track('Product Added', p, c),
|
|
733
840
|
productRemoved: (p, c) => track('Product Removed', p, c),
|
|
734
841
|
/**
|
|
@@ -780,7 +887,10 @@ export function createAnalytics(options) {
|
|
|
780
887
|
/** Sent under the signed-in identity, which is then cleared for whoever uses the device next. */
|
|
781
888
|
signedOut(c) {
|
|
782
889
|
const event = track('Signed Out', {}, c);
|
|
890
|
+
if (userId) forgetVisitor();
|
|
783
891
|
userId = null;
|
|
892
|
+
identifiedUserId = null;
|
|
893
|
+
optionsUserId = null;
|
|
784
894
|
return event;
|
|
785
895
|
},
|
|
786
896
|
newsletterSubscribed: (p, c) => track('Newsletter Subscribed', p, c),
|
|
@@ -836,5 +946,5 @@ export function createAnalytics(options) {
|
|
|
836
946
|
replaying = null;
|
|
837
947
|
}
|
|
838
948
|
},
|
|
839
|
-
};
|
|
949
|
+
});
|
|
840
950
|
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* `new URLSearchParams(context)` is the query string to append, and an empty visit appends nothing.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { parseConsentCookie } from './consent.js';
|
|
11
|
+
import { getCookie } from './context.js';
|
|
10
12
|
import { storageName } from './storage-scope.js';
|
|
11
13
|
|
|
12
14
|
/** packages/cdp/src/recommendations/request.ts: MAX_IDS, MAX_ID_LENGTH, five queries, 60 pairs. */
|
|
@@ -86,9 +88,16 @@ export function forgetVisit() {
|
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* The visit an earlier page kept, or `{}` once it has lapsed, or when the consent cookie does not
|
|
93
|
+
* grant personalisation: a withdrawal on a page this SDK never ran on still stops it being read.
|
|
94
|
+
*/
|
|
90
95
|
export function keptVisit(now = Date.now()) {
|
|
91
96
|
try {
|
|
97
|
+
const { personalisation } = parseConsentCookie(
|
|
98
|
+
getCookie(storageName('consent')),
|
|
99
|
+
);
|
|
100
|
+
if (personalisation !== true) return {};
|
|
92
101
|
const kept = JSON.parse(
|
|
93
102
|
localStorage.getItem(storageName('recommendationContext')) ?? 'null',
|
|
94
103
|
);
|
package/src/session/record.js
CHANGED
|
@@ -13,11 +13,12 @@ const ENGAGEMENT_EVENTS = new Set([
|
|
|
13
13
|
'Product Added to Wishlist',
|
|
14
14
|
]);
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The browsing events the warehouse's category affinity counts (identifier_affinity_daily), so a
|
|
18
|
+
* session measure counts the same: a product shown in a listing, removed or found in search is not
|
|
19
|
+
* interest. The warehouse also counts orders, which a visit's own measure leaves out.
|
|
20
|
+
*/
|
|
21
|
+
const CATEGORY_EVENTS = new Set(['Product Viewed', 'Product Added']);
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Fold one CDP event into the session signals.
|
|
@@ -39,9 +40,8 @@ export function recordSessionEvent(eventName, properties) {
|
|
|
39
40
|
bump(s.recentBrowse, s.browsingCategory);
|
|
40
41
|
break;
|
|
41
42
|
case 'Product List Viewed':
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
// `cdp-` id), and counting it would let personalisation measure itself.
|
|
43
|
+
// Only a category list is browsing, or a category page viewed. A rail or carousel names its
|
|
44
|
+
// widget (no slash, or a `cdp-` id), and counting it would let personalisation measure itself.
|
|
45
45
|
{
|
|
46
46
|
const list = properties?.list_id;
|
|
47
47
|
const isCategoryPath =
|
|
@@ -49,6 +49,7 @@ export function recordSessionEvent(eventName, properties) {
|
|
|
49
49
|
list.includes('/') &&
|
|
50
50
|
!list.startsWith('cdp-');
|
|
51
51
|
if (isCategoryPath) {
|
|
52
|
+
s.listViews += 1;
|
|
52
53
|
s.browsingCategory = bump(s.recentBrowse, list) ?? s.browsingCategory;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -159,7 +160,7 @@ export function recordSessionEvent(eventName, properties) {
|
|
|
159
160
|
].slice(0, RECENT_LIMIT);
|
|
160
161
|
}
|
|
161
162
|
if (
|
|
162
|
-
|
|
163
|
+
CATEGORY_EVENTS.has(eventName) &&
|
|
163
164
|
typeof p.category === 'string' &&
|
|
164
165
|
p.category
|
|
165
166
|
) {
|
package/src/spec-version.js
CHANGED
package/src/transport.js
CHANGED
|
@@ -32,12 +32,30 @@ const DEFAULTS = {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
const now = () => Date.now();
|
|
35
|
+
const MAX_BACKOFF_MS = 30_000;
|
|
36
|
+
|
|
37
|
+
/** The largest event the SDK sends: with its batch envelope it still fits one keepalive request. */
|
|
38
|
+
export const MAX_EVENT_BYTES = 55 * 1024;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retry-After as milliseconds from now: delay-seconds or an HTTP date (RFC 9110 §10.2.3); 0 when
|
|
42
|
+
* absent or unreadable. Cross-origin, the browser exposes it only if the ingest lists it in
|
|
43
|
+
* Access-Control-Expose-Headers; a first-party proxy always can.
|
|
44
|
+
*/
|
|
45
|
+
export function retryAfterMs(value, at = now()) {
|
|
46
|
+
if (typeof value !== 'string') return 0;
|
|
47
|
+
const text = value.trim();
|
|
48
|
+
if (/^\d+$/.test(text)) return Number(text) * 1000;
|
|
49
|
+
if (text === '') return 0;
|
|
50
|
+
const date = Date.parse(text);
|
|
51
|
+
return Number.isNaN(date) ? 0 : Math.max(0, date - at);
|
|
52
|
+
}
|
|
35
53
|
|
|
36
54
|
/**
|
|
37
55
|
* UTF-8 bytes, the unit of sendBeacon's ceiling and the keepalive budget. String length counts
|
|
38
56
|
* UTF-16 code units and undercounts any non-ASCII catalogue.
|
|
39
57
|
*/
|
|
40
|
-
const byteLength = (s) =>
|
|
58
|
+
export const byteLength = (s) =>
|
|
41
59
|
typeof TextEncoder !== 'undefined'
|
|
42
60
|
? new TextEncoder().encode(s).length
|
|
43
61
|
: Buffer.byteLength(s);
|
|
@@ -282,6 +300,7 @@ export class Transport {
|
|
|
282
300
|
async #send(batch, attempt, persisted = null) {
|
|
283
301
|
if (persisted) this.sending.add(persisted);
|
|
284
302
|
const body = this.#body(batch);
|
|
303
|
+
let wait = 0;
|
|
285
304
|
try {
|
|
286
305
|
const res = await fetch(this.#url(), {
|
|
287
306
|
method: 'POST',
|
|
@@ -300,6 +319,7 @@ export class Transport {
|
|
|
300
319
|
// 4xx (except 429) means the payload is wrong; retrying cannot fix it and
|
|
301
320
|
// would loop forever. Drop and surface.
|
|
302
321
|
const retryable = res.status === 429 || res.status >= 500;
|
|
322
|
+
if (retryable) wait = retryAfterMs(res.headers?.get?.('retry-after'));
|
|
303
323
|
if (!retryable) {
|
|
304
324
|
this.#settle(persisted);
|
|
305
325
|
this.o.onError?.(
|
|
@@ -318,8 +338,11 @@ export class Transport {
|
|
|
318
338
|
);
|
|
319
339
|
return false;
|
|
320
340
|
}
|
|
321
|
-
|
|
322
|
-
|
|
341
|
+
// The ingest's Retry-After is a floor on the backoff, within the same cap.
|
|
342
|
+
const delay = Math.max(
|
|
343
|
+
Math.min(1000 * 2 ** attempt, MAX_BACKOFF_MS) * (0.5 + Math.random()),
|
|
344
|
+
Math.min(wait, MAX_BACKOFF_MS),
|
|
345
|
+
);
|
|
323
346
|
await new Promise((r) => setTimeout(r, delay));
|
|
324
347
|
return this.#send(batch, attempt + 1, persisted);
|
|
325
348
|
}
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '3.
|
|
1
|
+
export const VERSION = '3.19.0';
|
|
2
2
|
export const LIB = '@thg-agentic/hyper-personalisation-web';
|
package/types/consent.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
| 'ad_user_data'
|
|
6
|
-
| 'ad_personalization';
|
|
1
|
+
import type { CdpConsentPurpose } from './events';
|
|
2
|
+
|
|
3
|
+
/** The purposes a visitor answers for: the spec's, generated. */
|
|
4
|
+
export type ConsentPurpose = CdpConsentPurpose;
|
|
7
5
|
|
|
8
6
|
/** schemas/common/consent-state.json: true granted, false refused, null never asked. */
|
|
9
7
|
export type ConsentState = Record<ConsentPurpose, boolean | null>;
|
package/types/events.d.ts
CHANGED
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
// Generated from spec/registry.json by scripts/generate-sdk-models.mjs.
|
|
2
2
|
// DO NOT EDIT. Change the schema and regenerate — the schema is the contract,
|
|
3
3
|
// and a hand-edit here is an SDK that disagrees with the ingest.
|
|
4
|
-
// Spec version: 1.
|
|
4
|
+
// Spec version: 1.12
|
|
5
|
+
|
|
6
|
+
/** context.source.environment. Lets non-production traffic be filtered out of the warehouse without a separate writeKey. */
|
|
7
|
+
export type CdpSourceEnvironment = 'production' | 'staging' | 'development';
|
|
8
|
+
export declare const CDP_SOURCE_ENVIRONMENT_VALUES: readonly CdpSourceEnvironment[];
|
|
9
|
+
|
|
10
|
+
/** context.identity_trust. Whether anonymousId on THIS event identifies a single real visitor. */
|
|
11
|
+
export type CdpIdentityTrust = 'device' | 'shared_render' | 'none';
|
|
12
|
+
export declare const CDP_IDENTITY_TRUST_VALUES: readonly CdpIdentityTrust[];
|
|
13
|
+
|
|
14
|
+
/** context.page.type. Normalised page taxonomy. */
|
|
15
|
+
export type CdpPageType = 'home' | 'list' | 'product' | 'search' | 'basket' | 'checkout' | 'order_confirmation' | 'login' | 'register' | 'account' | 'info' | 'blog' | 'other';
|
|
16
|
+
export declare const CDP_PAGE_TYPE_VALUES: readonly CdpPageType[];
|
|
17
|
+
|
|
18
|
+
/** context.device.type. The kind of thing this is. */
|
|
19
|
+
export type CdpDeviceType = 'desktop' | 'mobile' | 'tablet' | 'bot' | 'app';
|
|
20
|
+
export declare const CDP_DEVICE_TYPE_VALUES: readonly CdpDeviceType[];
|
|
21
|
+
|
|
22
|
+
/** context.device.network. How the device was connected when the event happened, as a native app reports it. */
|
|
23
|
+
export type CdpDeviceNetwork = 'wifi' | 'cellular' | 'wired' | 'offline' | 'other';
|
|
24
|
+
export declare const CDP_DEVICE_NETWORK_VALUES: readonly CdpDeviceNetwork[];
|
|
25
|
+
|
|
26
|
+
/** context.consent.basis. Who decided whether this event may be collected (spec 1.5). */
|
|
27
|
+
export type CdpConsentBasis = 'visitor' | 'host';
|
|
28
|
+
export declare const CDP_CONSENT_BASIS_VALUES: readonly CdpConsentBasis[];
|
|
29
|
+
|
|
30
|
+
/** context.decisions[].kind. The KIND of decision this was. */
|
|
31
|
+
export type CdpDecisionKind = 'recommendation' | 'search_rank' | 'banner' | 'offer' | 'layout' | 'copy' | 'price' | 'other';
|
|
32
|
+
export declare const CDP_DECISION_KIND_VALUES: readonly CdpDecisionKind[];
|
|
33
|
+
|
|
34
|
+
/** context.consent. The purposes a visitor answers for: context.consent and Consent Updated `state`. */
|
|
35
|
+
export type CdpConsentPurpose = 'analytics' | 'marketing' | 'personalisation' | 'ad_user_data' | 'ad_personalization';
|
|
36
|
+
export declare const CDP_CONSENT_PURPOSE_VALUES: readonly CdpConsentPurpose[];
|
|
5
37
|
|
|
6
38
|
/** The product shape shared by every commerce event. */
|
|
7
39
|
export interface CdpProduct {
|
|
@@ -138,6 +170,13 @@ export interface ProductsSearched {
|
|
|
138
170
|
search_request_id?: string | null;
|
|
139
171
|
}
|
|
140
172
|
|
|
173
|
+
/** A search box suggested products for a partly typed query, and they were seen. */
|
|
174
|
+
export interface SearchSuggestionsViewed {
|
|
175
|
+
query: string;
|
|
176
|
+
products: CdpProduct[];
|
|
177
|
+
search_request_id?: string | null;
|
|
178
|
+
}
|
|
179
|
+
|
|
141
180
|
/** A product was added to the basket. */
|
|
142
181
|
export interface ProductAdded {
|
|
143
182
|
products: CdpProduct[];
|
|
@@ -261,6 +300,7 @@ export interface OrderUpdated {
|
|
|
261
300
|
taxes_included?: boolean | null;
|
|
262
301
|
discount?: number | null;
|
|
263
302
|
coupon?: string | null;
|
|
303
|
+
payment_method?: string | null;
|
|
264
304
|
products?: CdpProduct[] | null;
|
|
265
305
|
reason?: 'line_added' | 'line_removed' | 'quantity_changed' | 'address_changed' | 'delivery_changed' | 'payment_changed' | 'price_adjusted' | 'partial_cancellation' | 'other' | 'status_changed' | null;
|
|
266
306
|
status?: 'processing' | 'despatched' | 'partially_despatched' | 'delivered' | 'collected' | 'held' | 'payment_problem' | null;
|
|
@@ -591,7 +631,7 @@ export interface ApplicationUpdated {
|
|
|
591
631
|
}
|
|
592
632
|
|
|
593
633
|
/** The spec version these models implement. SDKs stamp it on context.spec.version. */
|
|
594
|
-
export const SPEC_VERSION = '1.
|
|
634
|
+
export const SPEC_VERSION = '1.12';
|
|
595
635
|
|
|
596
636
|
/** Every event name the registry accepts. Anything else is rejected at the ingest. */
|
|
597
637
|
export type CdpEventName =
|
|
@@ -605,6 +645,7 @@ export type CdpEventName =
|
|
|
605
645
|
| 'Product Clicked'
|
|
606
646
|
| 'Product List Filtered'
|
|
607
647
|
| 'Products Searched'
|
|
648
|
+
| 'Search Suggestions Viewed'
|
|
608
649
|
| 'Product Added'
|
|
609
650
|
| 'Product Removed'
|
|
610
651
|
| 'Cart Viewed'
|
|
@@ -663,6 +704,7 @@ export interface CdpEventProperties {
|
|
|
663
704
|
'Product Clicked': ProductClicked;
|
|
664
705
|
'Product List Filtered': ProductListFiltered;
|
|
665
706
|
'Products Searched': ProductsSearched;
|
|
707
|
+
'Search Suggestions Viewed': SearchSuggestionsViewed;
|
|
666
708
|
'Product Added': ProductAdded;
|
|
667
709
|
'Product Removed': ProductRemoved;
|
|
668
710
|
'Cart Viewed': CartViewed;
|
package/types/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
CartViewed,
|
|
7
7
|
CdpEventName,
|
|
8
8
|
CdpEventProperties,
|
|
9
|
+
CdpPageType,
|
|
9
10
|
CdpProduct,
|
|
10
11
|
CheckoutFailed,
|
|
11
12
|
CheckoutStarted,
|
|
@@ -36,6 +37,7 @@ import type {
|
|
|
36
37
|
PromotionClicked,
|
|
37
38
|
PromotionViewed,
|
|
38
39
|
RecommendationsViewed,
|
|
40
|
+
SearchSuggestionsViewed,
|
|
39
41
|
SignedIn,
|
|
40
42
|
SignedUp,
|
|
41
43
|
SlotEngaged,
|
|
@@ -45,20 +47,8 @@ import type {
|
|
|
45
47
|
export type { ConsentAdapter, ConsentBasis, ConsentState } from './consent';
|
|
46
48
|
export type * from './events';
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
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';
|
|
50
|
+
/** context.page.type: the spec's page taxonomy, generated. */
|
|
51
|
+
export type PageType = CdpPageType;
|
|
62
52
|
|
|
63
53
|
export interface PageProperties {
|
|
64
54
|
/** Stored as context.page.type and inherited by later events on the page. */
|
|
@@ -252,9 +242,13 @@ export interface AnalyticsClient {
|
|
|
252
242
|
properties?: Record<string, unknown>,
|
|
253
243
|
options?: CallOptions,
|
|
254
244
|
): CdpEvent | null;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
245
|
+
/** Null when the event is not sent: outside the sample, dropped by `beforeSend`, or failed. */
|
|
246
|
+
page(properties?: PageProperties, options?: CallOptions): CdpEvent | null;
|
|
247
|
+
identify(traits?: IdentifyTraits, options?: CallOptions): CdpEvent | null;
|
|
248
|
+
/**
|
|
249
|
+
* Sign-out on a shared device: forget the user, the page chain, the SDK's session, the decisions
|
|
250
|
+
* in force and the recommendation context. The device id stays.
|
|
251
|
+
*/
|
|
258
252
|
reset(): void;
|
|
259
253
|
|
|
260
254
|
productViewed: Emit<ProductViewed>;
|
|
@@ -293,7 +287,13 @@ export interface AnalyticsClient {
|
|
|
293
287
|
personalisationGroup(): PersonalisationGroup;
|
|
294
288
|
productClicked: Emit<ProductClicked>;
|
|
295
289
|
productListFiltered: Emit<ProductListFiltered>;
|
|
290
|
+
/** A submitted search and the results page it produced. */
|
|
296
291
|
productsSearched: Emit<ProductsSearched>;
|
|
292
|
+
/**
|
|
293
|
+
* The search box's suggested products for a partly typed query, once seen. Never productsSearched,
|
|
294
|
+
* which is a submitted search's results page.
|
|
295
|
+
*/
|
|
296
|
+
searchSuggestionsViewed: Emit<SearchSuggestionsViewed>;
|
|
297
297
|
productAdded: Emit<ProductAdded>;
|
|
298
298
|
productRemoved: Emit<ProductRemoved>;
|
|
299
299
|
/**
|