@replohq/sdk 2.18.2 → 2.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.
Files changed (40) hide show
  1. package/_vendor/replo-utils/lib/crypto.d.ts +5 -0
  2. package/_vendor/schemas/generated/consent.d.ts +13 -1
  3. package/analytics/analytics-provider.js +3 -1
  4. package/analytics/get-analytics-sinks.js +7 -4
  5. package/analytics/replo-pixel-script.d.ts +5 -5
  6. package/analytics/replo-pixel-script.js +23 -22
  7. package/cart/cart-actions.js +42 -13
  8. package/cart/checkout-actions.js +1 -1
  9. package/cart/checkout-session-actions.js +2 -2
  10. package/cart/gateways/cart-gateway.d.ts +29 -8
  11. package/cart/gateways/cookie-cart-gateway.d.ts +4 -2
  12. package/cart/gateways/cookie-cart-gateway.js +4 -4
  13. package/cart/gateways/resolver-cart-gateway.js +28 -16
  14. package/cart/gateways/stripe-cart-gateway.js +1 -1
  15. package/cart/start-stripe-checkout.js +1 -1
  16. package/consent/consent-platform.d.ts +20 -5
  17. package/consent/consent-platform.js +94 -11
  18. package/consent/consent-store.d.ts +7 -2
  19. package/consent/consent-store.js +16 -4
  20. package/consent/inject-script-descriptors.d.ts +8 -1
  21. package/consent/inject-script-descriptors.js +23 -0
  22. package/consent/replo-scripts.d.ts +6 -2
  23. package/consent/replo-scripts.js +35 -27
  24. package/consent/script-registration-store.d.ts +2 -0
  25. package/consent/script-snippets.d.ts +1 -1
  26. package/consent/types.d.ts +18 -4
  27. package/consent/usercentrics-consent.d.ts +15 -0
  28. package/consent/usercentrics-consent.js +96 -0
  29. package/consent/window-api.js +1 -1
  30. package/gateways/canopy-api.d.ts +21 -15
  31. package/gateways/canopy-api.js +105 -54
  32. package/lib/buildMetadata.js +3 -3
  33. package/lib/buyer-ip.d.ts +6 -0
  34. package/lib/buyer-ip.js +7 -0
  35. package/loaders/invoke-loader-action.js +6 -1
  36. package/loaders/invoke-loader-server.js +5 -1
  37. package/loaders/invoke-loader.js +5 -1
  38. package/loaders/loader-output.d.ts +2 -1
  39. package/loaders/loader-output.js +7 -3
  40. package/package.json +4 -4
@@ -23,17 +23,45 @@ function cookiebotPreset(identifier) {
23
23
  name: "data-cookieconsent",
24
24
  values: COOKIEBOT_CATEGORY_BY_CONSENT
25
25
  },
26
- isCookiebot: true
26
+ kind: "cookiebot",
27
+ reploUsercentricsService: null,
28
+ loaderContainerId: null
29
+ };
30
+ }
31
+ const USERCENTRICS_LOADER_URL = {
32
+ v2: "https://app.usercentrics.eu/browser-ui/latest/loader.js",
33
+ v3: "https://web.cmp.usercentrics.eu/ui/loader.js"
34
+ };
35
+ function usercentricsPreset(entry) {
36
+ return {
37
+ id: "usercentrics",
38
+ loader: entry.runtimeOwner ? [] : [
39
+ {
40
+ kind: "external",
41
+ src: USERCENTRICS_LOADER_URL[entry.loaderVersion ?? "v3"],
42
+ attributes: {
43
+ id: "usercentrics-cmp",
44
+ "data-settings-id": entry.identifier
45
+ }
46
+ }
47
+ ],
48
+ blockingAttribute: { name: "data-usercentrics", values: {} },
49
+ kind: "usercentrics",
50
+ reploUsercentricsService: entry.reploUsercentricsService ?? null,
51
+ loaderContainerId: entry.runtimeOwner?.type === "gtm" ? entry.runtimeOwner.containerId : null
27
52
  };
28
53
  }
29
54
  function isConsentPlatformEntry(entry) {
30
- return entry.type === "Cookiebot" || entry.type === "consentPlatform";
55
+ return entry.type === "Cookiebot" || entry.type === "Usercentrics" || entry.type === "consentPlatform";
31
56
  }
32
57
  function findConsentPlatform(scripts) {
33
58
  for (const entry of scripts) {
34
59
  if (entry.type === "Cookiebot") {
35
60
  return cookiebotPreset(entry.identifier);
36
61
  }
62
+ if (entry.type === "Usercentrics") {
63
+ return usercentricsPreset(entry);
64
+ }
37
65
  if (entry.type === "consentPlatform") {
38
66
  return {
39
67
  id: entry.id,
@@ -41,17 +69,25 @@ function findConsentPlatform(scripts) {
41
69
  { kind: "external", src: entry.src, attributes: entry.attributes }
42
70
  ],
43
71
  blockingAttribute: entry.blockingAttribute,
44
- isCookiebot: false
72
+ kind: "generic",
73
+ reploUsercentricsService: null,
74
+ loaderContainerId: null
45
75
  };
46
76
  }
47
77
  }
48
78
  return null;
49
79
  }
50
- function getBlockingAttributes(config, requiredConsent) {
80
+ function getBlockingAttributes(config, requiredConsent, usercentricsService) {
51
81
  const gated = requiredConsent.filter((category) => category !== "necessary");
52
82
  if (gated.length === 0) {
53
83
  return {};
54
84
  }
85
+ if (config.kind === "usercentrics") {
86
+ return {
87
+ type: "text/plain",
88
+ ...usercentricsService ? { [config.blockingAttribute.name]: usercentricsService } : {}
89
+ };
90
+ }
55
91
  const tokens = [
56
92
  ...new Set(
57
93
  gated.flatMap(
@@ -69,6 +105,11 @@ const COOKIEBOT_EVENTS = [
69
105
  "CookiebotOnAccept",
70
106
  "CookiebotOnDecline"
71
107
  ];
108
+ const USERCENTRICS_EVENTS = [
109
+ "UC_CONSENT",
110
+ "UC_UI_INITIALIZED",
111
+ "UC_UI_CMP_EVENT"
112
+ ];
72
113
  const OPTIONAL_CONSENT_CATEGORIES = [
73
114
  "analytics",
74
115
  "marketing",
@@ -79,6 +120,8 @@ const genericConsentCategorySchema = v.picklist(OPTIONAL_CONSENT_CATEGORIES);
79
120
  const GENERIC_CONSENT_GRANTED_EVENT = "reploConsentCategoryGranted";
80
121
  let delegatedPlatform = null;
81
122
  let listeningToCookiebot = false;
123
+ let listeningToUsercentrics = false;
124
+ let grantedUsercentricsServices = [];
82
125
  let listeningToGenericPlatform = false;
83
126
  let categories = DENIED_CATEGORIES;
84
127
  let decidedAt = null;
@@ -89,7 +132,7 @@ function findDelegatedPlatform() {
89
132
  return delegatedPlatform;
90
133
  }
91
134
  function getDelegatedConsent() {
92
- return { categories, decidedAt };
135
+ return delegatedPlatform?.kind === "usercentrics" ? { categories, decidedAt, grantedUsercentricsServices } : { categories, decidedAt };
93
136
  }
94
137
  function readCookiebotConsent() {
95
138
  const cookiebot = typeof window === "undefined" ? void 0 : window.Cookiebot;
@@ -130,6 +173,27 @@ function handleCookiebotEvent() {
130
173
  reloadPage();
131
174
  }
132
175
  }
176
+ async function handleUsercentricsEvent() {
177
+ const { readUsercentricsChange } = await import("./usercentrics-consent");
178
+ const change = await readUsercentricsChange({
179
+ categories,
180
+ decidedAt,
181
+ grantedUsercentricsServices
182
+ });
183
+ if (!change) {
184
+ return;
185
+ }
186
+ categories = change.categories;
187
+ grantedUsercentricsServices = change.grantedUsercentricsServices;
188
+ decidedAt = change.decidedAt;
189
+ notifyConsentListeners();
190
+ if (change.hasDowngrade) {
191
+ reloadPage();
192
+ }
193
+ }
194
+ function onUsercentricsEvent() {
195
+ void handleUsercentricsEvent();
196
+ }
133
197
  function handleGenericConsentActivation(event) {
134
198
  if (!(event instanceof CustomEvent)) {
135
199
  return;
@@ -153,28 +217,40 @@ function enableConsentDelegation(config) {
153
217
  return;
154
218
  }
155
219
  delegatedPlatform = config;
156
- if (config.isCookiebot && !listeningToCookiebot) {
220
+ if (config.kind === "cookiebot" && !listeningToCookiebot) {
157
221
  listeningToCookiebot = true;
158
222
  for (const event of COOKIEBOT_EVENTS) {
159
223
  window.addEventListener(event, handleCookiebotEvent);
160
224
  }
161
225
  }
162
- if (!config.isCookiebot && !listeningToGenericPlatform) {
226
+ if (config.kind === "usercentrics" && !listeningToUsercentrics) {
227
+ listeningToUsercentrics = true;
228
+ for (const event of USERCENTRICS_EVENTS) {
229
+ window.addEventListener(event, onUsercentricsEvent);
230
+ }
231
+ }
232
+ if (config.kind === "generic" && !listeningToGenericPlatform) {
163
233
  listeningToGenericPlatform = true;
164
234
  document.addEventListener(
165
235
  GENERIC_CONSENT_GRANTED_EVENT,
166
236
  handleGenericConsentActivation
167
237
  );
168
238
  }
169
- if (config.isCookiebot) {
239
+ if (config.kind === "cookiebot") {
170
240
  readCookiebotConsent();
171
241
  }
242
+ if (config.kind === "usercentrics" && (window.__ucCmp || window.UC_UI)) {
243
+ onUsercentricsEvent();
244
+ }
172
245
  }
173
246
  function activateBlockedScripts() {
174
- if (!delegatedPlatform?.isCookiebot) {
175
- return;
247
+ if (delegatedPlatform?.kind === "cookiebot") {
248
+ window.Cookiebot?.runScripts?.();
249
+ }
250
+ if (delegatedPlatform?.kind === "usercentrics") {
251
+ void window.__ucCmp?.refreshScripts?.();
252
+ window.UC_UI?.enableScriptsForServicesWithConsent?.();
176
253
  }
177
- window.Cookiebot?.runScripts?.();
178
254
  }
179
255
  function subscribeToConsentDelegation(listener) {
180
256
  listeners.add(listener);
@@ -191,6 +267,11 @@ function resetConsentPlatformForTests() {
191
267
  window.removeEventListener(event, handleCookiebotEvent);
192
268
  }
193
269
  }
270
+ if (typeof window !== "undefined" && listeningToUsercentrics) {
271
+ for (const event of USERCENTRICS_EVENTS) {
272
+ window.removeEventListener(event, onUsercentricsEvent);
273
+ }
274
+ }
194
275
  if (typeof document !== "undefined" && listeningToGenericPlatform) {
195
276
  document.removeEventListener(
196
277
  GENERIC_CONSENT_GRANTED_EVENT,
@@ -199,7 +280,9 @@ function resetConsentPlatformForTests() {
199
280
  }
200
281
  delegatedPlatform = null;
201
282
  listeningToCookiebot = false;
283
+ listeningToUsercentrics = false;
202
284
  listeningToGenericPlatform = false;
285
+ grantedUsercentricsServices = [];
203
286
  categories = DENIED_CATEGORIES;
204
287
  decidedAt = null;
205
288
  reloadPage = reloadCurrentPage;
@@ -13,7 +13,7 @@ import type { ConsentCategories } from "./cookie";
13
13
  */
14
14
  export interface ConsentState {
15
15
  /** Which platform owns consent. Absent means Replo's native banner; "external" is a generic `consentPlatform` entry. */
16
- cmp?: "replo" | "cookiebot" | "external";
16
+ cmp?: "replo" | "cookiebot" | "usercentrics" | "external";
17
17
  mode: ConsentMode;
18
18
  categories: ConsentCategories;
19
19
  /** Epoch ms when the visitor last made a choice, or null if undecided. */
@@ -24,6 +24,8 @@ export interface ConsentState {
24
24
  * next interaction lazily backfills it).
25
25
  */
26
26
  consentId?: string;
27
+ /** Usercentrics only: the services granted right now, including essentials that are granted automatically. */
28
+ grantedUsercentricsServices?: string[];
27
29
  }
28
30
  export declare const consentStore: {
29
31
  getSnapshot(): ConsentState;
@@ -62,8 +64,11 @@ export declare function useConsent(options?: UseConsentOptions): UseConsentResul
62
64
  /**
63
65
  * Whether a script requiring `requiredConsent` may load now. `mode === "off"`
64
66
  * allows everything only under Replo's banner; a delegated CMP answers instead.
67
+ * On Usercentrics a gated script names its service, or null for none, and only
68
+ * that service releases it. Omitting it means no one script: follow categories.
65
69
  */
66
- export declare function isConsentAllowed({ state, requiredConsent, }: {
70
+ export declare function isConsentAllowed({ state, requiredConsent, usercentricsService, }: {
67
71
  state: ConsentState;
68
72
  requiredConsent: ConsentCategory[];
73
+ usercentricsService?: string | null;
69
74
  }): boolean;
@@ -35,6 +35,14 @@ function resolveMode() {
35
35
  return isConsentMode(attribute) ? attribute : "off";
36
36
  }
37
37
  const DEFAULT_CONSENT_POLICY_VERSION = 1;
38
+ const DELEGATED_CMP_BY_KIND = {
39
+ cookiebot: "cookiebot",
40
+ usercentrics: "usercentrics",
41
+ generic: "external"
42
+ };
43
+ function isDelegatedCmp(cmp) {
44
+ return cmp !== void 0 && cmp !== "replo";
45
+ }
38
46
  function computeSnapshot() {
39
47
  if (typeof window === "undefined") {
40
48
  return SERVER_SNAPSHOT;
@@ -43,7 +51,7 @@ function computeSnapshot() {
43
51
  const platform = findDelegatedPlatform();
44
52
  if (platform) {
45
53
  return {
46
- cmp: platform.isCookiebot ? "cookiebot" : "external",
54
+ cmp: DELEGATED_CMP_BY_KIND[platform.kind],
47
55
  mode,
48
56
  ...getDelegatedConsent()
49
57
  };
@@ -87,7 +95,7 @@ function commit({
87
95
  policyVersion = DEFAULT_CONSENT_POLICY_VERSION
88
96
  }) {
89
97
  const previous = consentStore.getSnapshot();
90
- if (previous.cmp === "cookiebot" || previous.cmp === "external") {
98
+ if (isDelegatedCmp(previous.cmp)) {
91
99
  return;
92
100
  }
93
101
  const decidedAt = Date.now();
@@ -186,9 +194,13 @@ function useConsent(options = {}) {
186
194
  }
187
195
  function isConsentAllowed({
188
196
  state,
189
- requiredConsent
197
+ requiredConsent,
198
+ usercentricsService
190
199
  }) {
191
- if (state.cmp !== "cookiebot" && state.cmp !== "external" && state.mode === "off") {
200
+ if (state.cmp === "usercentrics" && usercentricsService !== void 0 && requiredConsent.some((category) => category !== "necessary")) {
201
+ return usercentricsService !== null && (state.grantedUsercentricsServices?.includes(usercentricsService) ?? false);
202
+ }
203
+ if (!isDelegatedCmp(state.cmp) && state.mode === "off") {
192
204
  return true;
193
205
  }
194
206
  if (requiredConsent.length === 0) {
@@ -1,4 +1,4 @@
1
- import type { ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
1
+ import type { ConsentCategory, ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
2
2
  /**
3
3
  * Creates and appends the script nodes for `descriptors`, skipping node ids
4
4
  * already in the document, and returns only the nodes created by this call.
@@ -11,3 +11,10 @@ export declare function injectScriptDescriptors({ baseId, descriptors, extraAttr
11
11
  /** Applied last, so a consent platform's `text/plain` beats a descriptor's own type. */
12
12
  extraAttributes?: Record<string, string>;
13
13
  }): HTMLScriptElement[];
14
+ /** Injects a script gated by whichever consent platform owns the page. */
15
+ export declare function injectGatedScript({ baseId, descriptors, requiredConsent, usercentricsService, }: {
16
+ baseId: string;
17
+ descriptors: ScriptTagDescriptor[];
18
+ requiredConsent: ConsentCategory[];
19
+ usercentricsService: string | null;
20
+ }): void;
@@ -1,3 +1,8 @@
1
+ import {
2
+ activateBlockedScripts,
3
+ findDelegatedPlatform,
4
+ getBlockingAttributes
5
+ } from "./consent-platform";
1
6
  function injectScriptDescriptors({
2
7
  baseId,
3
8
  descriptors,
@@ -37,6 +42,23 @@ function injectScriptDescriptors({
37
42
  });
38
43
  return createdNodes;
39
44
  }
45
+ function injectGatedScript({
46
+ baseId,
47
+ descriptors,
48
+ requiredConsent,
49
+ usercentricsService
50
+ }) {
51
+ const platform = findDelegatedPlatform();
52
+ const blockingAttributes = platform ? getBlockingAttributes(platform, requiredConsent, usercentricsService) : {};
53
+ const createdNodes = injectScriptDescriptors({
54
+ baseId,
55
+ descriptors,
56
+ extraAttributes: blockingAttributes
57
+ });
58
+ if ("type" in blockingAttributes && createdNodes.length > 0) {
59
+ activateBlockedScripts();
60
+ }
61
+ }
40
62
  function buildModuleShim(body) {
41
63
  return [
42
64
  'var moduleScript = document.createElement("script");',
@@ -46,5 +68,6 @@ function buildModuleShim(body) {
46
68
  ].join("\n");
47
69
  }
48
70
  export {
71
+ injectGatedScript,
49
72
  injectScriptDescriptors
50
73
  };
@@ -1,4 +1,5 @@
1
- import type { ConsentCategory, ReploScriptEntry, ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
1
+ import type { ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
2
+ import type { ConsentCategory, ReploScriptEntry } from "./types";
2
3
  import { Component } from "react";
3
4
  /**
4
5
  * Injects the resolved tags into `document.head` once a caller has decided
@@ -15,6 +16,7 @@ type InjectedScriptProps = {
15
16
  descriptors: ScriptTagDescriptor[];
16
17
  /** Used under a delegated platform to derive the attributes that gate the tag. */
17
18
  requiredConsent?: ConsentCategory[];
19
+ usercentricsService?: string;
18
20
  };
19
21
  export declare class InjectedScript extends Component<InjectedScriptProps> {
20
22
  componentDidMount(): void;
@@ -25,13 +27,15 @@ export declare class InjectedScript extends Component<InjectedScriptProps> {
25
27
  * Authored once in `app/layout.tsx`; the agent/miniapp only edit the `scripts`
26
28
  * array. Each entry registers itself and is gated + injected per consent.
27
29
  *
28
- * A consent-platform entry (`Cookiebot`, or a generic `consentPlatform`) takes
30
+ * A consent-platform entry (`Cookiebot`, `Usercentrics`, or a generic `consentPlatform`) takes
29
31
  * over consent site-wide: scripts carry the platform's blocking attributes, its
30
32
  * loader injects after them so its startup scan sees the full set, and Replo's
31
33
  * own gates follow the platform instead of the native banner.
32
34
  *
33
35
  * A GA4 entry's `runtimeOwner` leaves `gtag.js` to a GTM container or other
34
36
  * loader, which must keep loading it; `dataLayer`, config and sink still start.
37
+ * A Usercentrics entry's leaves the platform's loader to it the same way, and a
38
+ * GTM owner's container runs without waiting for consent.
35
39
  */
36
40
  export declare function ReploScripts({ scripts }: {
37
41
  scripts: ReploScriptEntry[];
@@ -2,16 +2,13 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Component } from "react";
4
4
  import {
5
- activateBlockedScripts,
6
5
  buildGrantMarker,
7
6
  enableConsentDelegation,
8
7
  findConsentPlatform,
9
- findDelegatedPlatform,
10
- getBlockingAttributes,
11
8
  isConsentPlatformEntry,
12
9
  OPTIONAL_CONSENT_CATEGORIES
13
10
  } from "./consent-platform";
14
- import { injectScriptDescriptors } from "./inject-script-descriptors";
11
+ import { injectGatedScript } from "./inject-script-descriptors";
15
12
  import {
16
13
  registerScript,
17
14
  snippetScriptId,
@@ -32,11 +29,14 @@ function scriptId(entry) {
32
29
  }
33
30
  return `${entry.type}:${entry.identifier}`;
34
31
  }
35
- function requiredConsentFor(entry) {
32
+ function requiredConsentFor(entry, loaderContainerId) {
36
33
  if (entry.type === "custom" || entry.type === "snippet") {
37
34
  return entry.requiredConsent;
38
35
  }
39
- if (entry.type === "consentPlatform" || entry.type === "Cookiebot") {
36
+ if (isConsentPlatformEntry(entry)) {
37
+ return [];
38
+ }
39
+ if (entry.type === "GoogleTagManager" && entry.identifier === loaderContainerId) {
40
40
  return [];
41
41
  }
42
42
  return entry.requiredConsent ?? defaultConsentFor(entry.type);
@@ -54,25 +54,20 @@ function descriptorsFor(entry) {
54
54
  if (entry.type === "snippet") {
55
55
  return [{ kind: "inline", body: entry.body, module: entry.module }];
56
56
  }
57
- if (entry.type === "consentPlatform" || entry.type === "Cookiebot") {
57
+ if (isConsentPlatformEntry(entry)) {
58
58
  return [];
59
59
  }
60
60
  return buildScriptTags(entry);
61
61
  }
62
62
  class InjectedScript extends Component {
63
63
  componentDidMount() {
64
- const { baseId, descriptors, requiredConsent = [] } = this.props;
65
- const platform = findDelegatedPlatform();
66
- const blockingAttributes = platform ? getBlockingAttributes(platform, requiredConsent) : {};
67
- const blockedByPlatform = "type" in blockingAttributes;
68
- const createdNodes = injectScriptDescriptors({
69
- baseId,
70
- descriptors,
71
- extraAttributes: blockingAttributes
64
+ const { requiredConsent = [], usercentricsService = null } = this.props;
65
+ injectGatedScript({
66
+ baseId: this.props.baseId,
67
+ descriptors: this.props.descriptors,
68
+ requiredConsent,
69
+ usercentricsService
72
70
  });
73
- if (blockedByPlatform && createdNodes.length > 0) {
74
- activateBlockedScripts();
75
- }
76
71
  }
77
72
  render() {
78
73
  return null;
@@ -89,23 +84,28 @@ class ScriptRegistration extends Component {
89
84
  return null;
90
85
  }
91
86
  }
92
- function registrationFor(entry) {
87
+ function registrationFor(entry, loaderContainerId) {
93
88
  const id = scriptId(entry);
94
- const requiredConsent = requiredConsentFor(entry);
89
+ const requiredConsent = requiredConsentFor(entry, loaderContainerId);
90
+ const usercentricsService = "usercentricsService" in entry && entry.usercentricsService ? entry.usercentricsService : null;
95
91
  if (entry.type === "X") {
96
92
  return {
97
93
  id,
98
94
  type: "X",
99
95
  requiredConsent,
96
+ usercentricsService,
100
97
  pixelId: entry.identifier,
101
98
  events: entry.events ?? {}
102
99
  };
103
100
  }
104
- return { id, type: entry.type, requiredConsent };
101
+ return { id, type: entry.type, requiredConsent, usercentricsService };
105
102
  }
106
- function ManagedScript({ entry }) {
107
- const registration = registrationFor(entry);
108
- const { id, requiredConsent } = registration;
103
+ function ManagedScript({
104
+ entry,
105
+ loaderContainerId
106
+ }) {
107
+ const registration = registrationFor(entry, loaderContainerId);
108
+ const { id, requiredConsent, usercentricsService } = registration;
109
109
  const isBlockedByNativeConsent = useIsBlockedByNativeConsent(requiredConsent);
110
110
  return /* @__PURE__ */ jsxs(Fragment, { children: [
111
111
  /* @__PURE__ */ jsx(
@@ -120,7 +120,8 @@ function ManagedScript({ entry }) {
120
120
  {
121
121
  baseId: id,
122
122
  descriptors: descriptorsFor(entry),
123
- requiredConsent
123
+ requiredConsent,
124
+ usercentricsService: usercentricsService ?? void 0
124
125
  }
125
126
  )
126
127
  ] });
@@ -145,8 +146,15 @@ function ReploScripts({ scripts }) {
145
146
  }
146
147
  return /* @__PURE__ */ jsxs(Fragment, { children: [
147
148
  /* @__PURE__ */ jsx(ConsentWindowApi, {}),
148
- scripts.filter((entry) => !isConsentPlatformEntry(entry)).map((entry) => /* @__PURE__ */ jsx(ManagedScript, { entry }, scriptId(entry))),
149
- platform && !platform.isCookiebot && OPTIONAL_CONSENT_CATEGORIES.map((category) => /* @__PURE__ */ jsx(
149
+ scripts.filter((entry) => !isConsentPlatformEntry(entry)).map((entry) => /* @__PURE__ */ jsx(
150
+ ManagedScript,
151
+ {
152
+ entry,
153
+ loaderContainerId: platform?.loaderContainerId ?? null
154
+ },
155
+ scriptId(entry)
156
+ )),
157
+ platform && platform.kind === "generic" && OPTIONAL_CONSENT_CATEGORIES.map((category) => /* @__PURE__ */ jsx(
150
158
  InjectedScript,
151
159
  {
152
160
  baseId: `consent-platform:${platform.id}:grant:${category}`,
@@ -9,6 +9,8 @@ import type { ConsentCategory, ReploScriptType, XPixelEvents } from "../_vendor/
9
9
  export type RegisteredScript = {
10
10
  id: string;
11
11
  requiredConsent: ConsentCategory[];
12
+ /** Null unless the entry names a Usercentrics service. */
13
+ usercentricsService: string | null;
12
14
  } & ({
13
15
  type: Exclude<ReploScriptType, "X">;
14
16
  } | {
@@ -1,5 +1,5 @@
1
1
  import type { ConsentCategory, ReploScriptEntry, ReploScriptType, ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
2
- type ProviderType = Exclude<ReploScriptType, "custom" | "snippet" | "consentPlatform" | "Cookiebot">;
2
+ type ProviderType = Exclude<ReploScriptType, "custom" | "snippet" | "consentPlatform" | "Cookiebot" | "Usercentrics">;
3
3
  type ProviderScriptEntry = Extract<ReploScriptEntry, {
4
4
  type: ProviderType;
5
5
  }>;
@@ -1,4 +1,4 @@
1
- export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "X" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "HubSpot" | "Mixpanel" | "Attentive" | "Alia" | "Cookiebot" | "snippet" | "custom" | "consentPlatform";
1
+ export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "X" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "HubSpot" | "Mixpanel" | "Attentive" | "Alia" | "Cookiebot" | "Usercentrics" | "snippet" | "custom" | "consentPlatform";
2
2
  export type ConsentCategory = "necessary" | "analytics" | "marketing" | "preferences" | "sale_of_data";
3
3
  export type ConsentMode = "off" | "simple" | "per-category";
4
4
  declare module "react" {
@@ -24,9 +24,11 @@ export type XPixelEvents = {
24
24
  purchase?: string;
25
25
  };
26
26
  /**
27
- * Another loader owns GA4's `gtag.js`. The SDK keeps `dataLayer`, the config
28
- * call and the analytics sink but never injects `gtag.js`, so if the GTM
29
- * container (or other code) stops loading it, events queue and never send.
27
+ * Another loader owns GA4's `gtag.js` or the Usercentrics loader. On GA4 the
28
+ * SDK keeps `dataLayer`, the config call and the analytics sink but never injects
29
+ * `gtag.js`, so if the GTM container (or other code) stops loading it, events
30
+ * queue and never send. On Usercentrics the SDK still blocks scripts and follows
31
+ * the visitor's answer, so if nothing loads it, gated scripts stay off.
30
32
  */
31
33
  export type ScriptRuntimeOwner = {
32
34
  type: "gtm";
@@ -39,37 +41,49 @@ export type ReploScriptEntry = {
39
41
  type: "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "HubSpot" | "Attentive" | "Alia";
40
42
  identifier: string;
41
43
  requiredConsent?: ConsentCategory[];
44
+ usercentricsService?: string;
42
45
  } | {
43
46
  type: "GA4";
44
47
  identifier: string;
45
48
  requiredConsent?: ConsentCategory[];
46
49
  runtimeOwner?: ScriptRuntimeOwner;
50
+ usercentricsService?: string;
47
51
  } | {
48
52
  type: "X";
49
53
  identifier: string;
50
54
  events?: XPixelEvents;
51
55
  requiredConsent?: ConsentCategory[];
56
+ usercentricsService?: string;
52
57
  } | {
53
58
  type: "Cookiebot";
54
59
  identifier: string;
55
60
  requiredConsent?: ConsentCategory[];
61
+ } | {
62
+ type: "Usercentrics";
63
+ identifier: string;
64
+ loaderVersion?: "v2" | "v3";
65
+ runtimeOwner?: ScriptRuntimeOwner;
66
+ reploUsercentricsService?: string;
56
67
  } | {
57
68
  type: "Mixpanel";
58
69
  identifier: string;
59
70
  region?: MixpanelRegion;
60
71
  requiredConsent?: ConsentCategory[];
72
+ usercentricsService?: string;
61
73
  } | {
62
74
  type: "snippet";
63
75
  id: string;
64
76
  body: string;
65
77
  module?: boolean;
66
78
  requiredConsent: ConsentCategory[];
79
+ usercentricsService?: string;
67
80
  } | {
68
81
  type: "custom";
69
82
  id: string;
70
83
  requiredConsent: ConsentCategory[];
71
84
  src?: string;
72
85
  body?: string;
86
+ usercentricsService?: string;
73
87
  } | {
74
88
  type: "consentPlatform";
75
89
  id: string;
@@ -0,0 +1,15 @@
1
+ import type { ConsentCategories } from "./cookie";
2
+ /**
3
+ * The visitor's answer as Replo consent, or null when there's nothing to apply:
4
+ * the platform isn't ready, its answer is unreadable, or nothing changed.
5
+ */
6
+ export declare function readUsercentricsChange(previous: {
7
+ categories: ConsentCategories;
8
+ decidedAt: number | null;
9
+ grantedUsercentricsServices: string[];
10
+ }): Promise<{
11
+ categories: ConsentCategories;
12
+ decidedAt: number | null;
13
+ grantedUsercentricsServices: string[];
14
+ hasDowngrade: boolean;
15
+ } | null>;