@pack/react 4.5.0 → 4.6.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/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ import { useOverlayScript } from "./use-overlay-script";
7
7
  import { usePlaybookCart } from "./use-playbook-cart";
8
8
  import { registerSection } from "./register-section";
9
9
  import { registerStorefrontSettingsSchema } from "./register-storefront-settings-schema";
10
- export { PackProvider, PreviewProvider, RenderSection, RenderSections, registerSection, registerStorefrontSettingsSchema, useLocale, useOverlayScript, usePackContext, usePlaybookCart, useSections, useSiteSettings, };
10
+ import { setPackConsent } from "./pack/consent";
11
+ export { PackProvider, PreviewProvider, RenderSection, RenderSections, registerSection, registerStorefrontSettingsSchema, setPackConsent, useLocale, useOverlayScript, usePackContext, usePlaybookCart, useSections, useSiteSettings, };
11
12
  export type { PlaybookCartAdd, PlaybookCartInput } from "./use-playbook-cart";
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,WAAW,EACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AAEzF,OAAO,EACL,YAAY,EACZ,eAAe,EACf,aAAa,EACb,cAAc,EACd,eAAe,EACf,gCAAgC,EAChC,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,GAChB,CAAC;AAEF,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,WAAW,EACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EACL,YAAY,EACZ,eAAe,EACf,aAAa,EACb,cAAc,EACd,eAAe,EACf,gCAAgC,EAChC,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,GAChB,CAAC;AAEF,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -7,4 +7,5 @@ import { useOverlayScript } from "./use-overlay-script";
7
7
  import { usePlaybookCart } from "./use-playbook-cart";
8
8
  import { registerSection } from "./register-section";
9
9
  import { registerStorefrontSettingsSchema } from "./register-storefront-settings-schema";
10
- export { PackProvider, PreviewProvider, RenderSection, RenderSections, registerSection, registerStorefrontSettingsSchema, useLocale, useOverlayScript, usePackContext, usePlaybookCart, useSections, useSiteSettings, };
10
+ import { setPackConsent } from "./pack/consent";
11
+ export { PackProvider, PreviewProvider, RenderSection, RenderSections, registerSection, registerStorefrontSettingsSchema, setPackConsent, useLocale, useOverlayScript, usePackContext, usePlaybookCart, useSections, useSiteSettings, };
@@ -0,0 +1,25 @@
1
+ declare const PURPOSES: readonly ["analytics", "marketing", "preferences", "saleOfData"];
2
+ export type ConsentPurpose = (typeof PURPOSES)[number];
3
+ /** A consent update, per purpose. Omitted purposes keep their current state. */
4
+ export type PackConsentUpdate = Partial<Record<ConsentPurpose, boolean>>;
5
+ export declare const UNSUPPORTED_RETRY_MS: number;
6
+ /**
7
+ * Record the visitor's consent with Pack, for themes whose banner is not
8
+ * Shopify's (OneTrust, Cookiebot, ...). The server sets
9
+ * `__pack_user_consent`; purposes that already match are not re-sent.
10
+ *
11
+ * Stops for 30 minutes, then retries, once the answer can't be Pack's
12
+ * endpoint: a 404 or 405, a redirect, or a 2xx that isn't JSON. A tab left open
13
+ * across an upgrade to `@pack/hydrogen` 4.0 records again after the retry.
14
+ */
15
+ export declare function setPackConsent(update: PackConsentUpdate): Promise<void>;
16
+ /**
17
+ * Forward the visitor's answer to Shopify's privacy banner to Pack.
18
+ *
19
+ * Reads Shopify's Customer Privacy API once it is loaded and again whenever
20
+ * the visitor answers the banner. An unanswered purpose is never sent, so the
21
+ * server keeps treating it as unknown.
22
+ */
23
+ export declare function usePackConsentBridge(): void;
24
+ export {};
25
+ //# sourceMappingURL=consent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/pack/consent.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,QAAQ,kEAAmE,CAAC;AAQlF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAIvD,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;AAwCzE,eAAO,MAAM,oBAAoB,QAAiB,CAAC;AAuBnD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqC7E;AAkCD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CA0B3C"}
@@ -0,0 +1,171 @@
1
+ import { useEffect } from "react";
2
+ // Must mirror @pack/hydrogen/src/session/consent.ts (cookie name, keys and
3
+ // value format) and consent-endpoint.ts (path).
4
+ const CONSENT_COOKIE = "__pack_user_consent";
5
+ const CONSENT_PATH = "/pack/consent";
6
+ const PURPOSES = ["analytics", "marketing", "preferences", "saleOfData"];
7
+ const COOKIE_KEYS = {
8
+ analytics: "a",
9
+ marketing: "m",
10
+ preferences: "p",
11
+ saleOfData: "s",
12
+ };
13
+ function readConsentCookie() {
14
+ const consent = {
15
+ analytics: "unknown",
16
+ marketing: "unknown",
17
+ preferences: "unknown",
18
+ saleOfData: "unknown",
19
+ };
20
+ let raw;
21
+ try {
22
+ const entry = document.cookie
23
+ .split("; ")
24
+ .find((part) => part.startsWith(`${CONSENT_COOKIE}=`));
25
+ raw = entry && decodeURIComponent(entry.slice(CONSENT_COOKIE.length + 1));
26
+ }
27
+ catch {
28
+ return consent;
29
+ }
30
+ if (!raw)
31
+ return consent;
32
+ if (raw === "true" || raw === "false") {
33
+ consent.analytics = raw === "true" ? "granted" : "denied";
34
+ return consent;
35
+ }
36
+ const params = new URLSearchParams(raw);
37
+ for (const purpose of PURPOSES) {
38
+ const value = params.get(COOKIE_KEYS[purpose]);
39
+ if (value === "1")
40
+ consent[purpose] = "granted";
41
+ else if (value === "0")
42
+ consent[purpose] = "denied";
43
+ }
44
+ return consent;
45
+ }
46
+ // A server without the endpoint (@pack/hydrogen before 4.0, or another
47
+ // framework) never writes the cookie, so without this every answer and every
48
+ // page load would post again. Retried after a while so a tab left open across
49
+ // an upgrade to 4.0 starts recording again.
50
+ const UNSUPPORTED_KEY = "__pack_consent_unsupported";
51
+ export const UNSUPPORTED_RETRY_MS = 30 * 60 * 1000;
52
+ let unsupportedAtInMemory;
53
+ function isEndpointUnsupported() {
54
+ let since = unsupportedAtInMemory;
55
+ if (since === undefined) {
56
+ try {
57
+ const stored = Number(sessionStorage.getItem(UNSUPPORTED_KEY));
58
+ if (stored > 0)
59
+ since = stored;
60
+ }
61
+ catch { }
62
+ }
63
+ return since !== undefined && Date.now() - since < UNSUPPORTED_RETRY_MS;
64
+ }
65
+ function markEndpointUnsupported() {
66
+ const now = Date.now();
67
+ try {
68
+ sessionStorage.setItem(UNSUPPORTED_KEY, String(now));
69
+ }
70
+ catch {
71
+ unsupportedAtInMemory = now;
72
+ }
73
+ }
74
+ /**
75
+ * Record the visitor's consent with Pack, for themes whose banner is not
76
+ * Shopify's (OneTrust, Cookiebot, ...). The server sets
77
+ * `__pack_user_consent`; purposes that already match are not re-sent.
78
+ *
79
+ * Stops for 30 minutes, then retries, once the answer can't be Pack's
80
+ * endpoint: a 404 or 405, a redirect, or a 2xx that isn't JSON. A tab left open
81
+ * across an upgrade to `@pack/hydrogen` 4.0 records again after the retry.
82
+ */
83
+ export async function setPackConsent(update) {
84
+ if (isEndpointUnsupported())
85
+ return;
86
+ const current = readConsentCookie();
87
+ const changed = {};
88
+ for (const purpose of PURPOSES) {
89
+ const value = update[purpose];
90
+ if (typeof value !== "boolean")
91
+ continue;
92
+ if (current[purpose] !== (value ? "granted" : "denied")) {
93
+ changed[purpose] = value;
94
+ }
95
+ }
96
+ if (!Object.keys(changed).length)
97
+ return;
98
+ try {
99
+ const response = await fetch(CONSENT_PATH, {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify(changed),
103
+ credentials: "same-origin",
104
+ keepalive: true,
105
+ });
106
+ const isPackEndpoint = response.status !== 404 &&
107
+ response.status !== 405 &&
108
+ !response.redirected &&
109
+ (!response.ok ||
110
+ !!response.headers
111
+ .get("Content-Type")
112
+ ?.toLowerCase()
113
+ .includes("application/json"));
114
+ if (!isPackEndpoint)
115
+ markEndpointUnsupported();
116
+ }
117
+ catch (error) {
118
+ console.error("Error hit /pack/consent: ", error?.message);
119
+ }
120
+ }
121
+ function fromShopifyValue(value) {
122
+ if (value === "yes")
123
+ return true;
124
+ if (value === "no")
125
+ return false;
126
+ return undefined;
127
+ }
128
+ function readShopifyConsent() {
129
+ const customerPrivacy = window.Shopify?.customerPrivacy;
130
+ const consent = customerPrivacy?.currentVisitorConsent?.();
131
+ if (!consent)
132
+ return undefined;
133
+ return {
134
+ analytics: fromShopifyValue(consent.analytics),
135
+ marketing: fromShopifyValue(consent.marketing),
136
+ preferences: fromShopifyValue(consent.preferences),
137
+ saleOfData: fromShopifyValue(consent.sale_of_data),
138
+ };
139
+ }
140
+ /**
141
+ * Forward the visitor's answer to Shopify's privacy banner to Pack.
142
+ *
143
+ * Reads Shopify's Customer Privacy API once it is loaded and again whenever
144
+ * the visitor answers the banner. An unanswered purpose is never sent, so the
145
+ * server keeps treating it as unknown.
146
+ */
147
+ export function usePackConsentBridge() {
148
+ useEffect(() => {
149
+ const syncFromApi = () => {
150
+ const consent = readShopifyConsent();
151
+ if (consent)
152
+ setPackConsent(consent);
153
+ };
154
+ const onCollected = (event) => {
155
+ const detail = event.detail || {};
156
+ setPackConsent({
157
+ analytics: detail.analyticsAllowed,
158
+ marketing: detail.marketingAllowed,
159
+ preferences: detail.preferencesAllowed,
160
+ saleOfData: detail.saleOfDataAllowed,
161
+ });
162
+ };
163
+ document.addEventListener("visitorConsentCollected", onCollected);
164
+ document.addEventListener("consentTrackingApiLoaded", syncFromApi);
165
+ syncFromApi();
166
+ return () => {
167
+ document.removeEventListener("visitorConsentCollected", onCollected);
168
+ document.removeEventListener("consentTrackingApiLoaded", syncFromApi);
169
+ };
170
+ }, []);
171
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"pack-context.d.ts","sourceRoot":"","sources":["../../src/pack/pack-context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAKZ,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxE,OAAO,cAAc,CAAC;AAEtB,KAAK,gBAAgB,GAAG;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE;QACf,OAAO,CAAC,EAAE;YACR,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,cAAc,CAAC,EAAE,cAAc,CAAC;QAChC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,YAAY,EAAE,GAAG,CAAC;IAClB,eAAe,EAAE,CAAC,YAAY,EAAE,GAAG,KAAK,IAAI,CAAC;IAC7C,WAAW,EAAE,GAAG,CAAC;IACjB,cAAc,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,CAAC;IACT,SAAS,EAAE,CACT,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,KACL,IAAI,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,WAAW,iCAUtB,CAAC;AAEH,eAAO,MAAM,cAAc,wBAAgC,CAAC;AAE5D,UAAU,gBAAgB;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,YAAY,EAAE,GAAG,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,cAAc,CAAC,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IACpD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,CAAC;IACT,WAAW,CAAC,EAAE,GAAG,CAAC;CACnB;AAUD,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,oBAA4B,EAC5B,YAAY,EAAE,yBAAyB,EACvC,cAAc,EACd,kBAAuB,EACvB,MAAM,EAAE,aAAoB,GAC7B,EAAE,gBAAgB,qBAuDlB;AAED,eAAO,MAAM,eAAe,qBAAe,CAAC"}
1
+ {"version":3,"file":"pack-context.d.ts","sourceRoot":"","sources":["../../src/pack/pack-context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAKZ,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxE,OAAO,cAAc,CAAC;AAEtB,KAAK,gBAAgB,GAAG;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE;QACf,OAAO,CAAC,EAAE;YACR,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,cAAc,CAAC,EAAE,cAAc,CAAC;QAChC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,YAAY,EAAE,GAAG,CAAC;IAClB,eAAe,EAAE,CAAC,YAAY,EAAE,GAAG,KAAK,IAAI,CAAC;IAC7C,WAAW,EAAE,GAAG,CAAC;IACjB,cAAc,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,CAAC;IACT,SAAS,EAAE,CACT,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,KACL,IAAI,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,WAAW,iCAUtB,CAAC;AAEH,eAAO,MAAM,cAAc,wBAAgC,CAAC;AAE5D,UAAU,gBAAgB;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,YAAY,EAAE,GAAG,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,cAAc,CAAC,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IACpD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,CAAC;IACT,WAAW,CAAC,EAAE,GAAG,CAAC;CACnB;AAUD,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,oBAA4B,EAC5B,YAAY,EAAE,yBAAyB,EACvC,cAAc,EACd,kBAAuB,EACvB,MAAM,EAAE,aAAoB,GAC7B,EAAE,gBAAgB,qBAwDlB;AAED,eAAO,MAAM,eAAe,qBAAe,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import React, { createContext, useContext, useState, useMemo, useEffect, } from "react";
2
2
  import { usePackTrack } from "./pack-analytics";
3
+ import { usePackConsentBridge } from "./consent";
3
4
  import { PreviewToolbar } from "./preview-toolbar";
4
5
  import { useOverlayScript } from "../use-overlay-script";
5
6
  import { useCustomizerShell } from "../use-customizer-shell";
@@ -53,6 +54,7 @@ export function PackProvider({ children, isPreviewModeEnabled = false, siteSetti
53
54
  setSiteSettings(previewStorefrontSettings);
54
55
  }, [previewStorefrontSettings]);
55
56
  usePackTrack();
57
+ usePackConsentBridge();
56
58
  return (React.createElement(PackContext.Provider, { value: value },
57
59
  React.createElement(CustomizerShell, null),
58
60
  children,
@@ -11,6 +11,7 @@ interface StorefrontTestRulesCacheInfo {
11
11
  }
12
12
  interface PackDebugHelpers {
13
13
  getTestInfo(): StorefrontTestInfo | null;
14
+ getTestInfos(): StorefrontTestInfo[];
14
15
  getTestRulesCacheInfo(): StorefrontTestRulesCacheInfo | null;
15
16
  }
16
17
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"pack-debug.d.ts","sourceRoot":"","sources":["../../src/pack/pack-debug.ts"],"names":[],"mappings":"AAmBA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,UAAU,4BAA4B;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,gBAAgB;IACxB,WAAW,IAAI,kBAAkB,GAAG,IAAI,CAAC;IACzC,qBAAqB,IAAI,4BAA4B,GAAG,IAAI,CAAC;CAC9D;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B;CACF;AA0HD,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"pack-debug.d.ts","sourceRoot":"","sources":["../../src/pack/pack-debug.ts"],"names":[],"mappings":"AA2BA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,UAAU,4BAA4B;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,gBAAgB;IACxB,WAAW,IAAI,kBAAkB,GAAG,IAAI,CAAC;IACzC,YAAY,IAAI,kBAAkB,EAAE,CAAC;IACrC,qBAAqB,IAAI,4BAA4B,GAAG,IAAI,CAAC;CAC9D;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B;CACF;AAwKD,OAAO,EAAE,CAAC"}
@@ -57,18 +57,53 @@ function parseTestSessionCookie(cookieHeader) {
57
57
  }
58
58
  return null;
59
59
  }
60
- function getStorefrontTestInfo() {
61
- const payload = parseTestSessionCookie(document.cookie || "");
62
- const test = payload?.test_data?.handle;
63
- const variant = payload?.test_data?.testVariant?.handle;
60
+ function toStorefrontTestInfo(assignment, expiresAt) {
61
+ const test = assignment?.handle;
62
+ const variant = assignment?.testVariant?.handle;
64
63
  if (!test || !variant) {
65
64
  return null;
66
65
  }
67
- return {
68
- test,
69
- variant,
70
- expiresAt: payload?.test_expire_at || null,
71
- };
66
+ return { test, variant, expiresAt };
67
+ }
68
+ /**
69
+ * Every test the visitor is currently bucketed into, oldest assignment first.
70
+ *
71
+ * Read from the session cookie, so this is what the visitor is *assigned* to —
72
+ * not which of those tests is serving the current page. Which assignment supplies
73
+ * content is decided per request on the server from the page's targeting rules
74
+ * and is never persisted; check the CMS request log or `useAbTest()` for that.
75
+ *
76
+ * Falls back to the pre-4.0 single-slot payload for sessions written by an older
77
+ * SDK that have not been migrated yet.
78
+ */
79
+ function getStorefrontTestInfos() {
80
+ const payload = parseTestSessionCookie(document.cookie || "");
81
+ const expiresAt = payload?.test_expire_at || null;
82
+ const assignments = payload?.test_data_map;
83
+ if (assignments && typeof assignments === "object") {
84
+ // Ordered the way the SDK picks its primary: oldest first, ties broken on
85
+ // test id. Every assignment made in one request shares a millisecond, so the
86
+ // tie-break decides the common case — without it `getTestInfo()` can name a
87
+ // different test than `pack.abTest` does. The map is keyed by test id, so
88
+ // the key stands in when a hand-edited entry has none.
89
+ return Object.entries(assignments)
90
+ .sort(([idA, a], [idB, b]) => (a?.assignedAt ?? 0) - (b?.assignedAt ?? 0) ||
91
+ (a?.id ?? idA).localeCompare(b?.id ?? idB))
92
+ .map(([, assignment]) => toStorefrontTestInfo(assignment, expiresAt))
93
+ .filter((info) => info !== null);
94
+ }
95
+ const legacy = toStorefrontTestInfo(payload?.test_data, expiresAt);
96
+ return legacy ? [legacy] : [];
97
+ }
98
+ /**
99
+ * The oldest assignment on the session.
100
+ *
101
+ * Deliberately not "the test serving this page": a visitor can be assigned to
102
+ * several tests at once, and the one supplying content varies by page. Kept for
103
+ * back-compat with the single-test debug surface — prefer `getTestInfos()`.
104
+ */
105
+ function getStorefrontTestInfo() {
106
+ return getStorefrontTestInfos()[0] || null;
72
107
  }
73
108
  function getStorefrontTestRulesCacheInfo() {
74
109
  const payload = parseTestSessionCookie(document.cookie || "");
@@ -98,6 +133,7 @@ function initializePackDebugHelpers() {
98
133
  window.packDebug = {
99
134
  ...window.packDebug,
100
135
  getTestInfo: getStorefrontTestInfo,
136
+ getTestInfos: getStorefrontTestInfos,
101
137
  getTestRulesCacheInfo: getStorefrontTestRulesCacheInfo,
102
138
  };
103
139
  }
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "4.5.0";
1
+ export const VERSION = "4.6.0";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pack/react",
3
3
  "description": "React",
4
- "version": "4.5.0",
4
+ "version": "4.6.0",
5
5
  "exports": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "engines": {