@wix/web5-core 1.63.15 → 1.63.17
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/cjs/component/componentDefinitions/TextBlockSectionDefinition.js +36 -32
- package/dist/cjs/component/componentDefinitions/TextBlockSectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/index.js +5 -4
- package/dist/cjs/component/componentDefinitions/index.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/parse-utils.js +43 -6
- package/dist/cjs/component/componentDefinitions/parse-utils.js.map +1 -1
- package/dist/cjs/index.js +32 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/match/astToParts.js +14 -0
- package/dist/cjs/match/astToParts.js.map +1 -1
- package/dist/cjs/privacy/consentGate.js +275 -0
- package/dist/cjs/privacy/consentGate.js.map +1 -0
- package/dist/cjs/privacy/consentOverride.js +94 -0
- package/dist/cjs/privacy/consentOverride.js.map +1 -0
- package/dist/cjs/privacy/detectProvider.js +71 -0
- package/dist/cjs/privacy/detectProvider.js.map +1 -0
- package/dist/cjs/privacy/index.js +40 -0
- package/dist/cjs/privacy/index.js.map +1 -0
- package/dist/cjs/privacy/providers/hostSuppliedProvider.js +89 -0
- package/dist/cjs/privacy/providers/hostSuppliedProvider.js.map +1 -0
- package/dist/cjs/privacy/providers/oneTrustProvider.js +72 -0
- package/dist/cjs/privacy/providers/oneTrustProvider.js.map +1 -0
- package/dist/cjs/privacy/providers/shopifyProvider.js +177 -0
- package/dist/cjs/privacy/providers/shopifyProvider.js.map +1 -0
- package/dist/cjs/privacy/types.js +34 -0
- package/dist/cjs/privacy/types.js.map +1 -0
- package/dist/cjs/utils/analyticsEvents.js +30 -25
- package/dist/cjs/utils/analyticsEvents.js.map +1 -1
- package/dist/esm/component/componentDefinitions/TextBlockSectionDefinition.js +37 -33
- package/dist/esm/component/componentDefinitions/TextBlockSectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/index.js +5 -4
- package/dist/esm/component/componentDefinitions/index.js.map +1 -1
- package/dist/esm/component/componentDefinitions/parse-utils.js +42 -6
- package/dist/esm/component/componentDefinitions/parse-utils.js.map +1 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/match/astToParts.js +14 -0
- package/dist/esm/match/astToParts.js.map +1 -1
- package/dist/esm/privacy/consentGate.js +265 -0
- package/dist/esm/privacy/consentGate.js.map +1 -0
- package/dist/esm/privacy/consentOverride.js +87 -0
- package/dist/esm/privacy/consentOverride.js.map +1 -0
- package/dist/esm/privacy/detectProvider.js +65 -0
- package/dist/esm/privacy/detectProvider.js.map +1 -0
- package/dist/esm/privacy/index.js +8 -0
- package/dist/esm/privacy/index.js.map +1 -0
- package/dist/esm/privacy/providers/hostSuppliedProvider.js +81 -0
- package/dist/esm/privacy/providers/hostSuppliedProvider.js.map +1 -0
- package/dist/esm/privacy/providers/oneTrustProvider.js +66 -0
- package/dist/esm/privacy/providers/oneTrustProvider.js.map +1 -0
- package/dist/esm/privacy/providers/shopifyProvider.js +174 -0
- package/dist/esm/privacy/providers/shopifyProvider.js.map +1 -0
- package/dist/esm/privacy/types.js +30 -0
- package/dist/esm/privacy/types.js.map +1 -0
- package/dist/esm/utils/analyticsEvents.js +30 -25
- package/dist/esm/utils/analyticsEvents.js.map +1 -1
- package/dist/types/component/componentDefinitions/TextBlockSectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/index.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/parse-utils.d.ts +21 -0
- package/dist/types/component/componentDefinitions/parse-utils.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/match/astToParts.d.ts.map +1 -1
- package/dist/types/privacy/consentGate.d.ts +85 -0
- package/dist/types/privacy/consentGate.d.ts.map +1 -0
- package/dist/types/privacy/consentOverride.d.ts +33 -0
- package/dist/types/privacy/consentOverride.d.ts.map +1 -0
- package/dist/types/privacy/detectProvider.d.ts +25 -0
- package/dist/types/privacy/detectProvider.d.ts.map +1 -0
- package/dist/types/privacy/index.d.ts +8 -0
- package/dist/types/privacy/index.d.ts.map +1 -0
- package/dist/types/privacy/providers/hostSuppliedProvider.d.ts +29 -0
- package/dist/types/privacy/providers/hostSuppliedProvider.d.ts.map +1 -0
- package/dist/types/privacy/providers/oneTrustProvider.d.ts +15 -0
- package/dist/types/privacy/providers/oneTrustProvider.d.ts.map +1 -0
- package/dist/types/privacy/providers/shopifyProvider.d.ts +26 -0
- package/dist/types/privacy/providers/shopifyProvider.d.ts.map +1 -0
- package/dist/types/privacy/types.d.ts +32 -0
- package/dist/types/privacy/types.d.ts.map +1 -0
- package/dist/types/utils/analyticsEvents.d.ts +9 -0
- package/dist/types/utils/analyticsEvents.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-supplied provider — DL #218.
|
|
3
|
+
*
|
|
4
|
+
* The escape hatch for every CMP we cannot detect: a host page that already
|
|
5
|
+
* knows its visitor's answer publishes it, and the gate believes it. This is
|
|
6
|
+
* how Circana, feature.com and any merchant on Cookiebot/Osano/Klaviyo reach
|
|
7
|
+
* the gate without web5 learning each vendor's API.
|
|
8
|
+
*
|
|
9
|
+
* Two ways in, because hosts differ in when they know:
|
|
10
|
+
* - `window.__web5_consent__` set before the bundle loads, read at install
|
|
11
|
+
* - `publishHostConsent()` called at any time afterwards
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { UNKNOWN_CONSENT } from '../types.js';
|
|
15
|
+
export const HOST_CONSENT_GLOBAL = '__web5_consent__';
|
|
16
|
+
|
|
17
|
+
/** What a host may publish. Anything missing stays `unknown`. */
|
|
18
|
+
|
|
19
|
+
const coerce = value => {
|
|
20
|
+
if (value === true) return 'granted';
|
|
21
|
+
if (value === false) return 'denied';
|
|
22
|
+
if (value === 'granted' || value === 'denied' || value === 'unknown') {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
return 'unknown';
|
|
26
|
+
};
|
|
27
|
+
const normalize = input => {
|
|
28
|
+
if (!input || typeof input !== 'object') {
|
|
29
|
+
return UNKNOWN_CONSENT;
|
|
30
|
+
}
|
|
31
|
+
const analytics = coerce(input.analytics);
|
|
32
|
+
return {
|
|
33
|
+
analytics,
|
|
34
|
+
marketing: coerce(input.marketing),
|
|
35
|
+
// A host that speaks only about analytics is taken to mean the same for
|
|
36
|
+
// load telemetry, which is the same wire and the same recipient.
|
|
37
|
+
performance: input.performance === undefined ? analytics : coerce(input.performance)
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const readGlobal = () => {
|
|
41
|
+
if (typeof window === 'undefined') {
|
|
42
|
+
return UNKNOWN_CONSENT;
|
|
43
|
+
}
|
|
44
|
+
const raw = window[HOST_CONSENT_GLOBAL];
|
|
45
|
+
return normalize(raw);
|
|
46
|
+
};
|
|
47
|
+
export const hasHostSuppliedConsent = () => {
|
|
48
|
+
if (typeof window === 'undefined') {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const raw = window[HOST_CONSENT_GLOBAL];
|
|
52
|
+
return !!raw && typeof raw === 'object';
|
|
53
|
+
};
|
|
54
|
+
const subscribers = new Set();
|
|
55
|
+
let published = null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Called by the host — directly, or by the loader when it is handed consent in
|
|
59
|
+
* its boot options — whenever the visitor's answer is known or changes.
|
|
60
|
+
*/
|
|
61
|
+
export const publishHostConsent = input => {
|
|
62
|
+
published = normalize(input);
|
|
63
|
+
for (const subscriber of subscribers) {
|
|
64
|
+
subscriber(published);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
export const createHostSuppliedConsentProvider = () => ({
|
|
68
|
+
name: 'host-supplied',
|
|
69
|
+
read: () => published ?? readGlobal(),
|
|
70
|
+
subscribe(onChange) {
|
|
71
|
+
subscribers.add(onChange);
|
|
72
|
+
return () => {
|
|
73
|
+
subscribers.delete(onChange);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
export const __resetHostConsentForTests = () => {
|
|
78
|
+
published = null;
|
|
79
|
+
subscribers.clear();
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=hostSuppliedProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["UNKNOWN_CONSENT","HOST_CONSENT_GLOBAL","coerce","value","normalize","input","analytics","marketing","performance","undefined","readGlobal","window","raw","hasHostSuppliedConsent","subscribers","Set","published","publishHostConsent","subscriber","createHostSuppliedConsentProvider","name","read","subscribe","onChange","add","delete","__resetHostConsentForTests","clear"],"sources":["../../../../src/privacy/providers/hostSuppliedProvider.ts"],"sourcesContent":["/**\n * Host-supplied provider — DL #218.\n *\n * The escape hatch for every CMP we cannot detect: a host page that already\n * knows its visitor's answer publishes it, and the gate believes it. This is\n * how Circana, feature.com and any merchant on Cookiebot/Osano/Klaviyo reach\n * the gate without web5 learning each vendor's API.\n *\n * Two ways in, because hosts differ in when they know:\n * - `window.__web5_consent__` set before the bundle loads, read at install\n * - `publishHostConsent()` called at any time afterwards\n */\n\nimport {\n UNKNOWN_CONSENT,\n type ConsentProvider,\n type ConsentSnapshot,\n type ConsentState,\n} from '../types';\n\nexport const HOST_CONSENT_GLOBAL = '__web5_consent__';\n\n/** What a host may publish. Anything missing stays `unknown`. */\nexport interface HostConsentInput {\n analytics?: ConsentState | boolean;\n marketing?: ConsentState | boolean;\n performance?: ConsentState | boolean;\n}\n\nconst coerce = (value: ConsentState | boolean | undefined): ConsentState => {\n if (value === true) return 'granted';\n if (value === false) return 'denied';\n if (value === 'granted' || value === 'denied' || value === 'unknown') {\n return value;\n }\n return 'unknown';\n};\n\nconst normalize = (input: HostConsentInput | null | undefined): ConsentSnapshot => {\n if (!input || typeof input !== 'object') {\n return UNKNOWN_CONSENT;\n }\n const analytics = coerce(input.analytics);\n return {\n analytics,\n marketing: coerce(input.marketing),\n // A host that speaks only about analytics is taken to mean the same for\n // load telemetry, which is the same wire and the same recipient.\n performance:\n input.performance === undefined ? analytics : coerce(input.performance),\n };\n};\n\nconst readGlobal = (): ConsentSnapshot => {\n if (typeof window === 'undefined') {\n return UNKNOWN_CONSENT;\n }\n const raw = (window as unknown as Record<string, unknown>)[HOST_CONSENT_GLOBAL];\n return normalize(raw as HostConsentInput | undefined);\n};\n\nexport const hasHostSuppliedConsent = (): boolean => {\n if (typeof window === 'undefined') {\n return false;\n }\n const raw = (window as unknown as Record<string, unknown>)[HOST_CONSENT_GLOBAL];\n return !!raw && typeof raw === 'object';\n};\n\nconst subscribers = new Set<(snapshot: ConsentSnapshot) => void>();\nlet published: ConsentSnapshot | null = null;\n\n/**\n * Called by the host — directly, or by the loader when it is handed consent in\n * its boot options — whenever the visitor's answer is known or changes.\n */\nexport const publishHostConsent = (input: HostConsentInput): void => {\n published = normalize(input);\n for (const subscriber of subscribers) {\n subscriber(published);\n }\n};\n\nexport const createHostSuppliedConsentProvider = (): ConsentProvider => ({\n name: 'host-supplied',\n\n read: () => published ?? readGlobal(),\n\n subscribe(onChange) {\n subscribers.add(onChange);\n return () => {\n subscribers.delete(onChange);\n };\n },\n});\n\nexport const __resetHostConsentForTests = (): void => {\n published = null;\n subscribers.clear();\n};\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SACEA,eAAe,QAIV,UAAU;AAEjB,OAAO,MAAMC,mBAAmB,GAAG,kBAAkB;;AAErD;;AAOA,MAAMC,MAAM,GAAIC,KAAyC,IAAmB;EAC1E,IAAIA,KAAK,KAAK,IAAI,EAAE,OAAO,SAAS;EACpC,IAAIA,KAAK,KAAK,KAAK,EAAE,OAAO,QAAQ;EACpC,IAAIA,KAAK,KAAK,SAAS,IAAIA,KAAK,KAAK,QAAQ,IAAIA,KAAK,KAAK,SAAS,EAAE;IACpE,OAAOA,KAAK;EACd;EACA,OAAO,SAAS;AAClB,CAAC;AAED,MAAMC,SAAS,GAAIC,KAA0C,IAAsB;EACjF,IAAI,CAACA,KAAK,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;IACvC,OAAOL,eAAe;EACxB;EACA,MAAMM,SAAS,GAAGJ,MAAM,CAACG,KAAK,CAACC,SAAS,CAAC;EACzC,OAAO;IACLA,SAAS;IACTC,SAAS,EAAEL,MAAM,CAACG,KAAK,CAACE,SAAS,CAAC;IAClC;IACA;IACAC,WAAW,EACTH,KAAK,CAACG,WAAW,KAAKC,SAAS,GAAGH,SAAS,GAAGJ,MAAM,CAACG,KAAK,CAACG,WAAW;EAC1E,CAAC;AACH,CAAC;AAED,MAAME,UAAU,GAAGA,CAAA,KAAuB;EACxC,IAAI,OAAOC,MAAM,KAAK,WAAW,EAAE;IACjC,OAAOX,eAAe;EACxB;EACA,MAAMY,GAAG,GAAID,MAAM,CAAwCV,mBAAmB,CAAC;EAC/E,OAAOG,SAAS,CAACQ,GAAmC,CAAC;AACvD,CAAC;AAED,OAAO,MAAMC,sBAAsB,GAAGA,CAAA,KAAe;EACnD,IAAI,OAAOF,MAAM,KAAK,WAAW,EAAE;IACjC,OAAO,KAAK;EACd;EACA,MAAMC,GAAG,GAAID,MAAM,CAAwCV,mBAAmB,CAAC;EAC/E,OAAO,CAAC,CAACW,GAAG,IAAI,OAAOA,GAAG,KAAK,QAAQ;AACzC,CAAC;AAED,MAAME,WAAW,GAAG,IAAIC,GAAG,CAAsC,CAAC;AAClE,IAAIC,SAAiC,GAAG,IAAI;;AAE5C;AACA;AACA;AACA;AACA,OAAO,MAAMC,kBAAkB,GAAIZ,KAAuB,IAAW;EACnEW,SAAS,GAAGZ,SAAS,CAACC,KAAK,CAAC;EAC5B,KAAK,MAAMa,UAAU,IAAIJ,WAAW,EAAE;IACpCI,UAAU,CAACF,SAAS,CAAC;EACvB;AACF,CAAC;AAED,OAAO,MAAMG,iCAAiC,GAAGA,CAAA,MAAwB;EACvEC,IAAI,EAAE,eAAe;EAErBC,IAAI,EAAEA,CAAA,KAAML,SAAS,IAAIN,UAAU,CAAC,CAAC;EAErCY,SAASA,CAACC,QAAQ,EAAE;IAClBT,WAAW,CAACU,GAAG,CAACD,QAAQ,CAAC;IACzB,OAAO,MAAM;MACXT,WAAW,CAACW,MAAM,CAACF,QAAQ,CAAC;IAC9B,CAAC;EACH;AACF,CAAC,CAAC;AAEF,OAAO,MAAMG,0BAA0B,GAAGA,CAAA,KAAY;EACpDV,SAAS,GAAG,IAAI;EAChBF,WAAW,CAACa,KAAK,CAAC,CAAC;AACrB,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneTrust provider — DL #218.
|
|
3
|
+
*
|
|
4
|
+
* Lifted from the consent check that used to live inside
|
|
5
|
+
* `utils/analyticsEvents.ts`, with one deliberate behaviour change: that
|
|
6
|
+
* function returned `true` when OneTrust was absent ("host is responsible for
|
|
7
|
+
* loading OneTrust"), which on a Shopify storefront meant unconditional
|
|
8
|
+
* default-allow. Absence is no longer this provider's problem — it is only
|
|
9
|
+
* selected when OneTrust is actually on the page, and absence is handled by
|
|
10
|
+
* the gate's own default.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** OneTrust's default taxonomy: C0002 is the performance/analytics category. */
|
|
14
|
+
const ANALYTICS_CATEGORY = 'C0002';
|
|
15
|
+
/** C0004 is targeting/advertising. */
|
|
16
|
+
const MARKETING_CATEGORY = 'C0004';
|
|
17
|
+
const readGroups = () => {
|
|
18
|
+
if (typeof window === 'undefined') {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const groups = window.OnetrustActiveGroups;
|
|
22
|
+
return typeof groups === 'string' ? groups : null;
|
|
23
|
+
};
|
|
24
|
+
export const isOneTrustHost = () => readGroups() !== null;
|
|
25
|
+
const readSnapshot = () => {
|
|
26
|
+
const groups = readGroups();
|
|
27
|
+
if (groups !== null) {
|
|
28
|
+
try {
|
|
29
|
+
console.log(`[w5-consent] onetrust: active groups "${groups}"`);
|
|
30
|
+
} catch {
|
|
31
|
+
/* never break on a console */
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (groups === null) {
|
|
35
|
+
return {
|
|
36
|
+
analytics: 'unknown',
|
|
37
|
+
marketing: 'unknown',
|
|
38
|
+
performance: 'unknown'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// OneTrust publishes the *active* groups, so a category that is absent from
|
|
42
|
+
// a string OneTrust has written is a refusal, not silence.
|
|
43
|
+
const analytics = groups.includes(ANALYTICS_CATEGORY) ? 'granted' : 'denied';
|
|
44
|
+
const marketing = groups.includes(MARKETING_CATEGORY) ? 'granted' : 'denied';
|
|
45
|
+
return {
|
|
46
|
+
analytics,
|
|
47
|
+
marketing,
|
|
48
|
+
performance: analytics
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export const createOneTrustConsentProvider = () => ({
|
|
52
|
+
name: 'onetrust',
|
|
53
|
+
read: readSnapshot,
|
|
54
|
+
subscribe(onChange) {
|
|
55
|
+
const publish = () => onChange(readSnapshot());
|
|
56
|
+
if (typeof window === 'undefined') {
|
|
57
|
+
return () => {};
|
|
58
|
+
}
|
|
59
|
+
// OneTrust fires this on every banner interaction.
|
|
60
|
+
window.addEventListener('OneTrustGroupsUpdated', publish);
|
|
61
|
+
return () => {
|
|
62
|
+
window.removeEventListener('OneTrustGroupsUpdated', publish);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=oneTrustProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ANALYTICS_CATEGORY","MARKETING_CATEGORY","readGroups","window","groups","OnetrustActiveGroups","isOneTrustHost","readSnapshot","console","log","analytics","marketing","performance","includes","createOneTrustConsentProvider","name","read","subscribe","onChange","publish","addEventListener","removeEventListener"],"sources":["../../../../src/privacy/providers/oneTrustProvider.ts"],"sourcesContent":["/**\n * OneTrust provider — DL #218.\n *\n * Lifted from the consent check that used to live inside\n * `utils/analyticsEvents.ts`, with one deliberate behaviour change: that\n * function returned `true` when OneTrust was absent (\"host is responsible for\n * loading OneTrust\"), which on a Shopify storefront meant unconditional\n * default-allow. Absence is no longer this provider's problem — it is only\n * selected when OneTrust is actually on the page, and absence is handled by\n * the gate's own default.\n */\n\nimport type { ConsentProvider, ConsentSnapshot } from '../types';\n\n/** OneTrust's default taxonomy: C0002 is the performance/analytics category. */\nconst ANALYTICS_CATEGORY = 'C0002';\n/** C0004 is targeting/advertising. */\nconst MARKETING_CATEGORY = 'C0004';\n\nconst readGroups = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n const groups = (window as unknown as { OnetrustActiveGroups?: unknown })\n .OnetrustActiveGroups;\n return typeof groups === 'string' ? groups : null;\n};\n\nexport const isOneTrustHost = (): boolean => readGroups() !== null;\n\nconst readSnapshot = (): ConsentSnapshot => {\n const groups = readGroups();\n if (groups !== null) {\n try {\n console.log(`[w5-consent] onetrust: active groups \"${groups}\"`);\n } catch {\n /* never break on a console */\n }\n }\n if (groups === null) {\n return { analytics: 'unknown', marketing: 'unknown', performance: 'unknown' };\n }\n // OneTrust publishes the *active* groups, so a category that is absent from\n // a string OneTrust has written is a refusal, not silence.\n const analytics = groups.includes(ANALYTICS_CATEGORY) ? 'granted' : 'denied';\n const marketing = groups.includes(MARKETING_CATEGORY) ? 'granted' : 'denied';\n return { analytics, marketing, performance: analytics };\n};\n\nexport const createOneTrustConsentProvider = (): ConsentProvider => ({\n name: 'onetrust',\n\n read: readSnapshot,\n\n subscribe(onChange) {\n const publish = () => onChange(readSnapshot());\n if (typeof window === 'undefined') {\n return () => {};\n }\n // OneTrust fires this on every banner interaction.\n window.addEventListener('OneTrustGroupsUpdated', publish);\n return () => {\n window.removeEventListener('OneTrustGroupsUpdated', publish);\n };\n },\n});\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIA;AACA,MAAMA,kBAAkB,GAAG,OAAO;AAClC;AACA,MAAMC,kBAAkB,GAAG,OAAO;AAElC,MAAMC,UAAU,GAAGA,CAAA,KAAqB;EACtC,IAAI,OAAOC,MAAM,KAAK,WAAW,EAAE;IACjC,OAAO,IAAI;EACb;EACA,MAAMC,MAAM,GAAID,MAAM,CACnBE,oBAAoB;EACvB,OAAO,OAAOD,MAAM,KAAK,QAAQ,GAAGA,MAAM,GAAG,IAAI;AACnD,CAAC;AAED,OAAO,MAAME,cAAc,GAAGA,CAAA,KAAeJ,UAAU,CAAC,CAAC,KAAK,IAAI;AAElE,MAAMK,YAAY,GAAGA,CAAA,KAAuB;EAC1C,MAAMH,MAAM,GAAGF,UAAU,CAAC,CAAC;EAC3B,IAAIE,MAAM,KAAK,IAAI,EAAE;IACnB,IAAI;MACFI,OAAO,CAACC,GAAG,CAAC,yCAAyCL,MAAM,GAAG,CAAC;IACjE,CAAC,CAAC,MAAM;MACN;IAAA;EAEJ;EACA,IAAIA,MAAM,KAAK,IAAI,EAAE;IACnB,OAAO;MAAEM,SAAS,EAAE,SAAS;MAAEC,SAAS,EAAE,SAAS;MAAEC,WAAW,EAAE;IAAU,CAAC;EAC/E;EACA;EACA;EACA,MAAMF,SAAS,GAAGN,MAAM,CAACS,QAAQ,CAACb,kBAAkB,CAAC,GAAG,SAAS,GAAG,QAAQ;EAC5E,MAAMW,SAAS,GAAGP,MAAM,CAACS,QAAQ,CAACZ,kBAAkB,CAAC,GAAG,SAAS,GAAG,QAAQ;EAC5E,OAAO;IAAES,SAAS;IAAEC,SAAS;IAAEC,WAAW,EAAEF;EAAU,CAAC;AACzD,CAAC;AAED,OAAO,MAAMI,6BAA6B,GAAGA,CAAA,MAAwB;EACnEC,IAAI,EAAE,UAAU;EAEhBC,IAAI,EAAET,YAAY;EAElBU,SAASA,CAACC,QAAQ,EAAE;IAClB,MAAMC,OAAO,GAAGA,CAAA,KAAMD,QAAQ,CAACX,YAAY,CAAC,CAAC,CAAC;IAC9C,IAAI,OAAOJ,MAAM,KAAK,WAAW,EAAE;MACjC,OAAO,MAAM,CAAC,CAAC;IACjB;IACA;IACAA,MAAM,CAACiB,gBAAgB,CAAC,uBAAuB,EAAED,OAAO,CAAC;IACzD,OAAO,MAAM;MACXhB,MAAM,CAACkB,mBAAmB,CAAC,uBAAuB,EAAEF,OAAO,CAAC;IAC9D,CAAC;EACH;AACF,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shopify Customer Privacy API provider — DL #218.
|
|
3
|
+
*
|
|
4
|
+
* Two things about this API shape the provider:
|
|
5
|
+
*
|
|
6
|
+
* 1. **It is not on the page by default.** `window.Shopify.customerPrivacy` is
|
|
7
|
+
* `undefined` until somebody asks for it with `Shopify.loadFeatures`. The
|
|
8
|
+
* theme app extension warms it so it is ready before the bundle mounts;
|
|
9
|
+
* this provider still requests it, because the extension may not be
|
|
10
|
+
* installed and the request is idempotent.
|
|
11
|
+
*
|
|
12
|
+
* 2. **`visitorConsentCollected` may have already fired.** The bundle executes
|
|
13
|
+
* into an already-rendered storefront page (DL #217), so `read()` answers
|
|
14
|
+
* from current state and the event is only ever an update.
|
|
15
|
+
*
|
|
16
|
+
* The region rule is the one judgement encoded here, and it keys off
|
|
17
|
+
* `isRegulationEnforced()` rather than `shouldShowBanner()`. The two are not
|
|
18
|
+
* interchangeable: the banner reflects what the *merchant* configured, while
|
|
19
|
+
* enforcement reflects what the *visitor's jurisdiction* requires. A store with
|
|
20
|
+
* an empty consent configuration reports "no banner needed" everywhere,
|
|
21
|
+
* including inside the EU.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { UNKNOWN_CONSENT } from '../types.js';
|
|
25
|
+
const CONSENT_FEATURE = {
|
|
26
|
+
name: 'consent-tracking-api',
|
|
27
|
+
version: '0.1'
|
|
28
|
+
};
|
|
29
|
+
const log = function () {
|
|
30
|
+
try {
|
|
31
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
32
|
+
args[_key] = arguments[_key];
|
|
33
|
+
}
|
|
34
|
+
console.log('[w5-consent] shopify:', ...args);
|
|
35
|
+
} catch {
|
|
36
|
+
/* never break on a console */
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const getShopify = () => {
|
|
40
|
+
if (typeof window === 'undefined') {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return window.Shopify ?? null;
|
|
44
|
+
};
|
|
45
|
+
export const isShopifyHost = () => getShopify() !== null;
|
|
46
|
+
const toState = value => {
|
|
47
|
+
if (value === 'yes') return 'granted';
|
|
48
|
+
if (value === 'no') return 'denied';
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
const readSnapshot = () => {
|
|
52
|
+
var _getShopify;
|
|
53
|
+
const privacy = (_getShopify = getShopify()) == null ? void 0 : _getShopify.customerPrivacy;
|
|
54
|
+
if (!privacy) {
|
|
55
|
+
log('customerPrivacy API not on the page — no answer available (store has no privacy configuration, or loadFeatures has not resolved yet)');
|
|
56
|
+
// The API has not loaded (or the store has no privacy configuration at
|
|
57
|
+
// all). Not an answer — the gate holds, and the visitor's own action is
|
|
58
|
+
// what unlocks the session.
|
|
59
|
+
return UNKNOWN_CONSENT;
|
|
60
|
+
}
|
|
61
|
+
let consent = {};
|
|
62
|
+
try {
|
|
63
|
+
consent = (privacy.currentVisitorConsent == null ? void 0 : privacy.currentVisitorConsent()) ?? {};
|
|
64
|
+
} catch {
|
|
65
|
+
// Present but not ready — treat as no answer yet.
|
|
66
|
+
}
|
|
67
|
+
const explicitAnalytics = toState(consent.analytics);
|
|
68
|
+
const explicitMarketing = toState(consent.marketing);
|
|
69
|
+
let allowed = false;
|
|
70
|
+
try {
|
|
71
|
+
allowed = (privacy.analyticsProcessingAllowed == null ? void 0 : privacy.analyticsProcessingAllowed()) === true;
|
|
72
|
+
} catch {
|
|
73
|
+
allowed = false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Is a privacy regulation in force for *this visitor's* region? This is the
|
|
77
|
+
// question that matters, and it is not the same question as "is a banner
|
|
78
|
+
// being shown" — see the comment on `enforced` below.
|
|
79
|
+
let enforced = null;
|
|
80
|
+
let regulation = '';
|
|
81
|
+
let region = '';
|
|
82
|
+
try {
|
|
83
|
+
if (typeof privacy.isRegulationEnforced === 'function') {
|
|
84
|
+
enforced = privacy.isRegulationEnforced() === true;
|
|
85
|
+
}
|
|
86
|
+
regulation = (privacy.getRegulation == null ? void 0 : privacy.getRegulation()) ?? '';
|
|
87
|
+
region = (privacy.getRegion == null ? void 0 : privacy.getRegion()) ?? '';
|
|
88
|
+
} catch {
|
|
89
|
+
enforced = null;
|
|
90
|
+
}
|
|
91
|
+
let bannerRequired = true;
|
|
92
|
+
try {
|
|
93
|
+
bannerRequired = (privacy.shouldShowBanner == null ? void 0 : privacy.shouldShowBanner()) !== false;
|
|
94
|
+
} catch {
|
|
95
|
+
bannerRequired = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* Measured on a live store from a Frankfurt IP, with no consent recorded:
|
|
100
|
+
*
|
|
101
|
+
* region 'DEBE' · regulation 'GDPR' · isRegulationEnforced() true
|
|
102
|
+
* shouldShowBanner() false · analyticsProcessingAllowed() TRUE
|
|
103
|
+
* getShopPrefs() { limit: [] }
|
|
104
|
+
*
|
|
105
|
+
* Shopify said tracking was allowed for a GDPR-protected visitor who had
|
|
106
|
+
* never been asked, because the *merchant* had configured no consent
|
|
107
|
+
* preferences. `shouldShowBanner()` and `analyticsProcessingAllowed()` both
|
|
108
|
+
* describe the merchant's setup; neither is a statement about a legal basis.
|
|
109
|
+
* A merchant's misconfiguration must not become our tracking decision, so
|
|
110
|
+
* under an enforced regulation an absent answer stays `unknown` and the gate
|
|
111
|
+
* holds — the visitor's own prompt is then the only thing that unlocks them.
|
|
112
|
+
*/
|
|
113
|
+
const permissiveDefault = allowed || !bannerRequired;
|
|
114
|
+
const fallbackWhenUnenforceable = permissiveDefault ? 'granted' : 'unknown';
|
|
115
|
+
const analytics = explicitAnalytics ?? (enforced === true ? 'unknown' : fallbackWhenUnenforceable);
|
|
116
|
+
const marketing = explicitMarketing ?? (enforced === true ? 'unknown' : !bannerRequired ? 'granted' : 'unknown');
|
|
117
|
+
log(`read — visitorConsent.analytics=${consent.analytics ?? '(unset)'} ` + `analyticsProcessingAllowed=${allowed} bannerRequired=${bannerRequired} ` + `region=${region || '?'} regulation=${regulation || '?'} enforced=${enforced === null ? 'unavailable' : enforced}` + (enforced === true && explicitAnalytics === null ? ' → regulated and unanswered, holding' : '') + ` ⇒ ${analytics}`);
|
|
118
|
+
return {
|
|
119
|
+
analytics,
|
|
120
|
+
marketing,
|
|
121
|
+
// Shopify has no separate performance bucket. Load telemetry carries an
|
|
122
|
+
// app name and a session id to a Wix endpoint, so it answers to the same
|
|
123
|
+
// answer analytics does rather than riding for free.
|
|
124
|
+
performance: analytics
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Ask Shopify to load the consent API if it is not already there. Safe to call
|
|
130
|
+
* more than once; the callback re-reads whatever state arrives.
|
|
131
|
+
*/
|
|
132
|
+
const requestConsentApi = onReady => {
|
|
133
|
+
const shopify = getShopify();
|
|
134
|
+
if (!shopify || shopify.customerPrivacy || !shopify.loadFeatures) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
log('requesting consent-tracking-api via Shopify.loadFeatures…');
|
|
139
|
+
shopify.loadFeatures([CONSENT_FEATURE], error => {
|
|
140
|
+
if (error) {
|
|
141
|
+
log('loadFeatures failed — gate stays held', error);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
log('loadFeatures resolved — re-reading consent');
|
|
145
|
+
onReady();
|
|
146
|
+
});
|
|
147
|
+
} catch {
|
|
148
|
+
// Storefronts without the feature simply never resolve; the gate stays
|
|
149
|
+
// held and engagement remains the only unlock.
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
export const createShopifyConsentProvider = () => ({
|
|
153
|
+
name: 'shopify-customer-privacy',
|
|
154
|
+
read: readSnapshot,
|
|
155
|
+
subscribe(onChange) {
|
|
156
|
+
const publish = () => {
|
|
157
|
+
log('visitorConsentCollected — the visitor answered the banner');
|
|
158
|
+
onChange(readSnapshot());
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// The visitor may have answered the banner before this bundle existed, so
|
|
162
|
+
// the event is an update — never the first read.
|
|
163
|
+
if (typeof document !== 'undefined') {
|
|
164
|
+
document.addEventListener('visitorConsentCollected', publish);
|
|
165
|
+
}
|
|
166
|
+
requestConsentApi(() => onChange(readSnapshot()));
|
|
167
|
+
return () => {
|
|
168
|
+
if (typeof document !== 'undefined') {
|
|
169
|
+
document.removeEventListener('visitorConsentCollected', publish);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
//# sourceMappingURL=shopifyProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["UNKNOWN_CONSENT","CONSENT_FEATURE","name","version","log","_len","arguments","length","args","Array","_key","console","getShopify","window","Shopify","isShopifyHost","toState","value","readSnapshot","_getShopify","privacy","customerPrivacy","consent","currentVisitorConsent","explicitAnalytics","analytics","explicitMarketing","marketing","allowed","analyticsProcessingAllowed","enforced","regulation","region","isRegulationEnforced","getRegulation","getRegion","bannerRequired","shouldShowBanner","permissiveDefault","fallbackWhenUnenforceable","performance","requestConsentApi","onReady","shopify","loadFeatures","error","createShopifyConsentProvider","read","subscribe","onChange","publish","document","addEventListener","removeEventListener"],"sources":["../../../../src/privacy/providers/shopifyProvider.ts"],"sourcesContent":["/**\n * Shopify Customer Privacy API provider — DL #218.\n *\n * Two things about this API shape the provider:\n *\n * 1. **It is not on the page by default.** `window.Shopify.customerPrivacy` is\n * `undefined` until somebody asks for it with `Shopify.loadFeatures`. The\n * theme app extension warms it so it is ready before the bundle mounts;\n * this provider still requests it, because the extension may not be\n * installed and the request is idempotent.\n *\n * 2. **`visitorConsentCollected` may have already fired.** The bundle executes\n * into an already-rendered storefront page (DL #217), so `read()` answers\n * from current state and the event is only ever an update.\n *\n * The region rule is the one judgement encoded here, and it keys off\n * `isRegulationEnforced()` rather than `shouldShowBanner()`. The two are not\n * interchangeable: the banner reflects what the *merchant* configured, while\n * enforcement reflects what the *visitor's jurisdiction* requires. A store with\n * an empty consent configuration reports \"no banner needed\" everywhere,\n * including inside the EU.\n */\n\nimport {\n UNKNOWN_CONSENT,\n type ConsentProvider,\n type ConsentSnapshot,\n type ConsentState,\n} from '../types';\n\ntype ShopifyConsentValue = 'yes' | 'no' | '' | undefined;\n\ninterface ShopifyVisitorConsent {\n analytics?: ShopifyConsentValue;\n marketing?: ShopifyConsentValue;\n preferences?: ShopifyConsentValue;\n sale_of_data?: ShopifyConsentValue;\n}\n\ninterface ShopifyCustomerPrivacy {\n analyticsProcessingAllowed?: () => boolean;\n marketingAllowed?: () => boolean;\n currentVisitorConsent?: () => ShopifyVisitorConsent;\n shouldShowBanner?: () => boolean;\n /** Whether a privacy regulation applies to this visitor's region. */\n isRegulationEnforced?: () => boolean;\n /** e.g. 'GDPR', 'CCPA'. */\n getRegulation?: () => string;\n /** e.g. 'DEBE' — country + subdivision. */\n getRegion?: () => string;\n}\n\ninterface ShopifyGlobal {\n customerPrivacy?: ShopifyCustomerPrivacy;\n loadFeatures?: (\n features: { name: string; version: string }[],\n callback: (error?: unknown) => void,\n ) => void;\n}\n\nconst CONSENT_FEATURE = { name: 'consent-tracking-api', version: '0.1' };\n\nconst log = (...args: unknown[]): void => {\n try {\n console.log('[w5-consent] shopify:', ...args);\n } catch {\n /* never break on a console */\n }\n};\n\nconst getShopify = (): ShopifyGlobal | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n return (window as unknown as { Shopify?: ShopifyGlobal }).Shopify ?? null;\n};\n\nexport const isShopifyHost = (): boolean => getShopify() !== null;\n\nconst toState = (value: ShopifyConsentValue): ConsentState | null => {\n if (value === 'yes') return 'granted';\n if (value === 'no') return 'denied';\n return null;\n};\n\nconst readSnapshot = (): ConsentSnapshot => {\n const privacy = getShopify()?.customerPrivacy;\n if (!privacy) {\n log(\n 'customerPrivacy API not on the page — no answer available (store has no privacy configuration, or loadFeatures has not resolved yet)',\n );\n // The API has not loaded (or the store has no privacy configuration at\n // all). Not an answer — the gate holds, and the visitor's own action is\n // what unlocks the session.\n return UNKNOWN_CONSENT;\n }\n\n let consent: ShopifyVisitorConsent = {};\n try {\n consent = privacy.currentVisitorConsent?.() ?? {};\n } catch {\n // Present but not ready — treat as no answer yet.\n }\n\n const explicitAnalytics = toState(consent.analytics);\n const explicitMarketing = toState(consent.marketing);\n\n let allowed = false;\n try {\n allowed = privacy.analyticsProcessingAllowed?.() === true;\n } catch {\n allowed = false;\n }\n\n // Is a privacy regulation in force for *this visitor's* region? This is the\n // question that matters, and it is not the same question as \"is a banner\n // being shown\" — see the comment on `enforced` below.\n let enforced: boolean | null = null;\n let regulation = '';\n let region = '';\n try {\n if (typeof privacy.isRegulationEnforced === 'function') {\n enforced = privacy.isRegulationEnforced() === true;\n }\n regulation = privacy.getRegulation?.() ?? '';\n region = privacy.getRegion?.() ?? '';\n } catch {\n enforced = null;\n }\n\n let bannerRequired = true;\n try {\n bannerRequired = privacy.shouldShowBanner?.() !== false;\n } catch {\n bannerRequired = true;\n }\n\n /*\n * Measured on a live store from a Frankfurt IP, with no consent recorded:\n *\n * region 'DEBE' · regulation 'GDPR' · isRegulationEnforced() true\n * shouldShowBanner() false · analyticsProcessingAllowed() TRUE\n * getShopPrefs() { limit: [] }\n *\n * Shopify said tracking was allowed for a GDPR-protected visitor who had\n * never been asked, because the *merchant* had configured no consent\n * preferences. `shouldShowBanner()` and `analyticsProcessingAllowed()` both\n * describe the merchant's setup; neither is a statement about a legal basis.\n * A merchant's misconfiguration must not become our tracking decision, so\n * under an enforced regulation an absent answer stays `unknown` and the gate\n * holds — the visitor's own prompt is then the only thing that unlocks them.\n */\n const permissiveDefault = allowed || !bannerRequired;\n const fallbackWhenUnenforceable = permissiveDefault ? 'granted' : 'unknown';\n\n const analytics: ConsentState =\n explicitAnalytics ??\n (enforced === true ? 'unknown' : fallbackWhenUnenforceable);\n\n const marketing: ConsentState =\n explicitMarketing ??\n (enforced === true ? 'unknown' : !bannerRequired ? 'granted' : 'unknown');\n\n log(\n `read — visitorConsent.analytics=${consent.analytics ?? '(unset)'} ` +\n `analyticsProcessingAllowed=${allowed} bannerRequired=${bannerRequired} ` +\n `region=${region || '?'} regulation=${regulation || '?'} enforced=${\n enforced === null ? 'unavailable' : enforced\n }` +\n (enforced === true && explicitAnalytics === null\n ? ' → regulated and unanswered, holding'\n : '') +\n ` ⇒ ${analytics}`,\n );\n\n return {\n analytics,\n marketing,\n // Shopify has no separate performance bucket. Load telemetry carries an\n // app name and a session id to a Wix endpoint, so it answers to the same\n // answer analytics does rather than riding for free.\n performance: analytics,\n };\n};\n\n/**\n * Ask Shopify to load the consent API if it is not already there. Safe to call\n * more than once; the callback re-reads whatever state arrives.\n */\nconst requestConsentApi = (onReady: () => void): void => {\n const shopify = getShopify();\n if (!shopify || shopify.customerPrivacy || !shopify.loadFeatures) {\n return;\n }\n try {\n log('requesting consent-tracking-api via Shopify.loadFeatures…');\n shopify.loadFeatures([CONSENT_FEATURE], (error) => {\n if (error) {\n log('loadFeatures failed — gate stays held', error);\n return;\n }\n log('loadFeatures resolved — re-reading consent');\n onReady();\n });\n } catch {\n // Storefronts without the feature simply never resolve; the gate stays\n // held and engagement remains the only unlock.\n }\n};\n\nexport const createShopifyConsentProvider = (): ConsentProvider => ({\n name: 'shopify-customer-privacy',\n\n read: readSnapshot,\n\n subscribe(onChange) {\n const publish = () => {\n log('visitorConsentCollected — the visitor answered the banner');\n onChange(readSnapshot());\n };\n\n // The visitor may have answered the banner before this bundle existed, so\n // the event is an update — never the first read.\n if (typeof document !== 'undefined') {\n document.addEventListener('visitorConsentCollected', publish);\n }\n requestConsentApi(() => onChange(readSnapshot()));\n\n return () => {\n if (typeof document !== 'undefined') {\n document.removeEventListener('visitorConsentCollected', publish);\n }\n };\n },\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent vocabulary shared by the gate and every provider.
|
|
3
|
+
*
|
|
4
|
+
* DL #218. Three states, because "we have not been told" is not the same
|
|
5
|
+
* answer as "no" and must not be collapsed into one: a visitor who has not
|
|
6
|
+
* been asked can still be unlocked by their own action, a visitor who
|
|
7
|
+
* refused never can.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `necessary` is never gated — auth tokens and the configuration fetch are
|
|
12
|
+
* what make the widget work at all, and a visitor who loaded the page asked
|
|
13
|
+
* for that much. Everything else answers to the gate.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const UNKNOWN_CONSENT = Object.freeze({
|
|
17
|
+
performance: 'unknown',
|
|
18
|
+
analytics: 'unknown',
|
|
19
|
+
marketing: 'unknown'
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A consent provider adapts one host's CMP to the gate.
|
|
24
|
+
*
|
|
25
|
+
* `read()` must answer synchronously with what is known *now*. Providers are
|
|
26
|
+
* installed into an already-rendered storefront page (DL #217), so a provider
|
|
27
|
+
* that only subscribes is inert — the event it waits for may have fired long
|
|
28
|
+
* before the bundle executed. Always read first, then subscribe.
|
|
29
|
+
*/
|
|
30
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["UNKNOWN_CONSENT","Object","freeze","performance","analytics","marketing"],"sources":["../../../src/privacy/types.ts"],"sourcesContent":["/**\n * Consent vocabulary shared by the gate and every provider.\n *\n * DL #218. Three states, because \"we have not been told\" is not the same\n * answer as \"no\" and must not be collapsed into one: a visitor who has not\n * been asked can still be unlocked by their own action, a visitor who\n * refused never can.\n */\n\nexport type ConsentState = 'granted' | 'denied' | 'unknown';\n\n/**\n * `necessary` is never gated — auth tokens and the configuration fetch are\n * what make the widget work at all, and a visitor who loaded the page asked\n * for that much. Everything else answers to the gate.\n */\nexport type ConsentPurpose =\n | 'necessary'\n | 'performance'\n | 'analytics'\n | 'marketing';\n\nexport type GatedPurpose = Exclude<ConsentPurpose, 'necessary'>;\n\nexport type ConsentSnapshot = Record<GatedPurpose, ConsentState>;\n\nexport const UNKNOWN_CONSENT: ConsentSnapshot = Object.freeze({\n performance: 'unknown',\n analytics: 'unknown',\n marketing: 'unknown',\n});\n\n/**\n * A consent provider adapts one host's CMP to the gate.\n *\n * `read()` must answer synchronously with what is known *now*. Providers are\n * installed into an already-rendered storefront page (DL #217), so a provider\n * that only subscribes is inert — the event it waits for may have fired long\n * before the bundle executed. Always read first, then subscribe.\n */\nexport interface ConsentProvider {\n readonly name: string;\n read(): ConsentSnapshot;\n subscribe(onChange: (snapshot: ConsentSnapshot) => void): () => void;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIA;AACA;AACA;AACA;AACA;;AAWA,OAAO,MAAMA,eAAgC,GAAGC,MAAM,CAACC,MAAM,CAAC;EAC5DC,WAAW,EAAE,SAAS;EACtBC,SAAS,EAAE,SAAS;EACpBC,SAAS,EAAE;AACb,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA;AACA;AACA","ignoreList":[]}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import { mayTransmit, transmit } from '../privacy/consentGate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether analytics may go on the wire right now.
|
|
4
|
+
*
|
|
5
|
+
* DL #218: this used to read OneTrust directly and return `true` when OneTrust
|
|
6
|
+
* was absent, which on a Shopify storefront — where no host loads OneTrust —
|
|
7
|
+
* meant unconditional default-allow. It now defers to the consent gate, which
|
|
8
|
+
* reads whichever CMP the host actually has and, in the absence of any answer,
|
|
9
|
+
* holds until the visitor does something deliberate.
|
|
10
|
+
*/
|
|
2
11
|
export function hasAnalyticsConsent() {
|
|
3
12
|
if (typeof window === 'undefined') {
|
|
4
13
|
return false;
|
|
5
14
|
}
|
|
6
|
-
|
|
7
|
-
if (typeof groups === 'string') {
|
|
8
|
-
return groups.includes(ANALYTICS_CATEGORY);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// No OneTrust on page — allow tracking (host is responsible for loading OneTrust)
|
|
12
|
-
return true;
|
|
15
|
+
return mayTransmit('analytics');
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -26,26 +29,28 @@ export function pushEvent(eventName, params) {
|
|
|
26
29
|
if (typeof window === 'undefined') {
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
29
|
-
if (!hasAnalyticsConsent()) {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
32
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
// DL #218: routed through the gate rather than checked against it, so an
|
|
34
|
+
// event raised before consent resolves is held and replayed rather than
|
|
35
|
+
// dropped on the floor.
|
|
36
|
+
transmit('analytics', () => {
|
|
37
|
+
// GTM mode
|
|
38
|
+
if (window.google_tag_manager && window.dataLayer) {
|
|
39
|
+
window.dataLayer.push({
|
|
40
|
+
event: eventName,
|
|
41
|
+
...params
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
// Standalone gtag.js mode
|
|
47
|
+
if (typeof window.gtag === 'function') {
|
|
48
|
+
window.gtag('event', eventName, params);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
// No analytics SDK loaded — no-op
|
|
53
|
+
}, `ga ${eventName}`);
|
|
49
54
|
}
|
|
50
55
|
export function pushPromptSubmit(promptType, promptText) {
|
|
51
56
|
pushEvent('web5_prompt_submit', {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["mayTransmit","transmit","hasAnalyticsConsent","window","pushEvent","eventName","params","google_tag_manager","dataLayer","push","event","gtag","pushPromptSubmit","promptType","promptText","prompt_type","prompt_text","pushLinkClick","linkUrl","linkType","linkText","link_url","link_type","link_text","pushError","errorType","statusCode","errorMessage","error_type","status_code","error_message","pushExit","exitUrl","exitType","exit_url","exit_type","pushEntityFiltered","reason","entityType","entityId","entity_type","entity_id"],"sources":["../../../src/utils/analyticsEvents.ts"],"sourcesContent":["import { mayTransmit, transmit } from '../privacy/consentGate';\n\ndeclare global {\n interface Window {\n dataLayer?: Record<string, unknown>[];\n gtag?: (...args: unknown[]) => void;\n google_tag_manager?: Record<string, unknown>;\n OnetrustActiveGroups?: string;\n }\n}\n\n/**\n * Whether analytics may go on the wire right now.\n *\n * DL #218: this used to read OneTrust directly and return `true` when OneTrust\n * was absent, which on a Shopify storefront — where no host loads OneTrust —\n * meant unconditional default-allow. It now defers to the consent gate, which\n * reads whichever CMP the host actually has and, in the absence of any answer,\n * holds until the visitor does something deliberate.\n */\nexport function hasAnalyticsConsent(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n return mayTransmit('analytics');\n}\n\n/**\n * Universal event dispatch: auto-detects GTM vs standalone gtag.js.\n *\n * GTM is checked first via `google_tag_manager` because when GTM has a GA4\n * tag configured it also creates `window.gtag`. Using dataLayer.push()\n * ensures GTM triggers/tags still process the event.\n */\nexport function pushEvent(\n eventName: string,\n params: Record<string, unknown> = {},\n): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // DL #218: routed through the gate rather than checked against it, so an\n // event raised before consent resolves is held and replayed rather than\n // dropped on the floor.\n transmit(\n 'analytics',\n () => {\n // GTM mode\n if (window.google_tag_manager && window.dataLayer) {\n window.dataLayer.push({ event: eventName, ...params });\n return;\n }\n\n // Standalone gtag.js mode\n if (typeof window.gtag === 'function') {\n window.gtag('event', eventName, params);\n return;\n }\n\n // No analytics SDK loaded — no-op\n },\n `ga ${eventName}`,\n );\n}\n\nexport function pushPromptSubmit(\n promptType: 'text' | 'chip',\n promptText: string,\n): void {\n pushEvent('web5_prompt_submit', {\n prompt_type: promptType,\n prompt_text: promptText,\n });\n}\n\nexport function pushLinkClick(\n linkUrl: string,\n linkType: 'web5' | 'external' | 'internal',\n linkText?: string,\n): void {\n pushEvent('web5_link_click', {\n link_url: linkUrl,\n link_type: linkType,\n link_text: linkText ?? '',\n });\n}\n\nexport function pushError(\n errorType: string,\n statusCode: number,\n errorMessage?: string,\n): void {\n pushEvent('web5_error', {\n error_type: errorType,\n status_code: statusCode,\n error_message: errorMessage ?? '',\n });\n}\n\nexport function pushExit(\n exitUrl: string,\n linkText: string,\n exitType: 'external' | 'internal',\n): void {\n pushEvent('web5_exit', {\n exit_url: exitUrl,\n link_text: linkText,\n exit_type: exitType,\n });\n}\n\nexport type EntityFilteredReason = 'no_data' | 'no_url';\n\nexport function pushEntityFiltered(\n reason: EntityFilteredReason,\n entityType: string,\n entityId: string,\n): void {\n pushEvent('web5_entity_filtered', {\n reason,\n entity_type: entityType,\n entity_id: entityId,\n });\n}\n"],"mappings":"AAAA,SAASA,WAAW,EAAEC,QAAQ,QAAQ,wBAAwB;AAW9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CAAA,EAAY;EAC7C,IAAI,OAAOC,MAAM,KAAK,WAAW,EAAE;IACjC,OAAO,KAAK;EACd;EACA,OAAOH,WAAW,CAAC,WAAW,CAAC;AACjC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASI,SAASA,CACvBC,SAAiB,EACjBC,MAA+B,EACzB;EAAA,IADNA,MAA+B;IAA/BA,MAA+B,GAAG,CAAC,CAAC;EAAA;EAEpC,IAAI,OAAOH,MAAM,KAAK,WAAW,EAAE;IACjC;EACF;;EAEA;EACA;EACA;EACAF,QAAQ,CACN,WAAW,EACX,MAAM;IACN;IACA,IAAIE,MAAM,CAACI,kBAAkB,IAAIJ,MAAM,CAACK,SAAS,EAAE;MACjDL,MAAM,CAACK,SAAS,CAACC,IAAI,CAAC;QAAEC,KAAK,EAAEL,SAAS;QAAE,GAAGC;MAAO,CAAC,CAAC;MACtD;IACF;;IAEA;IACA,IAAI,OAAOH,MAAM,CAACQ,IAAI,KAAK,UAAU,EAAE;MACrCR,MAAM,CAACQ,IAAI,CAAC,OAAO,EAAEN,SAAS,EAAEC,MAAM,CAAC;MACvC;IACF;;IAEA;EACA,CAAC,EACD,MAAMD,SAAS,EACjB,CAAC;AACH;AAEA,OAAO,SAASO,gBAAgBA,CAC9BC,UAA2B,EAC3BC,UAAkB,EACZ;EACNV,SAAS,CAAC,oBAAoB,EAAE;IAC9BW,WAAW,EAAEF,UAAU;IACvBG,WAAW,EAAEF;EACf,CAAC,CAAC;AACJ;AAEA,OAAO,SAASG,aAAaA,CAC3BC,OAAe,EACfC,QAA0C,EAC1CC,QAAiB,EACX;EACNhB,SAAS,CAAC,iBAAiB,EAAE;IAC3BiB,QAAQ,EAAEH,OAAO;IACjBI,SAAS,EAAEH,QAAQ;IACnBI,SAAS,EAAEH,QAAQ,IAAI;EACzB,CAAC,CAAC;AACJ;AAEA,OAAO,SAASI,SAASA,CACvBC,SAAiB,EACjBC,UAAkB,EAClBC,YAAqB,EACf;EACNvB,SAAS,CAAC,YAAY,EAAE;IACtBwB,UAAU,EAAEH,SAAS;IACrBI,WAAW,EAAEH,UAAU;IACvBI,aAAa,EAAEH,YAAY,IAAI;EACjC,CAAC,CAAC;AACJ;AAEA,OAAO,SAASI,QAAQA,CACtBC,OAAe,EACfZ,QAAgB,EAChBa,QAAiC,EAC3B;EACN7B,SAAS,CAAC,WAAW,EAAE;IACrB8B,QAAQ,EAAEF,OAAO;IACjBT,SAAS,EAAEH,QAAQ;IACnBe,SAAS,EAAEF;EACb,CAAC,CAAC;AACJ;AAIA,OAAO,SAASG,kBAAkBA,CAChCC,MAA4B,EAC5BC,UAAkB,EAClBC,QAAgB,EACV;EACNnC,SAAS,CAAC,sBAAsB,EAAE;IAChCiC,MAAM;IACNG,WAAW,EAAEF,UAAU;IACvBG,SAAS,EAAEF;EACb,CAAC,CAAC;AACJ","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextBlockSectionDefinition.d.ts","sourceRoot":"","sources":["../../../../src/component/componentDefinitions/TextBlockSectionDefinition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,KAAK,EACV,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACf,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG7E,qBAAa,0BACX,YAAW,iBAAiB,CAAC,yBAAyB,CAAC;IAEvD,WAAW,cAAwB;
|
|
1
|
+
{"version":3,"file":"TextBlockSectionDefinition.d.ts","sourceRoot":"","sources":["../../../../src/component/componentDefinitions/TextBlockSectionDefinition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,KAAK,EACV,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACf,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG7E,qBAAa,0BACX,YAAW,iBAAiB,CAAC,yBAAyB,CAAC;IAEvD,WAAW,cAAwB;IAkCnC,QAAQ,WAAwC;IAEhD,QAAQ,EAAE,kBAAkB,CAG1B;IAEF,QAAQ,EAAE,cAAc,CAAC,kBAAkB,CAAC,EAAE,CAuC5C;IAEF,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,kBAAkB,GAAG,IAAI;CAcxE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/component/componentDefinitions/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,iCAAiC,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAExE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAoBnD;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/component/componentDefinitions/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,iCAAiC,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAExE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAoBnD;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAqC3D"}
|
|
@@ -7,6 +7,27 @@ export declare function headingText(parts: Part[], level?: number): string;
|
|
|
7
7
|
/** Like headingText but preserves inline markdown formatting (bold, italic, etc.) */
|
|
8
8
|
export declare function headingMarkdown(parts: Part[], level?: number): string;
|
|
9
9
|
export declare function bodyText(parts: Part[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Body copy as markdown, with inline links intact and paragraph breaks kept.
|
|
12
|
+
*
|
|
13
|
+
* `bodyText` reads text parts only, so a paragraph carrying an inline link —
|
|
14
|
+
* `Try narrowing to [men](web5://ask/…) instead` — arrives as `t + link + t`
|
|
15
|
+
* and comes back as `"Try narrowing to\n\ninstead"`: label and url both
|
|
16
|
+
* deleted, and the sentence broken in two at the gap. That is DL #134's list
|
|
17
|
+
* bug in a different helper, and it is why an ask link in prose never reached
|
|
18
|
+
* the page.
|
|
19
|
+
*
|
|
20
|
+
* `inlinePartsToMarkdown` already rebuilds a run of inline parts with links
|
|
21
|
+
* intact, but it flattens to one line, which is wrong for multi-paragraph
|
|
22
|
+
* copy. So group first: `astToParts` marks every part after the first of a
|
|
23
|
+
* given AST node with `meta.continuesNode`, which is exactly "same paragraph,
|
|
24
|
+
* keep going". Parts without it open a new paragraph.
|
|
25
|
+
*
|
|
26
|
+
* Kept separate from `bodyText` deliberately — the other seven callers hand
|
|
27
|
+
* their result to consumers that do not render markdown, and would show a
|
|
28
|
+
* visitor a raw `[label](url)`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function bodyMarkdown(parts: Part[]): string;
|
|
10
31
|
/**
|
|
11
32
|
* Entity sections may be authored as one paragraph per entity:
|
|
12
33
|
* `[Product](web5://entity/...) - why it matches`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-utils.d.ts","sourceRoot":"","sources":["../../../../src/component/componentDefinitions/parse-utils.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parse-utils.d.ts","sourceRoot":"","sources":["../../../../src/component/componentDefinitions/parse-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export { analyzeBackdrop, computeContentBBox, buildProbeUrl, loadImagePixels, lo
|
|
|
48
48
|
export { stripMarkdown } from './component/componentDefinitions/parse-utils';
|
|
49
49
|
export { type RGB, rgbToHsl, hslToRgb, ensureMinLightness, toRgb, deriveDarkColor, deriveDarkGradient, deriveLightColor, } from './color/colorUtils';
|
|
50
50
|
export { pushEvent, pushPromptSubmit, pushLinkClick, pushError, pushExit, pushEntityFiltered, hasAnalyticsConsent, type EntityFilteredReason, } from './utils/analyticsEvents';
|
|
51
|
+
export { type ConsentState, type ConsentPurpose, type GatedPurpose, type ConsentSnapshot, type ConsentProvider, type GateView, type HostConsentInput, UNKNOWN_CONSENT, HOST_CONSENT_GLOBAL, transmit, mayTransmit, unlockOnUserAction, isUserEngaged, mayPersistIdentity, getConsentSnapshot, getGateView, subscribeToConsent, installConsentProvider, getInstalledProviderName, setConsentBufferLimit, getConsentGateStats, resetConsentGateForTests, detectConsentProvider, initConsentGate, CONSENT_OVERRIDE_KEY, CONSENT_OVERRIDE_QUERY_PARAM, getConsentOverride, setConsentOverride, createShopifyConsentProvider, isShopifyHost, createOneTrustConsentProvider, isOneTrustHost, createHostSuppliedConsentProvider, hasHostSuppliedConsent, publishHostConsent, } from './privacy';
|
|
51
52
|
export { type ConversationErrorType, ERROR_MARKDOWN, getErrorTypeFromStatus, createErrorMarkdown, STREAMING_TIMEOUT_MS, type ErrorActionType, type ErrorIconType, type ErrorTemplateButton, type ErrorTemplate, type ErrorTemplateOverrides, DEFAULT_ERROR_TEMPLATES, resolveErrorTemplate, } from './errors';
|
|
52
53
|
export { getForwardStack, setForwardStack, clearForwardStack, pushToForwardStack, popFromForwardStack, } from './utils/navigationStack';
|
|
53
54
|
export { UserQuery, type UserQueryProps, type ProductBackContext, } from './components/ui/UserQuery';
|