@replohq/sdk 2.18.3 → 2.20.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 (33) 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/hooks/use-add-to-cart.d.ts +17 -4
  8. package/cart/hooks/use-add-to-cart.js +6 -2
  9. package/cart/hooks/use-buy-now.d.ts +17 -5
  10. package/cart/hooks/use-buy-now.js +6 -2
  11. package/cart/hooks/use-checkout-availability.d.ts +21 -0
  12. package/cart/hooks/use-checkout-availability.js +71 -0
  13. package/consent/consent-platform.d.ts +20 -5
  14. package/consent/consent-platform.js +94 -11
  15. package/consent/consent-store.d.ts +7 -2
  16. package/consent/consent-store.js +16 -4
  17. package/consent/inject-script-descriptors.d.ts +8 -1
  18. package/consent/inject-script-descriptors.js +23 -0
  19. package/consent/replo-scripts.d.ts +6 -2
  20. package/consent/replo-scripts.js +35 -27
  21. package/consent/script-registration-store.d.ts +2 -0
  22. package/consent/script-snippets.d.ts +1 -1
  23. package/consent/types.d.ts +18 -4
  24. package/consent/usercentrics-consent.d.ts +15 -0
  25. package/consent/usercentrics-consent.js +96 -0
  26. package/consent/window-api.js +1 -1
  27. package/lib/buildMetadata.js +3 -3
  28. package/loaders/integration-loader-schemas.d.ts +1 -0
  29. package/loaders/loader-schemas.d.ts +3 -0
  30. package/loaders/loader-schemas.js +4 -2
  31. package/package.json +4 -4
  32. package/providers/canopy-client-config.d.ts +2 -0
  33. package/providers/replo-provider.js +2 -1
@@ -22,4 +22,9 @@ export declare function getHmacSha256Hex({ rawBody, signingKey, }: {
22
22
  rawBody: string;
23
23
  signingKey: string;
24
24
  }): Promise<string>;
25
+ export declare function isValidHmacSha256Hex({ rawBody, signingKey, signature, }: {
26
+ rawBody: string;
27
+ signingKey: string;
28
+ signature: string;
29
+ }): Promise<boolean>;
25
30
  export declare function deterministicUUID(value: string): string;
@@ -1,4 +1,4 @@
1
- export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "X" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Mixpanel" | "HubSpot" | "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" | "Mixpanel" | "HubSpot" | "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
  export type ScriptTagDescriptor = {
@@ -30,37 +30,49 @@ export type ReploScriptEntry = {
30
30
  type: "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "HubSpot" | "Attentive" | "Alia";
31
31
  identifier: string;
32
32
  requiredConsent?: ConsentCategory[];
33
+ usercentricsService?: string;
33
34
  } | {
34
35
  type: "GA4";
35
36
  identifier: string;
36
37
  requiredConsent?: ConsentCategory[];
37
38
  runtimeOwner?: ScriptRuntimeOwner;
39
+ usercentricsService?: string;
38
40
  } | {
39
41
  type: "X";
40
42
  identifier: string;
41
43
  events?: Partial<XPixelEvents>;
42
44
  requiredConsent?: ConsentCategory[];
45
+ usercentricsService?: string;
43
46
  } | {
44
47
  type: "Mixpanel";
45
48
  identifier: string;
46
49
  region?: MixpanelRegion;
47
50
  requiredConsent?: ConsentCategory[];
51
+ usercentricsService?: string;
48
52
  } | {
49
53
  type: "Cookiebot";
50
54
  identifier: string;
51
55
  requiredConsent?: ConsentCategory[];
56
+ } | {
57
+ type: "Usercentrics";
58
+ identifier: string;
59
+ loaderVersion?: "v2" | "v3";
60
+ runtimeOwner?: ScriptRuntimeOwner;
61
+ reploUsercentricsService?: string;
52
62
  } | {
53
63
  type: "snippet";
54
64
  id: string;
55
65
  body: string;
56
66
  module?: boolean;
57
67
  requiredConsent: ConsentCategory[];
68
+ usercentricsService?: string;
58
69
  } | {
59
70
  type: "custom";
60
71
  id: string;
61
72
  requiredConsent: ConsentCategory[];
62
73
  src?: string;
63
74
  body?: string;
75
+ usercentricsService?: string;
64
76
  } | {
65
77
  type: "consentPlatform";
66
78
  id: string;
@@ -37,7 +37,8 @@ function AnalyticsProvider({
37
37
  cmp: consent.cmp,
38
38
  mode: consent.mode,
39
39
  categories: consent.categories,
40
- decidedAt: consent.decidedAt
40
+ decidedAt: consent.decidedAt,
41
+ grantedUsercentricsServices: consent.grantedUsercentricsServices
41
42
  },
42
43
  registeredScripts,
43
44
  checkoutProvider
@@ -49,6 +50,7 @@ function AnalyticsProvider({
49
50
  consent.mode,
50
51
  consent.categories,
51
52
  consent.decidedAt,
53
+ consent.grantedUsercentricsServices,
52
54
  registeredScripts,
53
55
  checkoutProvider,
54
56
  additionalSinks
@@ -1,3 +1,4 @@
1
+ import { findDelegatedPlatform } from "../consent/consent-platform";
1
2
  import { isConsentAllowed } from "../consent/consent-store";
2
3
  import { snippetScriptId } from "../consent/script-registration-store";
3
4
  import { ConvergeSink } from "./sinks/converge-sink";
@@ -51,11 +52,12 @@ function getAnalyticsSinks({
51
52
  checkoutProvider
52
53
  }) {
53
54
  const sinks = [new WindowSink()];
54
- const firstPartyAllowed = isConsentAllowed({
55
+ const isReploPixelAllowed = isConsentAllowed({
55
56
  state: consent,
56
- requiredConsent: ["analytics"]
57
+ requiredConsent: ["analytics"],
58
+ usercentricsService: findDelegatedPlatform()?.reploUsercentricsService ?? null
57
59
  });
58
- if (firstPartyAllowed) {
60
+ if (isReploPixelAllowed) {
59
61
  sinks.push(new ReploSink());
60
62
  }
61
63
  const shopifyOwnsCheckout = checkoutProvider === "shopify";
@@ -68,7 +70,8 @@ function getAnalyticsSinks({
68
70
  }
69
71
  if (isConsentAllowed({
70
72
  state: consent,
71
- requiredConsent: script.requiredConsent
73
+ requiredConsent: script.requiredConsent,
74
+ usercentricsService: script.usercentricsService
72
75
  })) {
73
76
  const sink = factory();
74
77
  const gated = shopifyOwnsCheckout && !SHOPIFY_FUNNEL_GATE_EXEMPT_PROVIDERS.has(script.type);
@@ -4,11 +4,11 @@
4
4
  * events into, so gating it on `analytics` consent keeps the first-party sink
5
5
  * consent-correct without the pixel loading unconditionally in `<head>` the way
6
6
  * the legacy scaffold does. `analytics` consent here matches the
7
- * `firstPartyAllowed` gate that controls whether `ReploSink` is created at all.
7
+ * `isReploPixelAllowed` gate that controls whether `ReploSink` is created at all.
8
8
  *
9
- * Under a consent platform the tag is rendered regardless and `InjectedScript`
10
- * stamps the blocking attributes at mount time, since delegation is not yet
11
- * set here.
9
+ * Under a consent platform the tag is injected regardless, with its blocking
10
+ * attributes read in the effect because this renders before `ReploScripts`
11
+ * publishes the platform.
12
12
  */
13
13
  export declare function ReploPixelScript({ pixelUrl, apiUrl, projectId, siteVersion, }: {
14
14
  pixelUrl: string;
@@ -16,4 +16,4 @@ export declare function ReploPixelScript({ pixelUrl, apiUrl, projectId, siteVers
16
16
  projectId: string;
17
17
  /** Publish id of the running site; absent on unpublished or older deploys. */
18
18
  siteVersion?: string;
19
- }): import("react/jsx-runtime").JSX.Element | null;
19
+ }): null;
@@ -1,6 +1,7 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { InjectedScript } from "../consent/replo-scripts";
2
+ import { useEffect } from "react";
3
+ import { findDelegatedPlatform } from "../consent/consent-platform";
4
+ import { injectGatedScript } from "../consent/inject-script-descriptors";
4
5
  import { useIsBlockedByNativeConsent } from "../consent/use-is-blocked-by-native-consent";
5
6
  const PIXEL_SCRIPT_ID = "replo:first-party-pixel";
6
7
  const PIXEL_REQUIRED_CONSENT = ["analytics"];
@@ -13,28 +14,28 @@ function ReploPixelScript({
13
14
  const isBlockedByNativeConsent = useIsBlockedByNativeConsent(
14
15
  PIXEL_REQUIRED_CONSENT
15
16
  );
16
- if (isBlockedByNativeConsent) {
17
- return null;
18
- }
19
- const descriptors = [
20
- {
21
- kind: "external",
22
- src: `${pixelUrl}/latest.js`,
23
- attributes: {
24
- "data-api-url": apiUrl,
25
- "data-project-id": projectId,
26
- ...siteVersion ? { "data-site-version": siteVersion } : {}
27
- }
17
+ useEffect(() => {
18
+ if (isBlockedByNativeConsent) {
19
+ return;
28
20
  }
29
- ];
30
- return /* @__PURE__ */ jsx(
31
- InjectedScript,
32
- {
21
+ injectGatedScript({
33
22
  baseId: PIXEL_SCRIPT_ID,
34
- descriptors,
35
- requiredConsent: PIXEL_REQUIRED_CONSENT
36
- }
37
- );
23
+ descriptors: [
24
+ {
25
+ kind: "external",
26
+ src: `${pixelUrl}/latest.js`,
27
+ attributes: {
28
+ "data-api-url": apiUrl,
29
+ "data-project-id": projectId,
30
+ ...siteVersion ? { "data-site-version": siteVersion } : {}
31
+ }
32
+ }
33
+ ],
34
+ requiredConsent: PIXEL_REQUIRED_CONSENT,
35
+ usercentricsService: findDelegatedPlatform()?.reploUsercentricsService ?? null
36
+ });
37
+ }, [isBlockedByNativeConsent, pixelUrl, apiUrl, projectId, siteVersion]);
38
+ return null;
38
39
  }
39
40
  export {
40
41
  ReploPixelScript
@@ -1,6 +1,7 @@
1
1
  import type { QueryClient } from "@tanstack/react-query";
2
2
  import type { CartError } from "../cart-provider";
3
3
  import type { CartLineAdjustment, CartLineAttribute, CartLineMerchandise } from "../cart-types";
4
+ import type { CheckoutCatalog } from "./use-checkout-availability";
4
5
  /**
5
6
  * Describes a single item to add to the cart. Uses the merchandiseId
6
7
  * (e.g. a variant GID) directly from data loader output. Product
@@ -56,27 +57,39 @@ export declare function findMerchandiseInQueryCache({ queryClient, merchandiseId
56
57
  * and opens the cart UI, which renders it, so a click handler needs no
57
58
  * handling of its own.
58
59
  *
60
+ * `checkoutAvailability` is `"unavailable"` while the store can't take
61
+ * payments yet, or when the `catalog` passed is one this site's checkout
62
+ * can't sell. Show the button disabled with "Coming soon" then: a shopper
63
+ * could fill a cart but never check out.
64
+ *
59
65
  * @example
60
66
  * ```tsx
61
67
  * import { useAddToCart } from "@replohq/sdk/cart/hooks/use-add-to-cart";
62
68
  *
63
69
  * function AddToCartButton({ variant }) {
64
- * const { addToCart, isAdding } = useAddToCart();
70
+ * const { addToCart, isAdding, checkoutAvailability } = useAddToCart({
71
+ * catalog: "shopify",
72
+ * });
73
+ * const isComingSoon = checkoutAvailability === "unavailable";
65
74
  *
66
75
  * return (
67
76
  * <button
68
- * disabled={!variant.availableForSale || isAdding}
77
+ * disabled={!variant.availableForSale || isAdding || isComingSoon}
69
78
  * onClick={() => {
70
79
  * void addToCart([{ merchandiseId: variant.id, quantity: 1 }]);
71
80
  * }}
72
81
  * >
73
- * {isAdding ? "Adding..." : "Add to Cart"}
82
+ * {isComingSoon ? "Coming soon" : isAdding ? "Adding..." : "Add to Cart"}
74
83
  * </button>
75
84
  * );
76
85
  * }
77
86
  * ```
78
87
  */
79
- export declare function useAddToCart(): {
88
+ export declare function useAddToCart({ catalog, }?: {
89
+ /** The catalog this control sells from, so `checkoutAvailability` can say whether this site's checkout sells it. */
90
+ catalog?: CheckoutCatalog | null;
91
+ }): {
80
92
  addToCart: (items: AddToCartItem[], openCartAfterAdd?: boolean) => Promise<AddToCartResult>;
81
93
  isAdding: boolean;
94
+ checkoutAvailability: import("./use-checkout-availability").CheckoutAvailability;
82
95
  };
@@ -6,6 +6,7 @@ import { DATA_LOADER_KEYS } from "../../loaders/loader-keys";
6
6
  import { isPlaceholderMerchandiseId } from "../../loaders/placeholder-product";
7
7
  import { useCartInternal } from "../cart-provider";
8
8
  import { toCartError } from "../utils/cart-error";
9
+ import { useCheckoutAvailability } from "./use-checkout-availability";
9
10
  const PRODUCT_LOADER_KEYS = [
10
11
  DATA_LOADER_KEYS.SHOPIFY_PRODUCT,
11
12
  DATA_LOADER_KEYS.REPLO_PRODUCT
@@ -81,10 +82,13 @@ function findMerchandiseInQueryCache({
81
82
  }
82
83
  return void 0;
83
84
  }
84
- function useAddToCart() {
85
+ function useAddToCart({
86
+ catalog = null
87
+ } = {}) {
85
88
  const cart = useCartInternal();
86
89
  const queryClient = useQueryClient();
87
90
  const [isAdding, setIsAdding] = React.useState(false);
91
+ const checkoutAvailability = useCheckoutAvailability({ catalog });
88
92
  const addToCart = React.useCallback(
89
93
  async (items, openCartAfterAdd = true) => {
90
94
  const buyableItems = items.filter((item) => {
@@ -121,7 +125,7 @@ function useAddToCart() {
121
125
  },
122
126
  [cart, queryClient]
123
127
  );
124
- return { addToCart, isAdding };
128
+ return { addToCart, isAdding, checkoutAvailability };
125
129
  }
126
130
  export {
127
131
  findMerchandiseInQueryCache,
@@ -1,4 +1,5 @@
1
1
  import type { CartLineAttribute } from "../cart-types";
2
+ import type { CheckoutCatalog } from "./use-checkout-availability";
2
3
  /**
3
4
  * What a `buyNow` call did. Out of stock is an expected storefront outcome
4
5
  * (a buyer can race the last unit), so it rides the return value rather than
@@ -34,18 +35,25 @@ export interface VariantBuyNowInfo {
34
35
  * the browser to checkout. Stripe checkout ignores selling plans and discount
35
36
  * codes, logging a server-side warning when either is supplied.
36
37
  *
38
+ * `checkoutAvailability` is `"unavailable"` while the store can't take
39
+ * payments yet (Replo checkout not set up, or checkout turned off), or when
40
+ * the `catalog` passed is one this site's checkout can't sell. Show the button
41
+ * disabled with "Coming soon" then, instead of letting the shopper hit a
42
+ * failed checkout.
43
+ *
37
44
  * @example
38
45
  *
39
46
  * ```tsx
40
47
  * import { useBuyNow } from "@replohq/sdk/cart/hooks/use-buy-now";
41
48
  *
42
49
  * function MyComponent({ selectedVariant, selectedSellingPlan, quantity }) {
43
- * const { buyNow } = useBuyNow();
50
+ * const { buyNow, checkoutAvailability } = useBuyNow();
44
51
  * const [isSoldOut, setIsSoldOut] = React.useState(false);
52
+ * const isComingSoon = checkoutAvailability === "unavailable";
45
53
  *
46
54
  * return (
47
55
  * <Button
48
- * disabled={!selectedVariant?.availableForSale || isSoldOut}
56
+ * disabled={!selectedVariant?.availableForSale || isSoldOut || isComingSoon}
49
57
  * onClick={async () => {
50
58
  * if (selectedVariant) {
51
59
  * const outcome = await buyNow(
@@ -70,15 +78,18 @@ export interface VariantBuyNowInfo {
70
78
  * }
71
79
  * }}
72
80
  * >
73
- * {isSoldOut ? "Sold out" : "Shop Now"}
81
+ * {isComingSoon ? "Coming soon" : isSoldOut ? "Sold out" : "Shop Now"}
74
82
  * </Button>
75
83
  * );
76
84
  * }
77
85
  * ```
78
86
  *
79
- * @returns Object containing the buyNow function
87
+ * @returns Object containing the buyNow function and whether checkout can take payments
80
88
  */
81
- export declare function useBuyNow(): {
89
+ export declare function useBuyNow({ catalog, }?: {
90
+ /** The catalog this control sells from, so `checkoutAvailability` can say whether this site's checkout sells it. */
91
+ catalog?: CheckoutCatalog | null;
92
+ }): {
82
93
  buyNow: (variants: VariantBuyNowInfo[], options?: {
83
94
  discountCodes?: string[];
84
95
  /** Order-level Shopify cart attributes, including experiment attribution. */
@@ -87,4 +98,5 @@ export declare function useBuyNow(): {
87
98
  value: string;
88
99
  }[];
89
100
  }) => Promise<BuyNowOutcome>;
101
+ checkoutAvailability: import("./use-checkout-availability").CheckoutAvailability;
90
102
  };
@@ -5,6 +5,7 @@ import { CanopyError } from "../../lib/canopy-error";
5
5
  import { navigateExternal } from "../../lib/external-navigation";
6
6
  import { buyNowWithRecoveryAction } from "../buy-now-action";
7
7
  import { CartCountryContext } from "../cart-country-context";
8
+ import { useCheckoutAvailability } from "./use-checkout-availability";
8
9
  const REPLO_LOCAL_STORAGE_SESSION_KEY = "_replo_sid";
9
10
  const REPLO_SESSION_ID_KEY = "rsid";
10
11
  const REPLO_CLIENT_ID_KEY = "rclid";
@@ -13,8 +14,11 @@ class ProductHasNoVariantsError extends CanopyError {
13
14
  class CheckoutAttemptError extends CanopyError {
14
15
  }
15
16
  const checkoutAttempts = /* @__PURE__ */ new Map();
16
- function useBuyNow() {
17
+ function useBuyNow({
18
+ catalog = null
19
+ } = {}) {
17
20
  const country = React.useContext(CartCountryContext);
21
+ const checkoutAvailability = useCheckoutAvailability({ catalog });
18
22
  const reploSessionString = useClientValue(
19
23
  () => localStorage.getItem(REPLO_LOCAL_STORAGE_SESSION_KEY),
20
24
  null
@@ -88,7 +92,7 @@ function useBuyNow() {
88
92
  },
89
93
  [reploSessionString, country]
90
94
  );
91
- return { buyNow };
95
+ return { buyNow, checkoutAvailability };
92
96
  }
93
97
  export {
94
98
  useBuyNow
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Whether shoppers can check out on this site right now. `"checking"` while
3
+ * the store's payments are being confirmed; buy controls stay usable then, so
4
+ * a store that can sell never flashes an unavailable state.
5
+ */
6
+ export type CheckoutAvailability = "checking" | "available" | "unavailable";
7
+ /**
8
+ * Which catalog a buy control sells from: `"shopify"` for Shopify loader
9
+ * output, `"replo"` for Replo products. Shopify checkout can't sell Replo
10
+ * products and Replo checkout can't sell Shopify ones.
11
+ */
12
+ export type CheckoutCatalog = "shopify" | "replo";
13
+ /**
14
+ * Replo checkout can only sell once the store's payments are set up, which
15
+ * can finish or lapse long after a publish, so it is read live. Shopify
16
+ * checkout is the store's own and is treated as available. Without a
17
+ * `catalog` only the site's checkout is judged, as before the option existed.
18
+ */
19
+ export declare function useCheckoutAvailability({ catalog, }: {
20
+ catalog: CheckoutCatalog | null;
21
+ }): CheckoutAvailability;
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { CanopyError } from "../../lib/canopy-error";
4
+ import { useCanopyClientConfig } from "../../providers/canopy-client-config";
5
+ class CheckoutAvailabilityError extends CanopyError {
6
+ }
7
+ const CHECKOUT_AVAILABILITY_STALE_MS = 6e4;
8
+ async function fetchCanAcceptPayments(config) {
9
+ const response = await fetch(
10
+ `${config.canopyApiBaseUrl}/api/v1/stripe-checkout/availability`,
11
+ {
12
+ method: "POST",
13
+ headers: { "Content-Type": "application/json" },
14
+ body: JSON.stringify({ projectId: config.projectId })
15
+ }
16
+ );
17
+ if (!response.ok) {
18
+ throw new CheckoutAvailabilityError({
19
+ message: `Checkout availability check failed with status ${response.status}`
20
+ });
21
+ }
22
+ const body = await response.json();
23
+ if (typeof body !== "object" || body === null || !("canAcceptPayments" in body) || typeof body.canAcceptPayments !== "boolean") {
24
+ throw new CheckoutAvailabilityError({
25
+ message: "Checkout availability check returned an unexpected response"
26
+ });
27
+ }
28
+ return body.canAcceptPayments;
29
+ }
30
+ function useCheckoutAvailability({
31
+ catalog
32
+ }) {
33
+ const config = useCanopyClientConfig();
34
+ const checkoutProvider = config?.checkoutProvider ?? null;
35
+ const query = useQuery({
36
+ queryKey: ["replo", "checkout-availability", config?.projectId ?? null],
37
+ queryFn: () => {
38
+ if (!config) {
39
+ throw new CheckoutAvailabilityError({
40
+ message: "This site has no canopy-api config"
41
+ });
42
+ }
43
+ return fetchCanAcceptPayments(config);
44
+ },
45
+ enabled: checkoutProvider === "stripe" && catalog !== "shopify",
46
+ staleTime: CHECKOUT_AVAILABILITY_STALE_MS,
47
+ retry: 1
48
+ });
49
+ if (checkoutProvider === null) {
50
+ return "available";
51
+ }
52
+ if (checkoutProvider === "none") {
53
+ return "unavailable";
54
+ }
55
+ if (checkoutProvider === "shopify") {
56
+ return catalog === "replo" ? "unavailable" : "available";
57
+ }
58
+ if (catalog === "shopify") {
59
+ return "unavailable";
60
+ }
61
+ if (query.isPending) {
62
+ return "checking";
63
+ }
64
+ if (query.isError) {
65
+ return "available";
66
+ }
67
+ return query.data ? "available" : "unavailable";
68
+ }
69
+ export {
70
+ useCheckoutAvailability
71
+ };
@@ -7,15 +7,21 @@ export interface ConsentPlatformConfig {
7
7
  name: string;
8
8
  values: Partial<Record<ConsentCategory, string>>;
9
9
  };
10
- /** Cookiebot preset only: enables consent read-back and runScripts re-scan. */
11
- isCookiebot: boolean;
10
+ /** Presets read the visitor's answer back; a generic platform only reports grants. */
11
+ kind: "cookiebot" | "usercentrics" | "generic";
12
+ /** Usercentrics only: the service that releases Replo's own pixel, or null until one is chosen. */
13
+ reploUsercentricsService: string | null;
14
+ /** The GTM container that loads this platform, which therefore can't wait for it. */
15
+ loaderContainerId: string | null;
12
16
  }
13
17
  /** Whether this entry connects a consent platform (vs a tracker it would gate). */
14
- export declare function isConsentPlatformEntry(entry: ReploScriptEntry): boolean;
18
+ export declare function isConsentPlatformEntry(entry: ReploScriptEntry): entry is Extract<ReploScriptEntry, {
19
+ type: "Cookiebot" | "Usercentrics" | "consentPlatform";
20
+ }>;
15
21
  /** The connected platform's config, or null when Replo's banner owns consent. */
16
22
  export declare function findConsentPlatform(scripts: ReploScriptEntry[]): ConsentPlatformConfig | null;
17
23
  /** Blocks a gated script; `text/plain` is unconditional so an unmapped category fails closed. */
18
- export declare function getBlockingAttributes(config: ConsentPlatformConfig, requiredConsent: ConsentCategory[]): Record<string, string>;
24
+ export declare function getBlockingAttributes(config: ConsentPlatformConfig, requiredConsent: ConsentCategory[], usercentricsService: string | null): Record<string, string>;
19
25
  /** The bits of Cookiebot's `window.Cookiebot` global we read. */
20
26
  interface CookiebotGlobal {
21
27
  consent?: Partial<Record<CookiebotCategory, boolean>>;
@@ -26,6 +32,14 @@ type CookiebotCategory = "necessary" | "preferences" | "statistics" | "marketing
26
32
  declare global {
27
33
  interface Window {
28
34
  Cookiebot?: CookiebotGlobal;
35
+ __ucCmp?: {
36
+ getConsentDetails?: () => Promise<unknown>;
37
+ refreshScripts?: () => Promise<unknown>;
38
+ };
39
+ UC_UI?: {
40
+ getServicesBaseInfo?: () => unknown;
41
+ enableScriptsForServicesWithConsent?: () => unknown;
42
+ };
29
43
  }
30
44
  }
31
45
  type OptionalConsentCategory = Exclude<ConsentCategory, "necessary">;
@@ -36,12 +50,13 @@ export declare function findDelegatedPlatform(): ConsentPlatformConfig | null;
36
50
  export declare function getDelegatedConsent(): {
37
51
  categories: ConsentCategories;
38
52
  decidedAt: number | null;
53
+ grantedUsercentricsServices?: string[];
39
54
  };
40
55
  /** Inline marker a generic CMP executes when it releases one consent category. */
41
56
  export declare function buildGrantMarker(category: OptionalConsentCategory): ScriptTagDescriptor;
42
57
  /** Latches the platform as the consent backend; un-delegating would need a reload, as native revocation already does. */
43
58
  export declare function enableConsentDelegation(config: ConsentPlatformConfig): void;
44
- /** Cookiebot-only re-scan; other platforms rely on the loader injecting after every tag. */
59
+ /** Re-scan for late tags; a generic platform relies on the loader injecting after every tag. */
45
60
  export declare function activateBlockedScripts(): void;
46
61
  /** Subscribe to delegated-consent changes. Returns an unsubscribe function. */
47
62
  export declare function subscribeToConsentDelegation(listener: () => void): () => void;