@replohq/sdk 1.2.0 → 1.2.1

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 (31) hide show
  1. package/analytics/hooks/use-product-viewed-analytics.d.ts +6 -1
  2. package/analytics/hooks/use-product-viewed-analytics.js +4 -1
  3. package/analytics/hooks/use-product-viewed-analytics.js.map +2 -2
  4. package/analytics/replo-pixel-script.d.ts +1 -1
  5. package/analytics/replo-pixel-script.js.map +1 -1
  6. package/analytics/sinks/converge-event-mapper.d.ts +41 -0
  7. package/analytics/sinks/converge-event-mapper.js +182 -0
  8. package/analytics/sinks/converge-event-mapper.js.map +7 -0
  9. package/analytics/sinks/converge-sink.d.ts +24 -9
  10. package/analytics/sinks/converge-sink.js +45 -23
  11. package/analytics/sinks/converge-sink.js.map +2 -2
  12. package/analytics/sinks/northbeam-sink.d.ts +2 -4
  13. package/analytics/sinks/northbeam-sink.js +1 -4
  14. package/analytics/sinks/northbeam-sink.js.map +2 -2
  15. package/analytics/utils/analytics-utils.d.ts +6 -1
  16. package/analytics/utils/analytics-utils.js +24 -11
  17. package/analytics/utils/analytics-utils.js.map +2 -2
  18. package/analytics/utils/navigation-tracker.d.ts +2 -0
  19. package/analytics/utils/navigation-tracker.js +7 -3
  20. package/analytics/utils/navigation-tracker.js.map +2 -2
  21. package/cart/cart-provider.js +4 -1
  22. package/cart/cart-provider.js.map +2 -2
  23. package/consent/inject-script-descriptors.d.ts +3 -3
  24. package/consent/inject-script-descriptors.js.map +1 -1
  25. package/consent/replo-scripts.d.ts +8 -2
  26. package/consent/replo-scripts.js +49 -33
  27. package/consent/replo-scripts.js.map +2 -2
  28. package/consent/script-snippets.js +2 -6
  29. package/consent/script-snippets.js.map +2 -2
  30. package/lib/buildMetadata.js +3 -3
  31. package/package.json +4 -4
@@ -1,5 +1,9 @@
1
1
  const QUEUE_TIMEOUT_MS = 3e4;
2
- function createEventQueue(checkPixelReady, sendEvent, pixelName) {
2
+ function createAnalyticsQueue({
3
+ isReady,
4
+ send,
5
+ queueName
6
+ }) {
3
7
  const queue = [];
4
8
  let flushTimer = null;
5
9
  let timeoutTimer = null;
@@ -15,14 +19,14 @@ function createEventQueue(checkPixelReady, sendEvent, pixelName) {
15
19
  }
16
20
  };
17
21
  const flush = () => {
18
- if (!checkPixelReady()) {
22
+ if (!isReady()) {
19
23
  return;
20
24
  }
21
25
  console.debug(
22
- `[analytics] flushing ${queue.length} queued events for ${pixelName}`
26
+ `[analytics] flushing ${queue.length} queued events for ${queueName}`
23
27
  );
24
28
  try {
25
- queue.forEach(({ event, params }) => sendEvent(event, params));
29
+ queue.forEach(send);
26
30
  } finally {
27
31
  queue.length = 0;
28
32
  cleanup();
@@ -36,26 +40,34 @@ function createEventQueue(checkPixelReady, sendEvent, pixelName) {
36
40
  timeoutTimer = window.setTimeout(() => {
37
41
  queue.length = 0;
38
42
  permanentlyDisabled = true;
39
- console.debug(`Analytics queue timed out for ${pixelName}`);
43
+ console.debug(`Analytics queue timed out for ${queueName}`);
40
44
  cleanup();
41
45
  }, QUEUE_TIMEOUT_MS);
42
46
  };
43
- return (event, params = {}) => {
47
+ return (payload) => {
44
48
  if (permanentlyDisabled) {
45
49
  console.debug(
46
- `[analytics] ${pixelName} queue is disabled, dropping event`,
47
- event
50
+ `[analytics] ${queueName} queue is disabled, dropping event`,
51
+ payload
48
52
  );
49
53
  return;
50
54
  }
51
- if (checkPixelReady()) {
52
- sendEvent(event, params);
55
+ if (isReady()) {
56
+ send(payload);
53
57
  } else {
54
- queue.push({ event, params });
58
+ queue.push(payload);
55
59
  scheduleFlush();
56
60
  }
57
61
  };
58
62
  }
63
+ function createEventQueue(checkPixelReady, sendEvent, pixelName) {
64
+ const enqueue = createAnalyticsQueue({
65
+ isReady: checkPixelReady,
66
+ send: ({ event, params }) => sendEvent(event, params),
67
+ queueName: pixelName
68
+ });
69
+ return (event, params = {}) => enqueue({ event, params });
70
+ }
59
71
  function createMethodQueue(checkInstanceReady, options) {
60
72
  const queue = [];
61
73
  let flushTimer = null;
@@ -123,6 +135,7 @@ function createMethodQueue(checkInstanceReady, options) {
123
135
  };
124
136
  }
125
137
  export {
138
+ createAnalyticsQueue,
126
139
  createEventQueue,
127
140
  createMethodQueue
128
141
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../analytics/utils/analytics-utils.ts"],
4
- "sourcesContent": ["// oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\ntype QueuedEvent = { event: string; params: Record<string, any> };\n\nconst QUEUE_TIMEOUT_MS = 30_000;\n\nexport function createEventQueue(\n checkPixelReady: () => boolean,\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n sendEvent: (event: string, params: Record<string, any>) => void,\n pixelName: string,\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n): (event: string, params?: Record<string, any>) => void {\n const queue: QueuedEvent[] = [];\n let flushTimer: number | null = null;\n let timeoutTimer: number | null = null;\n let permanentlyDisabled = false;\n\n const cleanup = () => {\n if (flushTimer !== null) {\n window.clearInterval(flushTimer);\n flushTimer = null;\n }\n if (timeoutTimer !== null) {\n window.clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const flush = () => {\n if (!checkPixelReady()) {\n return;\n }\n console.debug(\n `[analytics] flushing ${queue.length} queued events for ${pixelName}`,\n );\n try {\n queue.forEach(({ event, params }) => sendEvent(event, params));\n } finally {\n queue.length = 0;\n cleanup();\n }\n };\n\n const scheduleFlush = () => {\n if (typeof window === \"undefined\" || flushTimer !== null) {\n return;\n }\n\n flushTimer = window.setInterval(flush, 500);\n timeoutTimer = window.setTimeout(() => {\n queue.length = 0;\n permanentlyDisabled = true;\n console.debug(`Analytics queue timed out for ${pixelName}`);\n cleanup();\n }, QUEUE_TIMEOUT_MS);\n };\n\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n return (event: string, params: Record<string, any> = {}) => {\n if (permanentlyDisabled) {\n console.debug(\n `[analytics] ${pixelName} queue is disabled, dropping event`,\n event,\n );\n return;\n }\n\n if (checkPixelReady()) {\n sendEvent(event, params);\n } else {\n queue.push({ event, params });\n scheduleFlush();\n }\n };\n}\n\ntype MethodQueueOptions = {\n queueName?: string;\n onTimeout?: () => void;\n};\n\nexport function createMethodQueue<\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n InstanceType extends Record<string, (...args: any[]) => any>,\n>(\n checkInstanceReady: () => InstanceType | null,\n options?: MethodQueueOptions,\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n): (method: keyof InstanceType, ...args: any[]) => void {\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n const queue: { method: keyof InstanceType; args: any[] }[] = [];\n let flushTimer: number | null = null;\n let timeoutTimer: number | null = null;\n let permanentlyDisabled = false;\n const queueName = options?.queueName ?? \"method queue\";\n\n const cleanup = () => {\n if (flushTimer !== null) {\n window.clearInterval(flushTimer);\n flushTimer = null;\n }\n if (timeoutTimer !== null) {\n window.clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const flush = () => {\n const instance = checkInstanceReady();\n if (!instance) {\n return;\n }\n try {\n queue.forEach(({ method, args }) => {\n const methodFunction = instance[method];\n if (typeof methodFunction === \"function\") {\n void methodFunction.apply(instance, args);\n }\n });\n } finally {\n queue.length = 0;\n cleanup();\n }\n };\n\n const scheduleFlush = () => {\n if (typeof window === \"undefined\" || flushTimer !== null) {\n return;\n }\n\n flushTimer = window.setInterval(flush, 500);\n timeoutTimer = window.setTimeout(() => {\n queue.length = 0;\n permanentlyDisabled = true;\n if (typeof window !== \"undefined\") {\n console.debug(\n `[analytics] ${queueName} timed out and has been disabled`,\n );\n }\n options?.onTimeout?.();\n cleanup();\n }, QUEUE_TIMEOUT_MS);\n };\n\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n return (method: keyof InstanceType, ...args: any[]) => {\n if (permanentlyDisabled) {\n return;\n }\n\n const instance = checkInstanceReady();\n if (instance) {\n const methodFunction = instance[method];\n if (typeof methodFunction === \"function\") {\n void methodFunction.apply(instance, args);\n }\n } else {\n queue.push({ method, args });\n scheduleFlush();\n }\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["type QueuedEvent = { event: string; params: Record<string, unknown> };\n\nconst QUEUE_TIMEOUT_MS = 30_000;\n\nexport function createAnalyticsQueue<Payload>({\n isReady,\n send,\n queueName,\n}: {\n isReady: () => boolean;\n send: (payload: Payload) => void;\n queueName: string;\n}): (payload: Payload) => void {\n const queue: Payload[] = [];\n let flushTimer: number | null = null;\n let timeoutTimer: number | null = null;\n let permanentlyDisabled = false;\n\n const cleanup = () => {\n if (flushTimer !== null) {\n window.clearInterval(flushTimer);\n flushTimer = null;\n }\n if (timeoutTimer !== null) {\n window.clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const flush = () => {\n if (!isReady()) {\n return;\n }\n console.debug(\n `[analytics] flushing ${queue.length} queued events for ${queueName}`,\n );\n try {\n queue.forEach(send);\n } finally {\n queue.length = 0;\n cleanup();\n }\n };\n\n const scheduleFlush = () => {\n if (typeof window === \"undefined\" || flushTimer !== null) {\n return;\n }\n\n flushTimer = window.setInterval(flush, 500);\n timeoutTimer = window.setTimeout(() => {\n queue.length = 0;\n permanentlyDisabled = true;\n console.debug(`Analytics queue timed out for ${queueName}`);\n cleanup();\n }, QUEUE_TIMEOUT_MS);\n };\n\n return (payload: Payload) => {\n if (permanentlyDisabled) {\n console.debug(\n `[analytics] ${queueName} queue is disabled, dropping event`,\n payload,\n );\n return;\n }\n\n if (isReady()) {\n send(payload);\n } else {\n queue.push(payload);\n scheduleFlush();\n }\n };\n}\n\nexport function createEventQueue(\n checkPixelReady: () => boolean,\n sendEvent: (event: string, params: Record<string, unknown>) => void,\n pixelName: string,\n): (event: string, params?: Record<string, unknown>) => void {\n const enqueue = createAnalyticsQueue<QueuedEvent>({\n isReady: checkPixelReady,\n send: ({ event, params }) => sendEvent(event, params),\n queueName: pixelName,\n });\n\n return (event, params = {}) => enqueue({ event, params });\n}\n\ntype MethodQueueOptions = {\n queueName?: string;\n onTimeout?: () => void;\n};\n\nexport function createMethodQueue<\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n InstanceType extends Record<string, (...args: any[]) => any>,\n>(\n checkInstanceReady: () => InstanceType | null,\n options?: MethodQueueOptions,\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n): (method: keyof InstanceType, ...args: any[]) => void {\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n const queue: { method: keyof InstanceType; args: any[] }[] = [];\n let flushTimer: number | null = null;\n let timeoutTimer: number | null = null;\n let permanentlyDisabled = false;\n const queueName = options?.queueName ?? \"method queue\";\n\n const cleanup = () => {\n if (flushTimer !== null) {\n window.clearInterval(flushTimer);\n flushTimer = null;\n }\n if (timeoutTimer !== null) {\n window.clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const flush = () => {\n const instance = checkInstanceReady();\n if (!instance) {\n return;\n }\n try {\n queue.forEach(({ method, args }) => {\n const methodFunction = instance[method];\n if (typeof methodFunction === \"function\") {\n void methodFunction.apply(instance, args);\n }\n });\n } finally {\n queue.length = 0;\n cleanup();\n }\n };\n\n const scheduleFlush = () => {\n if (typeof window === \"undefined\" || flushTimer !== null) {\n return;\n }\n\n flushTimer = window.setInterval(flush, 500);\n timeoutTimer = window.setTimeout(() => {\n queue.length = 0;\n permanentlyDisabled = true;\n if (typeof window !== \"undefined\") {\n console.debug(\n `[analytics] ${queueName} timed out and has been disabled`,\n );\n }\n options?.onTimeout?.();\n cleanup();\n }, QUEUE_TIMEOUT_MS);\n };\n\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n return (method: keyof InstanceType, ...args: any[]) => {\n if (permanentlyDisabled) {\n return;\n }\n\n const instance = checkInstanceReady();\n if (instance) {\n const methodFunction = instance[method];\n if (typeof methodFunction === \"function\") {\n void methodFunction.apply(instance, args);\n }\n } else {\n queue.push({ method, args });\n scheduleFlush();\n }\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ export type PageMetadata = {
3
3
  title: string;
4
4
  referrer: string;
5
5
  timestamp: number;
6
+ navigationType?: "initial" | "soft";
6
7
  };
7
8
  export declare class NavigationTracker {
8
9
  private lastUrl;
@@ -12,5 +13,6 @@ export declare class NavigationTracker {
12
13
  init(): void;
13
14
  private setupSPATracking;
14
15
  private handleNavigation;
16
+ private emitNavigation;
15
17
  cleanup(): void;
16
18
  }
@@ -6,7 +6,7 @@ class NavigationTracker {
6
6
  this.onNavigate = onNavigate;
7
7
  }
8
8
  init() {
9
- this.handleNavigation();
9
+ this.emitNavigation("initial");
10
10
  window.addEventListener("popstate", this.handleNavigation);
11
11
  this.setupSPATracking();
12
12
  }
@@ -18,6 +18,9 @@ class NavigationTracker {
18
18
  };
19
19
  }
20
20
  handleNavigation = () => {
21
+ this.emitNavigation("soft");
22
+ };
23
+ emitNavigation(navigationType) {
21
24
  const currentUrl = window.location.href;
22
25
  if (this.lastUrl !== null && this.lastUrl === currentUrl) {
23
26
  return;
@@ -27,10 +30,11 @@ class NavigationTracker {
27
30
  url: currentUrl,
28
31
  title: document.title,
29
32
  referrer: document.referrer,
30
- timestamp: Date.now()
33
+ timestamp: Date.now(),
34
+ navigationType
31
35
  };
32
36
  this.onNavigate(metadata);
33
- };
37
+ }
34
38
  cleanup() {
35
39
  window.removeEventListener("popstate", this.handleNavigation);
36
40
  if (this.originalPushState) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../analytics/utils/navigation-tracker.ts"],
4
- "sourcesContent": ["export type PageMetadata = {\n url: string;\n title: string;\n referrer: string;\n timestamp: number;\n};\n\nexport class NavigationTracker {\n private lastUrl: string | null = null;\n private onNavigate: (metadata: PageMetadata) => void;\n private originalPushState: typeof window.history.pushState | null = null;\n\n constructor(onNavigate: (metadata: PageMetadata) => void) {\n this.onNavigate = onNavigate;\n }\n\n init() {\n this.handleNavigation();\n\n window.addEventListener(\"popstate\", this.handleNavigation);\n\n this.setupSPATracking();\n }\n\n private setupSPATracking() {\n this.originalPushState = window.history.pushState;\n\n window.history.pushState = (...args) => {\n this.originalPushState!.apply(window.history, args);\n this.handleNavigation();\n };\n }\n\n private handleNavigation = () => {\n const currentUrl = window.location.href;\n\n if (this.lastUrl !== null && this.lastUrl === currentUrl) {\n return;\n }\n this.lastUrl = currentUrl;\n\n const metadata: PageMetadata = {\n url: currentUrl,\n title: document.title,\n referrer: document.referrer,\n timestamp: Date.now(),\n };\n\n this.onNavigate(metadata);\n };\n\n cleanup() {\n window.removeEventListener(\"popstate\", this.handleNavigation);\n\n if (this.originalPushState) {\n window.history.pushState = this.originalPushState;\n }\n }\n}\n"],
5
- "mappings": "AAOO,MAAM,kBAAkB;AAAA,EACrB,UAAyB;AAAA,EACzB;AAAA,EACA,oBAA4D;AAAA,EAEpE,YAAY,YAA8C;AACxD,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,OAAO;AACL,SAAK,iBAAiB;AAEtB,WAAO,iBAAiB,YAAY,KAAK,gBAAgB;AAEzD,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEQ,mBAAmB;AACzB,SAAK,oBAAoB,OAAO,QAAQ;AAExC,WAAO,QAAQ,YAAY,IAAI,SAAS;AACtC,WAAK,kBAAmB,MAAM,OAAO,SAAS,IAAI;AAClD,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,mBAAmB,MAAM;AAC/B,UAAM,aAAa,OAAO,SAAS;AAEnC,QAAI,KAAK,YAAY,QAAQ,KAAK,YAAY,YAAY;AACxD;AAAA,IACF;AACA,SAAK,UAAU;AAEf,UAAM,WAAyB;AAAA,MAC7B,KAAK;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,UAAU,SAAS;AAAA,MACnB,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,SAAK,WAAW,QAAQ;AAAA,EAC1B;AAAA,EAEA,UAAU;AACR,WAAO,oBAAoB,YAAY,KAAK,gBAAgB;AAE5D,QAAI,KAAK,mBAAmB;AAC1B,aAAO,QAAQ,YAAY,KAAK;AAAA,IAClC;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["export type PageMetadata = {\n url: string;\n title: string;\n referrer: string;\n timestamp: number;\n navigationType?: \"initial\" | \"soft\";\n};\n\nexport class NavigationTracker {\n private lastUrl: string | null = null;\n private onNavigate: (metadata: PageMetadata) => void;\n private originalPushState: typeof window.history.pushState | null = null;\n\n constructor(onNavigate: (metadata: PageMetadata) => void) {\n this.onNavigate = onNavigate;\n }\n\n init() {\n this.emitNavigation(\"initial\");\n\n window.addEventListener(\"popstate\", this.handleNavigation);\n\n this.setupSPATracking();\n }\n\n private setupSPATracking() {\n this.originalPushState = window.history.pushState;\n\n window.history.pushState = (...args) => {\n this.originalPushState!.apply(window.history, args);\n this.handleNavigation();\n };\n }\n\n private handleNavigation = () => {\n this.emitNavigation(\"soft\");\n };\n\n private emitNavigation(navigationType: PageMetadata[\"navigationType\"]) {\n const currentUrl = window.location.href;\n\n if (this.lastUrl !== null && this.lastUrl === currentUrl) {\n return;\n }\n this.lastUrl = currentUrl;\n\n const metadata: PageMetadata = {\n url: currentUrl,\n title: document.title,\n referrer: document.referrer,\n timestamp: Date.now(),\n navigationType,\n };\n\n this.onNavigate(metadata);\n }\n\n cleanup() {\n window.removeEventListener(\"popstate\", this.handleNavigation);\n\n if (this.originalPushState) {\n window.history.pushState = this.originalPushState;\n }\n }\n}\n"],
5
+ "mappings": "AAQO,MAAM,kBAAkB;AAAA,EACrB,UAAyB;AAAA,EACzB;AAAA,EACA,oBAA4D;AAAA,EAEpE,YAAY,YAA8C;AACxD,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,OAAO;AACL,SAAK,eAAe,SAAS;AAE7B,WAAO,iBAAiB,YAAY,KAAK,gBAAgB;AAEzD,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEQ,mBAAmB;AACzB,SAAK,oBAAoB,OAAO,QAAQ;AAExC,WAAO,QAAQ,YAAY,IAAI,SAAS;AACtC,WAAK,kBAAmB,MAAM,OAAO,SAAS,IAAI;AAClD,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,mBAAmB,MAAM;AAC/B,SAAK,eAAe,MAAM;AAAA,EAC5B;AAAA,EAEQ,eAAe,gBAAgD;AACrE,UAAM,aAAa,OAAO,SAAS;AAEnC,QAAI,KAAK,YAAY,QAAQ,KAAK,YAAY,YAAY;AACxD;AAAA,IACF;AACA,SAAK,UAAU;AAEf,UAAM,WAAyB;AAAA,MAC7B,KAAK;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,UAAU,SAAS;AAAA,MACnB,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,IACF;AAEA,SAAK,WAAW,QAAQ;AAAA,EAC1B;AAAA,EAEA,UAAU;AACR,WAAO,oBAAoB,YAAY,KAAK,gBAAgB;AAE5D,QAAI,KAAK,mBAAmB;AAC1B,aAAO,QAAQ,YAAY,KAAK;AAAA,IAClC;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -133,7 +133,10 @@ function CartProvider({
133
133
  price: fromMinorUnitsToMajorUnits({
134
134
  amount: item.merchandise.price,
135
135
  currencyCode: analyticsCurrencyCode
136
- })
136
+ }),
137
+ currency: analyticsCurrencyCode,
138
+ productTitle: item.merchandise.product.title,
139
+ variantTitle: item.merchandise.title
137
140
  }
138
141
  ];
139
142
  })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../cart/cart-provider.tsx"],
4
- "sourcesContent": ["/**\n * Use useCart() hook to access cart UI state (itemsCount, isCartOpen, openCart, closeCart). For adding products to cart, use useAddToCart. For buy now functionality, use useBuyNow.\n * @module\n */\n\"use client\";\n\nimport type { Cart, CartLine, CartLinePayload } from \"./cart-types\";\n\nimport React from \"react\";\n\nimport { fromMinorUnitsToMajorUnits } from \"schemas/money\";\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { useAnalyticsOptional } from \"../analytics/analytics-provider\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport {\n addToCartAction,\n createCartAction,\n getOrCreateCartAction,\n removeCartLineItemsAction,\n updateCartDiscountCodesAction,\n updateCartLineItemAction,\n} from \"./cart-actions\";\nimport { recalculateCartCost } from \"./utils/cart-utils\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nclass CartContextError extends CanopyError {}\n\n// Global promise to prevent multiple cart creation attempts\nlet cartCreationPromise: Promise<Cart | null> | null = null;\n\n/**\n * Client-side cart operation to add a line item. Used in editor mode.\n * Merges with existing lines if they match by merchandise ID and selling plan.\n *\n * @param cart - The cart to add the line to\n * @param line - The cart line payload to add\n * @returns The updated cart\n */\nfunction addLineToCart(cart: Cart, line: CartLinePayload): Cart {\n const existingLineIndex = cart.lines.findIndex((existingLine) => {\n return (\n existingLine.merchandise?.id === line.id &&\n // Distinguish by selling plan to avoid merging subscription with one-time\n (line.sellingPlanId ?? null) ===\n (existingLine.sellingPlanAllocation?.sellingPlan.id ?? null)\n );\n });\n\n if (existingLineIndex >= 0) {\n // Update existing line\n const updatedLines = [...cart.lines];\n updatedLines[existingLineIndex] = {\n ...updatedLines[existingLineIndex]!,\n quantity: updatedLines[existingLineIndex]!.quantity + line.quantity,\n };\n return recalculateCartCost({ ...cart, lines: updatedLines });\n } else {\n const optimisticSellingPlanAllocation =\n line.sellingPlanId && line.merchandise\n ? createOptimisticSellingPlanAllocation({\n merchandise: line.merchandise,\n sellingPlanId: line.sellingPlanId,\n currencyCode: cart.cost?.currencyCode ?? \"USD\",\n })\n : null;\n\n const newLine: CartLine = {\n id: `temp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,\n quantity: line.quantity,\n merchandise: line.merchandise ?? null,\n attributes: line.properties ?? [],\n sellingPlanAllocation: optimisticSellingPlanAllocation,\n };\n return recalculateCartCost({ ...cart, lines: [newLine, ...cart.lines] });\n }\n}\n\n/**\n * Client-side cart operation to update a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to update\n * @param line - The cart line payload with updated values\n * @returns The updated cart\n */\nfunction updateLineInCart(cart: Cart, line: CartLinePayload): Cart {\n const updatedLines = cart.lines.map((existingLine) => {\n return existingLine.id === line.id\n ? { ...existingLine, quantity: line.quantity }\n : existingLine;\n });\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Client-side cart operation to remove a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to remove\n * @param lineId - The ID of the line to remove\n * @returns The updated cart\n */\nfunction removeLineFromCart(cart: Cart, lineId: string): Cart {\n const updatedLines = cart.lines.filter((line) => line.id !== lineId);\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Internal cart context type with full functionality.\n * This is for internal use only - external code should use the limited CartContextType.\n */\nexport interface CartContextTypeInternal {\n /** The current cart (can be null) */\n cartData: Cart | null;\n /** Array of cart line items */\n lineItems: CartLine[];\n /** Total price of all items */\n subtotal: number;\n /** Total number of items in cart */\n itemsCount: number;\n /** Currency code for the cart */\n currencyCode: string;\n /** Function to add items to cart */\n addToCart: (\n lines: CartLinePayload[],\n openCartAfterAdd?: boolean,\n ) => Promise<void>;\n /** Function to update cart item quantity */\n updateCartItem: (line: CartLinePayload) => Promise<void>;\n /** Function to remove item from cart */\n removeCartItem: (lineId: string) => Promise<void>;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /** Function to create cart and checkout immediately */\n buyNow: (\n lines: CartLinePayload[],\n discountCodes?: string[],\n ) => Promise<Cart | null>;\n /** URL to redirect to checkout */\n checkoutUrl: string;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n}\n\n// NOTE (Gabe, 2026-01-08): We intentionally duplicate properties from CartContextTypeInternal\n// rather than using Pick<> so that TypeDoc generates expanded documentation for each property.\n/**\n * Public cart context type for LLM use.\n * For adding products to cart, use useAddToCart.\n * For buy now functionality, use useBuyNow.\n */\nexport interface CartContextType {\n /** Total number of items in cart */\n itemsCount: number;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n}\n\nconst CartContext = React.createContext<CartContextTypeInternal | undefined>(\n undefined,\n);\n\n/**\n * Provider component that manages global cart state and operations. Handles both\n * server-side cart synchronization and client-side optimistic updates.\n *\n * @returns A context provider wrapping the children with cart functionality\n */\nexport function CartProvider({\n children,\n cart,\n}: React.PropsWithChildren<{\n /** Initial cart data from the server (can be null) */\n cart: Cart | null;\n}>) {\n const [cartData, setCartData] = React.useState<Cart | null>(cart);\n const [isCartOpen, setIsCartOpen] = React.useState(false);\n const hasInitialized = React.useRef(false);\n const analytics = useAnalyticsOptional();\n\n // Initialize cart on mount\n // eslint-disable-next-line replo/no-use-effect -- Legacy effect - may or may not be necessary\n React.useEffect(() => {\n // Early exit if already initialized\n if (hasInitialized.current) {\n return;\n }\n\n // If there's already a creation in progress, attach to it\n if (cartCreationPromise) {\n void cartCreationPromise.then((cart) => {\n if (cart) {\n setCartData(cart);\n }\n });\n return;\n }\n\n // Mark as initialized to prevent duplicate calls\n hasInitialized.current = true;\n\n // Get existing cart or create new one\n cartCreationPromise = getOrCreateCartAction()\n .then((cart) => {\n if (cart) {\n setCartData(cart);\n } else {\n console.warn(\n \"[Replo] Failed to get or create cart on the server. Reach out to support@replo.app if this persists.\",\n );\n }\n return cart;\n })\n .catch((error) => {\n console.error(\"[Replo] Error getting or creating cart:\", error);\n return null;\n })\n .finally(() => {\n // Reset the promise after completion\n cartCreationPromise = null;\n });\n }, []);\n\n // Derived values - handle null cart gracefully\n const lineItems = cartData?.lines ?? [];\n\n const subtotal =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0 && lineItem.merchandise) {\n sum += lineItem.merchandise.price * lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n const itemsCount =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0) {\n sum += lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n // Cart UI state functions\n const openCart = React.useCallback(() => {\n setIsCartOpen(true);\n\n if (analytics) {\n const analyticsCurrencyCode = cartData?.cost.currencyCode ?? \"USD\";\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.viewCart({\n data: {\n itemCount: itemsCount,\n subtotal: fromMinorUnitsToMajorUnits({\n amount: subtotal,\n currencyCode: analyticsCurrencyCode,\n }),\n currency: cartData?.cost.currencyCode,\n lineItems: cartData?.lines.flatMap((item) => {\n if (!item.merchandise) {\n return [];\n }\n return [\n {\n productId: item.merchandise.product.id,\n variantId: item.merchandise.id,\n quantity: item.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: item.merchandise.price,\n currencyCode: analyticsCurrencyCode,\n }),\n },\n ];\n }),\n },\n });\n }\n }, [analytics, cartData, itemsCount, subtotal]);\n\n const closeCart = React.useCallback(() => {\n setIsCartOpen(false);\n }, []);\n\n // Track the latest update request to avoid updating with stale responses\n const latestUpdateRequestId = React.useRef<string | null>(null);\n\n const addToCart = async (\n lines: CartLinePayload[],\n openCartAfterAdd: boolean = true,\n ) => {\n // If no cart exists, we can't add to it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot add to cart: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n let updatedCart = { ...currentCart };\n for (const line of lines) {\n updatedCart = addLineToCart(updatedCart, line);\n }\n return updatedCart;\n });\n\n if (openCartAfterAdd) {\n openCart();\n }\n\n if (analytics) {\n for (const line of lines) {\n if (!line.merchandise) {\n continue;\n }\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.addToCart({\n data: {\n productId: line.merchandise.product.id,\n variantId: line.merchandise.id,\n quantity: line.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: line.merchandise.price,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n }),\n currency: cartData?.cost.currencyCode,\n productTitle: line.merchandise.product.title,\n variantTitle: line.merchandise.title,\n },\n });\n }\n }\n\n // Direct server update (no debouncing)\n const serverCart = await addToCartAction(lines, cartData.id);\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to add item to cart on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const updateCartItem = async (line: CartLinePayload) => {\n // If no cart exists, we can't update it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Generate a unique request ID for this update\n const requestId = uuidv4();\n latestUpdateRequestId.current = requestId;\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return updateLineInCart(currentCart, line);\n });\n\n // Server update\n const serverCart = await updateCartLineItemAction(line, cartData.id);\n\n // Only update if this is still the latest request\n if (latestUpdateRequestId.current === requestId) {\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to update cart item on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n }\n };\n\n const removeCartItem = async (lineId: string) => {\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n const itemToRemove = cartData.lines.find((item) => item.id === lineId);\n if (analytics && itemToRemove && itemToRemove.merchandise) {\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.removeFromCart({\n data: {\n productId: itemToRemove.merchandise.product.id,\n variantId: itemToRemove.merchandise.id,\n quantity: itemToRemove.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: itemToRemove.merchandise.price,\n currencyCode: cartData.cost.currencyCode,\n }),\n currency: cartData.cost.currencyCode,\n productTitle: itemToRemove.merchandise.product.title,\n variantTitle: itemToRemove.merchandise.title,\n },\n });\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return removeLineFromCart(currentCart, lineId);\n });\n\n // Direct server update (no debouncing)\n const serverCart = await removeCartLineItemsAction([lineId], cartData.id);\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to remove cart item on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const updateDiscountCodes = async (discountCodes: string[]) => {\n // If no cart exists, we can't update discount codes\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update discount codes: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return {\n ...currentCart,\n discountCodes: discountCodes.map((code) => ({\n code,\n applicable: true,\n })),\n };\n });\n\n // Server update\n const serverCart = await updateCartDiscountCodesAction(\n discountCodes,\n cartData.id,\n );\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to update discount codes on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const buyNow = async (lines: CartLinePayload[], discountCodes?: string[]) => {\n // Create a new cart with the specified lines and discount codes\n const cart = await createCartAction({\n lines,\n skipStoringCart: true,\n discountCodes,\n });\n if (cart) {\n return cart;\n } else {\n console.warn(\n \"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists.\",\n );\n return null;\n }\n };\n\n const checkoutUrl = cartData?.checkoutUrl ?? \"/\";\n\n return (\n <CartContext.Provider\n value={{\n cartData,\n lineItems,\n subtotal,\n itemsCount,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n addToCart,\n updateCartItem,\n removeCartItem,\n updateDiscountCodes,\n buyNow,\n checkoutUrl,\n isCartOpen,\n openCart,\n closeCart,\n }}\n >\n {children}\n </CartContext.Provider>\n );\n}\n\n/**\n * @deprecated For internal use only. Use useAddToCart or useBuyNow\n * for cart operations. Use useCart for cart UI state (itemsCount, openCart, closeCart).\n */\nexport function useCartInternal(): CartContextTypeInternal {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCartInternal must be used within a CartProvider\",\n });\n }\n return context;\n}\n\n/**\n * Hook to access cart UI state and controls. For adding products to cart,\n * use useAddToCart. For buy now functionality, use useBuyNow.\n *\n * @example\n * ```tsx\n * import { useCart } from \"@replohq/sdk/cart/cart-provider\";\n *\n * function MyComponent() {\n * const { itemsCount, openCart } = useCart();\n * ...\n * }\n * ```\n * @throws Error if used outside of a CartProvider\n */\nexport function useCart(): CartContextType {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCart must be used within a CartProvider\",\n });\n }\n return {\n itemsCount: context.itemsCount,\n isCartOpen: context.isCartOpen,\n openCart: context.openCart,\n closeCart: context.closeCart,\n updateDiscountCodes: context.updateDiscountCodes,\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Use useCart() hook to access cart UI state (itemsCount, isCartOpen, openCart, closeCart). For adding products to cart, use useAddToCart. For buy now functionality, use useBuyNow.\n * @module\n */\n\"use client\";\n\nimport type { Cart, CartLine, CartLinePayload } from \"./cart-types\";\n\nimport React from \"react\";\n\nimport { fromMinorUnitsToMajorUnits } from \"schemas/money\";\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { useAnalyticsOptional } from \"../analytics/analytics-provider\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport {\n addToCartAction,\n createCartAction,\n getOrCreateCartAction,\n removeCartLineItemsAction,\n updateCartDiscountCodesAction,\n updateCartLineItemAction,\n} from \"./cart-actions\";\nimport { recalculateCartCost } from \"./utils/cart-utils\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nclass CartContextError extends CanopyError {}\n\n// Global promise to prevent multiple cart creation attempts\nlet cartCreationPromise: Promise<Cart | null> | null = null;\n\n/**\n * Client-side cart operation to add a line item. Used in editor mode.\n * Merges with existing lines if they match by merchandise ID and selling plan.\n *\n * @param cart - The cart to add the line to\n * @param line - The cart line payload to add\n * @returns The updated cart\n */\nfunction addLineToCart(cart: Cart, line: CartLinePayload): Cart {\n const existingLineIndex = cart.lines.findIndex((existingLine) => {\n return (\n existingLine.merchandise?.id === line.id &&\n // Distinguish by selling plan to avoid merging subscription with one-time\n (line.sellingPlanId ?? null) ===\n (existingLine.sellingPlanAllocation?.sellingPlan.id ?? null)\n );\n });\n\n if (existingLineIndex >= 0) {\n // Update existing line\n const updatedLines = [...cart.lines];\n updatedLines[existingLineIndex] = {\n ...updatedLines[existingLineIndex]!,\n quantity: updatedLines[existingLineIndex]!.quantity + line.quantity,\n };\n return recalculateCartCost({ ...cart, lines: updatedLines });\n } else {\n const optimisticSellingPlanAllocation =\n line.sellingPlanId && line.merchandise\n ? createOptimisticSellingPlanAllocation({\n merchandise: line.merchandise,\n sellingPlanId: line.sellingPlanId,\n currencyCode: cart.cost?.currencyCode ?? \"USD\",\n })\n : null;\n\n const newLine: CartLine = {\n id: `temp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,\n quantity: line.quantity,\n merchandise: line.merchandise ?? null,\n attributes: line.properties ?? [],\n sellingPlanAllocation: optimisticSellingPlanAllocation,\n };\n return recalculateCartCost({ ...cart, lines: [newLine, ...cart.lines] });\n }\n}\n\n/**\n * Client-side cart operation to update a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to update\n * @param line - The cart line payload with updated values\n * @returns The updated cart\n */\nfunction updateLineInCart(cart: Cart, line: CartLinePayload): Cart {\n const updatedLines = cart.lines.map((existingLine) => {\n return existingLine.id === line.id\n ? { ...existingLine, quantity: line.quantity }\n : existingLine;\n });\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Client-side cart operation to remove a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to remove\n * @param lineId - The ID of the line to remove\n * @returns The updated cart\n */\nfunction removeLineFromCart(cart: Cart, lineId: string): Cart {\n const updatedLines = cart.lines.filter((line) => line.id !== lineId);\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Internal cart context type with full functionality.\n * This is for internal use only - external code should use the limited CartContextType.\n */\nexport interface CartContextTypeInternal {\n /** The current cart (can be null) */\n cartData: Cart | null;\n /** Array of cart line items */\n lineItems: CartLine[];\n /** Total price of all items */\n subtotal: number;\n /** Total number of items in cart */\n itemsCount: number;\n /** Currency code for the cart */\n currencyCode: string;\n /** Function to add items to cart */\n addToCart: (\n lines: CartLinePayload[],\n openCartAfterAdd?: boolean,\n ) => Promise<void>;\n /** Function to update cart item quantity */\n updateCartItem: (line: CartLinePayload) => Promise<void>;\n /** Function to remove item from cart */\n removeCartItem: (lineId: string) => Promise<void>;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /** Function to create cart and checkout immediately */\n buyNow: (\n lines: CartLinePayload[],\n discountCodes?: string[],\n ) => Promise<Cart | null>;\n /** URL to redirect to checkout */\n checkoutUrl: string;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n}\n\n// NOTE (Gabe, 2026-01-08): We intentionally duplicate properties from CartContextTypeInternal\n// rather than using Pick<> so that TypeDoc generates expanded documentation for each property.\n/**\n * Public cart context type for LLM use.\n * For adding products to cart, use useAddToCart.\n * For buy now functionality, use useBuyNow.\n */\nexport interface CartContextType {\n /** Total number of items in cart */\n itemsCount: number;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n}\n\nconst CartContext = React.createContext<CartContextTypeInternal | undefined>(\n undefined,\n);\n\n/**\n * Provider component that manages global cart state and operations. Handles both\n * server-side cart synchronization and client-side optimistic updates.\n *\n * @returns A context provider wrapping the children with cart functionality\n */\nexport function CartProvider({\n children,\n cart,\n}: React.PropsWithChildren<{\n /** Initial cart data from the server (can be null) */\n cart: Cart | null;\n}>) {\n const [cartData, setCartData] = React.useState<Cart | null>(cart);\n const [isCartOpen, setIsCartOpen] = React.useState(false);\n const hasInitialized = React.useRef(false);\n const analytics = useAnalyticsOptional();\n\n // Initialize cart on mount\n // eslint-disable-next-line replo/no-use-effect -- Legacy effect - may or may not be necessary\n React.useEffect(() => {\n // Early exit if already initialized\n if (hasInitialized.current) {\n return;\n }\n\n // If there's already a creation in progress, attach to it\n if (cartCreationPromise) {\n void cartCreationPromise.then((cart) => {\n if (cart) {\n setCartData(cart);\n }\n });\n return;\n }\n\n // Mark as initialized to prevent duplicate calls\n hasInitialized.current = true;\n\n // Get existing cart or create new one\n cartCreationPromise = getOrCreateCartAction()\n .then((cart) => {\n if (cart) {\n setCartData(cart);\n } else {\n console.warn(\n \"[Replo] Failed to get or create cart on the server. Reach out to support@replo.app if this persists.\",\n );\n }\n return cart;\n })\n .catch((error) => {\n console.error(\"[Replo] Error getting or creating cart:\", error);\n return null;\n })\n .finally(() => {\n // Reset the promise after completion\n cartCreationPromise = null;\n });\n }, []);\n\n // Derived values - handle null cart gracefully\n const lineItems = cartData?.lines ?? [];\n\n const subtotal =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0 && lineItem.merchandise) {\n sum += lineItem.merchandise.price * lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n const itemsCount =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0) {\n sum += lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n // Cart UI state functions\n const openCart = React.useCallback(() => {\n setIsCartOpen(true);\n\n if (analytics) {\n const analyticsCurrencyCode = cartData?.cost.currencyCode ?? \"USD\";\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.viewCart({\n data: {\n itemCount: itemsCount,\n subtotal: fromMinorUnitsToMajorUnits({\n amount: subtotal,\n currencyCode: analyticsCurrencyCode,\n }),\n currency: cartData?.cost.currencyCode,\n lineItems: cartData?.lines.flatMap((item) => {\n if (!item.merchandise) {\n return [];\n }\n return [\n {\n productId: item.merchandise.product.id,\n variantId: item.merchandise.id,\n quantity: item.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: item.merchandise.price,\n currencyCode: analyticsCurrencyCode,\n }),\n currency: analyticsCurrencyCode,\n productTitle: item.merchandise.product.title,\n variantTitle: item.merchandise.title,\n },\n ];\n }),\n },\n });\n }\n }, [analytics, cartData, itemsCount, subtotal]);\n\n const closeCart = React.useCallback(() => {\n setIsCartOpen(false);\n }, []);\n\n // Track the latest update request to avoid updating with stale responses\n const latestUpdateRequestId = React.useRef<string | null>(null);\n\n const addToCart = async (\n lines: CartLinePayload[],\n openCartAfterAdd: boolean = true,\n ) => {\n // If no cart exists, we can't add to it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot add to cart: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n let updatedCart = { ...currentCart };\n for (const line of lines) {\n updatedCart = addLineToCart(updatedCart, line);\n }\n return updatedCart;\n });\n\n if (openCartAfterAdd) {\n openCart();\n }\n\n if (analytics) {\n for (const line of lines) {\n if (!line.merchandise) {\n continue;\n }\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.addToCart({\n data: {\n productId: line.merchandise.product.id,\n variantId: line.merchandise.id,\n quantity: line.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: line.merchandise.price,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n }),\n currency: cartData?.cost.currencyCode,\n productTitle: line.merchandise.product.title,\n variantTitle: line.merchandise.title,\n },\n });\n }\n }\n\n // Direct server update (no debouncing)\n const serverCart = await addToCartAction(lines, cartData.id);\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to add item to cart on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const updateCartItem = async (line: CartLinePayload) => {\n // If no cart exists, we can't update it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Generate a unique request ID for this update\n const requestId = uuidv4();\n latestUpdateRequestId.current = requestId;\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return updateLineInCart(currentCart, line);\n });\n\n // Server update\n const serverCart = await updateCartLineItemAction(line, cartData.id);\n\n // Only update if this is still the latest request\n if (latestUpdateRequestId.current === requestId) {\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to update cart item on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n }\n };\n\n const removeCartItem = async (lineId: string) => {\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n const itemToRemove = cartData.lines.find((item) => item.id === lineId);\n if (analytics && itemToRemove && itemToRemove.merchandise) {\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.removeFromCart({\n data: {\n productId: itemToRemove.merchandise.product.id,\n variantId: itemToRemove.merchandise.id,\n quantity: itemToRemove.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: itemToRemove.merchandise.price,\n currencyCode: cartData.cost.currencyCode,\n }),\n currency: cartData.cost.currencyCode,\n productTitle: itemToRemove.merchandise.product.title,\n variantTitle: itemToRemove.merchandise.title,\n },\n });\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return removeLineFromCart(currentCart, lineId);\n });\n\n // Direct server update (no debouncing)\n const serverCart = await removeCartLineItemsAction([lineId], cartData.id);\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to remove cart item on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const updateDiscountCodes = async (discountCodes: string[]) => {\n // If no cart exists, we can't update discount codes\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update discount codes: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return {\n ...currentCart,\n discountCodes: discountCodes.map((code) => ({\n code,\n applicable: true,\n })),\n };\n });\n\n // Server update\n const serverCart = await updateCartDiscountCodesAction(\n discountCodes,\n cartData.id,\n );\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to update discount codes on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const buyNow = async (lines: CartLinePayload[], discountCodes?: string[]) => {\n // Create a new cart with the specified lines and discount codes\n const cart = await createCartAction({\n lines,\n skipStoringCart: true,\n discountCodes,\n });\n if (cart) {\n return cart;\n } else {\n console.warn(\n \"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists.\",\n );\n return null;\n }\n };\n\n const checkoutUrl = cartData?.checkoutUrl ?? \"/\";\n\n return (\n <CartContext.Provider\n value={{\n cartData,\n lineItems,\n subtotal,\n itemsCount,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n addToCart,\n updateCartItem,\n removeCartItem,\n updateDiscountCodes,\n buyNow,\n checkoutUrl,\n isCartOpen,\n openCart,\n closeCart,\n }}\n >\n {children}\n </CartContext.Provider>\n );\n}\n\n/**\n * @deprecated For internal use only. Use useAddToCart or useBuyNow\n * for cart operations. Use useCart for cart UI state (itemsCount, openCart, closeCart).\n */\nexport function useCartInternal(): CartContextTypeInternal {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCartInternal must be used within a CartProvider\",\n });\n }\n return context;\n}\n\n/**\n * Hook to access cart UI state and controls. For adding products to cart,\n * use useAddToCart. For buy now functionality, use useBuyNow.\n *\n * @example\n * ```tsx\n * import { useCart } from \"@replohq/sdk/cart/cart-provider\";\n *\n * function MyComponent() {\n * const { itemsCount, openCart } = useCart();\n * ...\n * }\n * ```\n * @throws Error if used outside of a CartProvider\n */\nexport function useCart(): CartContextType {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCart must be used within a CartProvider\",\n });\n }\n return {\n itemsCount: context.itemsCount,\n isCartOpen: context.isCartOpen,\n openCart: context.openCart,\n closeCart: context.closeCart,\n updateDiscountCodes: context.updateDiscountCodes,\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["cart"]
7
7
  }
@@ -1,9 +1,9 @@
1
1
  import type { ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
2
2
  /**
3
3
  * Creates and appends the script nodes for `descriptors`, skipping node ids
4
- * already in the document, and returns the created nodes so the caller owns
5
- * their removal. Lives outside replo-scripts.tsx so the component file only
6
- * exports components (HMR) and this stays off the published public surface.
4
+ * already in the document, and returns only the nodes created by this call.
5
+ * Lives outside replo-scripts.tsx so the component file only exports components
6
+ * (HMR) and this stays off the published public surface.
7
7
  */
8
8
  export declare function injectScriptDescriptors({ baseId, descriptors, extraAttributes, }: {
9
9
  baseId: string;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../consent/inject-script-descriptors.ts"],
4
- "sourcesContent": ["import type { ScriptTagDescriptor } from \"schemas/generated/consent\";\n\n/**\n * Creates and appends the script nodes for `descriptors`, skipping node ids\n * already in the document, and returns the created nodes so the caller owns\n * their removal. Lives outside replo-scripts.tsx so the component file only\n * exports components (HMR) and this stays off the published public surface.\n */\nexport function injectScriptDescriptors({\n baseId,\n descriptors,\n extraAttributes,\n}: {\n baseId: string;\n descriptors: ScriptTagDescriptor[];\n /** Applied last, so a consent platform's `text/plain` beats a descriptor's own type. */\n extraAttributes?: Record<string, string>;\n}): HTMLScriptElement[] {\n const createdNodes: HTMLScriptElement[] = [];\n descriptors.forEach((descriptor, index) => {\n const nodeId = `${baseId}#${index}`;\n if (document.querySelector(`script[data-replo-script-id=\"${nodeId}\"]`)) {\n return;\n }\n const scriptElement = document.createElement(\"script\");\n scriptElement.dataset.reploScriptId = nodeId;\n if (descriptor.kind === \"external\") {\n scriptElement.src = descriptor.src;\n scriptElement.async = true;\n for (const [attributeName, attributeValue] of Object.entries(\n descriptor.attributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n } else if (descriptor.module && extraAttributes?.type === \"text/plain\") {\n // A consent platform restores blocked tags as CLASSIC scripts, which is a\n // SyntaxError for module source. The blocked body is a classic shim that\n // re-creates the real module tag when the platform activates it.\n scriptElement.textContent = buildModuleShim(descriptor.body);\n } else {\n // Module snippets (top-level await, import()) are a SyntaxError as\n // classic scripts, so the type must survive injection.\n if (descriptor.module) {\n scriptElement.type = \"module\";\n }\n scriptElement.textContent = descriptor.body;\n }\n for (const [attributeName, attributeValue] of Object.entries(\n extraAttributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n document.head.append(scriptElement);\n createdNodes.push(scriptElement);\n });\n return createdNodes;\n}\n\nfunction buildModuleShim(body: string): string {\n return [\n 'var moduleScript = document.createElement(\"script\");',\n 'moduleScript.type = \"module\";',\n `moduleScript.textContent = ${JSON.stringify(body)};`,\n \"document.head.append(moduleScript);\",\n ].join(\"\\n\");\n}\n"],
4
+ "sourcesContent": ["import type { ScriptTagDescriptor } from \"schemas/generated/consent\";\n\n/**\n * Creates and appends the script nodes for `descriptors`, skipping node ids\n * already in the document, and returns only the nodes created by this call.\n * Lives outside replo-scripts.tsx so the component file only exports components\n * (HMR) and this stays off the published public surface.\n */\nexport function injectScriptDescriptors({\n baseId,\n descriptors,\n extraAttributes,\n}: {\n baseId: string;\n descriptors: ScriptTagDescriptor[];\n /** Applied last, so a consent platform's `text/plain` beats a descriptor's own type. */\n extraAttributes?: Record<string, string>;\n}): HTMLScriptElement[] {\n const createdNodes: HTMLScriptElement[] = [];\n descriptors.forEach((descriptor, index) => {\n const nodeId = `${baseId}#${index}`;\n if (document.querySelector(`script[data-replo-script-id=\"${nodeId}\"]`)) {\n return;\n }\n const scriptElement = document.createElement(\"script\");\n scriptElement.dataset.reploScriptId = nodeId;\n if (descriptor.kind === \"external\") {\n scriptElement.src = descriptor.src;\n scriptElement.async = true;\n for (const [attributeName, attributeValue] of Object.entries(\n descriptor.attributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n } else if (descriptor.module && extraAttributes?.type === \"text/plain\") {\n // A consent platform restores blocked tags as CLASSIC scripts, which is a\n // SyntaxError for module source. The blocked body is a classic shim that\n // re-creates the real module tag when the platform activates it.\n scriptElement.textContent = buildModuleShim(descriptor.body);\n } else {\n // Module snippets (top-level await, import()) are a SyntaxError as\n // classic scripts, so the type must survive injection.\n if (descriptor.module) {\n scriptElement.type = \"module\";\n }\n scriptElement.textContent = descriptor.body;\n }\n for (const [attributeName, attributeValue] of Object.entries(\n extraAttributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n document.head.append(scriptElement);\n createdNodes.push(scriptElement);\n });\n return createdNodes;\n}\n\nfunction buildModuleShim(body: string): string {\n return [\n 'var moduleScript = document.createElement(\"script\");',\n 'moduleScript.type = \"module\";',\n `moduleScript.textContent = ${JSON.stringify(body)};`,\n \"document.head.append(moduleScript);\",\n ].join(\"\\n\");\n}\n"],
5
5
  "mappings": "AAQO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAKwB;AACtB,QAAM,eAAoC,CAAC;AAC3C,cAAY,QAAQ,CAAC,YAAY,UAAU;AACzC,UAAM,SAAS,GAAG,MAAM,IAAI,KAAK;AACjC,QAAI,SAAS,cAAc,gCAAgC,MAAM,IAAI,GAAG;AACtE;AAAA,IACF;AACA,UAAM,gBAAgB,SAAS,cAAc,QAAQ;AACrD,kBAAc,QAAQ,gBAAgB;AACtC,QAAI,WAAW,SAAS,YAAY;AAClC,oBAAc,MAAM,WAAW;AAC/B,oBAAc,QAAQ;AACtB,iBAAW,CAAC,eAAe,cAAc,KAAK,OAAO;AAAA,QACnD,WAAW,cAAc,CAAC;AAAA,MAC5B,GAAG;AACD,sBAAc,aAAa,eAAe,cAAc;AAAA,MAC1D;AAAA,IACF,WAAW,WAAW,UAAU,iBAAiB,SAAS,cAAc;AAItE,oBAAc,cAAc,gBAAgB,WAAW,IAAI;AAAA,IAC7D,OAAO;AAGL,UAAI,WAAW,QAAQ;AACrB,sBAAc,OAAO;AAAA,MACvB;AACA,oBAAc,cAAc,WAAW;AAAA,IACzC;AACA,eAAW,CAAC,eAAe,cAAc,KAAK,OAAO;AAAA,MACnD,mBAAmB,CAAC;AAAA,IACtB,GAAG;AACD,oBAAc,aAAa,eAAe,cAAc;AAAA,IAC1D;AACA,aAAS,KAAK,OAAO,aAAa;AAClC,iBAAa,KAAK,aAAa;AAAA,EACjC,CAAC;AACD,SAAO;AACT;AAEA,SAAS,gBAAgB,MAAsB;AAC7C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,8BAA8B,KAAK,UAAU,IAAI,CAAC;AAAA,IAClD;AAAA,EACF,EAAE,KAAK,IAAI;AACb;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import type { ConsentCategory, ReploScriptEntry, ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
2
+ import { Component } from "react";
2
3
  /**
3
4
  * Injects the resolved tags into `document.head` once a caller has decided
4
5
  * consent allows it. Reused both for author-managed `ReploScripts` entries and
@@ -9,12 +10,16 @@ import type { ConsentCategory, ReploScriptEntry, ScriptTagDescriptor } from "../
9
10
  * `data-replo-script-id` (plus a per-tag index) so re-renders and React Strict
10
11
  * Mode double-invokes do not double-inject.
11
12
  */
12
- export declare function InjectedScript({ baseId, descriptors, requiredConsent, }: {
13
+ type InjectedScriptProps = {
13
14
  baseId: string;
14
15
  descriptors: ScriptTagDescriptor[];
15
16
  /** Used under a delegated platform to derive the attributes that gate the tag. */
16
17
  requiredConsent?: ConsentCategory[];
17
- }): null;
18
+ };
19
+ export declare class InjectedScript extends Component<InjectedScriptProps> {
20
+ componentDidMount(): void;
21
+ render(): null;
22
+ }
18
23
  /**
19
24
  * The single registry component for all managed tracking scripts on the site.
20
25
  * Authored once in `app/layout.tsx`; the agent/miniapp only edit the `scripts`
@@ -28,3 +33,4 @@ export declare function InjectedScript({ baseId, descriptors, requiredConsent, }
28
33
  export declare function ReploScripts({ scripts }: {
29
34
  scripts: ReploScriptEntry[];
30
35
  }): import("react/jsx-runtime").JSX.Element;
36
+ export {};
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect } from "react";
3
+ import { Component } from "react";
4
4
  import {
5
5
  activateBlockedScripts,
6
6
  buildGrantMarker,
@@ -55,16 +55,9 @@ function descriptorsFor(entry) {
55
55
  }
56
56
  return buildScriptTags({ type: entry.type, identifier: entry.identifier });
57
57
  }
58
- function InjectedScript({
59
- baseId,
60
- descriptors,
61
- requiredConsent = []
62
- }) {
63
- const injectionKey = `${baseId}|${JSON.stringify(descriptors)}|${requiredConsent.join(",")}`;
64
- useEffect(() => {
65
- if (typeof document === "undefined") {
66
- return;
67
- }
58
+ class InjectedScript extends Component {
59
+ componentDidMount() {
60
+ const { baseId, descriptors, requiredConsent = [] } = this.props;
68
61
  const platform = findDelegatedPlatform();
69
62
  const blockingAttributes = platform ? getBlockingAttributes(platform, requiredConsent) : {};
70
63
  const blockedByPlatform = "type" in blockingAttributes;
@@ -76,42 +69,65 @@ function InjectedScript({
76
69
  if (blockedByPlatform && createdNodes.length > 0) {
77
70
  activateBlockedScripts();
78
71
  }
79
- return () => {
80
- for (const node of createdNodes) {
81
- node.remove();
82
- }
83
- };
84
- }, [injectionKey]);
85
- return null;
72
+ }
73
+ render() {
74
+ return null;
75
+ }
76
+ }
77
+ class ScriptRegistration extends Component {
78
+ componentDidMount() {
79
+ registerScript(this.props.script);
80
+ }
81
+ componentWillUnmount() {
82
+ unregisterScript(this.props.script.id);
83
+ }
84
+ render() {
85
+ return null;
86
+ }
86
87
  }
87
88
  function ManagedScript({ entry }) {
88
89
  const consent = useConsent();
89
90
  const requiredConsent = requiredConsentFor(entry);
90
91
  const id = scriptId(entry);
91
- const consentKey = requiredConsent.join(",");
92
- useEffect(() => {
93
- registerScript({ id, type: entry.type, requiredConsent });
94
- return () => unregisterScript(id);
95
- }, [id, entry.type, consentKey]);
96
- if (!findDelegatedPlatform() && !isConsentAllowed({ state: consent, requiredConsent })) {
92
+ const isBlockedByNativeConsent = !findDelegatedPlatform() && !isConsentAllowed({ state: consent, requiredConsent });
93
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
94
+ /* @__PURE__ */ jsx(
95
+ ScriptRegistration,
96
+ {
97
+ script: { id, type: entry.type, requiredConsent }
98
+ },
99
+ `${id}:${requiredConsent.join(",")}`
100
+ ),
101
+ !isBlockedByNativeConsent && /* @__PURE__ */ jsx(
102
+ InjectedScript,
103
+ {
104
+ baseId: id,
105
+ descriptors: descriptorsFor(entry),
106
+ requiredConsent
107
+ }
108
+ )
109
+ ] });
110
+ }
111
+ class ConsentWindowApi extends Component {
112
+ uninstall = () => {
113
+ };
114
+ componentDidMount() {
115
+ this.uninstall = installConsentWindowApi();
116
+ }
117
+ componentWillUnmount() {
118
+ this.uninstall();
119
+ }
120
+ render() {
97
121
  return null;
98
122
  }
99
- return /* @__PURE__ */ jsx(
100
- InjectedScript,
101
- {
102
- baseId: id,
103
- descriptors: descriptorsFor(entry),
104
- requiredConsent
105
- }
106
- );
107
123
  }
108
124
  function ReploScripts({ scripts }) {
109
- useEffect(() => installConsentWindowApi(), []);
110
125
  const platform = findConsentPlatform(scripts);
111
126
  if (platform) {
112
127
  enableConsentDelegation(platform);
113
128
  }
114
129
  return /* @__PURE__ */ jsxs(Fragment, { children: [
130
+ /* @__PURE__ */ jsx(ConsentWindowApi, {}),
115
131
  scripts.filter((entry) => !isConsentPlatformEntry(entry)).map((entry) => /* @__PURE__ */ jsx(ManagedScript, { entry }, scriptId(entry))),
116
132
  platform && !platform.isCookiebot && OPTIONAL_CONSENT_CATEGORIES.map((category) => /* @__PURE__ */ jsx(
117
133
  InjectedScript,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../consent/replo-scripts.tsx"],
4
- "sourcesContent": ["\"use client\";\n\nimport type {\n ConsentCategory,\n ReploScriptEntry,\n ScriptTagDescriptor,\n} from \"schemas/generated/consent\";\n\nimport { useEffect } from \"react\";\n\nimport {\n activateBlockedScripts,\n buildGrantMarker,\n enableConsentDelegation,\n findConsentPlatform,\n findDelegatedPlatform,\n getBlockingAttributes,\n isConsentPlatformEntry,\n OPTIONAL_CONSENT_CATEGORIES,\n} from \"./consent-platform\";\nimport { isConsentAllowed, useConsent } from \"./consent-store\";\nimport { injectScriptDescriptors } from \"./inject-script-descriptors\";\nimport { registerScript, unregisterScript } from \"./script-registration-store\";\nimport { buildScriptTags, defaultConsentFor } from \"./script-snippets\";\nimport { installConsentWindowApi } from \"./window-api\";\n\n/**\n * Stable identity for an entry, used as React key and DOM dedupe key. `snippet`\n * and `custom` entries key on their `id`; identifier providers on type+id.\n */\nfunction scriptId(entry: ReploScriptEntry): string {\n if (entry.type === \"custom\") {\n return `custom:${entry.id}`;\n }\n if (entry.type === \"snippet\") {\n return `snippet:${entry.id}`;\n }\n if (entry.type === \"consentPlatform\") {\n return `consent-platform:${entry.id}`;\n }\n return `${entry.type}:${entry.identifier}`;\n}\n\nfunction requiredConsentFor(entry: ReploScriptEntry): ConsentCategory[] {\n if (entry.type === \"custom\" || entry.type === \"snippet\") {\n return entry.requiredConsent;\n }\n // Platform entries are filtered out before render; arms exist for the types.\n if (entry.type === \"consentPlatform\" || entry.type === \"Cookiebot\") {\n return [];\n }\n return entry.requiredConsent ?? defaultConsentFor(entry.type);\n}\n\n/**\n * Resolves an entry to the concrete tags to inject. An identifier provider may\n * resolve to multiple tags (GA4 = external loader + inline config); a `snippet`\n * entry injects its pasted body inline; a `custom` entry is a single external\n * `src` or inline `body`.\n */\nfunction descriptorsFor(entry: ReploScriptEntry): ScriptTagDescriptor[] {\n if (entry.type === \"custom\") {\n if (entry.src) {\n return [{ kind: \"external\", src: entry.src }];\n }\n if (entry.body) {\n return [{ kind: \"inline\", body: entry.body }];\n }\n return [];\n }\n if (entry.type === \"snippet\") {\n return [{ kind: \"inline\", body: entry.body, module: entry.module }];\n }\n // Platform entries are filtered out before render; the arm exists for the\n // types \u2014 the loader injects via the platform config, not per-entry.\n if (entry.type === \"consentPlatform\" || entry.type === \"Cookiebot\") {\n return [];\n }\n return buildScriptTags({ type: entry.type, identifier: entry.identifier });\n}\n\n/**\n * Injects the resolved tags into `document.head` once a caller has decided\n * consent allows it. Reused both for author-managed `ReploScripts` entries and\n * for the implicit Replo first-party pixel.\n *\n * DOM insertion (not JSX) is required because inline `<script>` bodies set via\n * React's `dangerouslySetInnerHTML` never execute. Each node is tagged with\n * `data-replo-script-id` (plus a per-tag index) so re-renders and React Strict\n * Mode double-invokes do not double-inject.\n */\nexport function InjectedScript({\n baseId,\n descriptors,\n requiredConsent = [],\n}: {\n baseId: string;\n descriptors: ScriptTagDescriptor[];\n /** Used under a delegated platform to derive the attributes that gate the tag. */\n requiredConsent?: ConsentCategory[];\n}) {\n // Re-inject only when the resolved tags actually change.\n const injectionKey = `${baseId}|${JSON.stringify(descriptors)}|${requiredConsent.join(\",\")}`;\n\n // eslint-disable-next-line replo/no-use-effect -- script injection is a DOM side effect that must run after mount and on consent changes\n useEffect(() => {\n if (typeof document === \"undefined\") {\n return;\n }\n // Resolved in the effect, not render: delegation is published mid-render, after\n // earlier-mounted tags (the first-party pixel) render but before any effect runs.\n const platform = findDelegatedPlatform();\n const blockingAttributes = platform\n ? getBlockingAttributes(platform, requiredConsent)\n : {};\n const blockedByPlatform = \"type\" in blockingAttributes;\n const createdNodes = injectScriptDescriptors({\n baseId,\n descriptors,\n extraAttributes: blockingAttributes,\n });\n\n // Belt-and-suspenders for Cookiebot, whose loader may have scanned already.\n if (blockedByPlatform && createdNodes.length > 0) {\n activateBlockedScripts();\n }\n\n return () => {\n // Removing the node does not unload an already-loaded vendor (see the plan's\n // revocation note); full teardown is a reload triggered by the banner. This\n // cleanup keeps the DOM tidy and prevents duplicates across remounts.\n for (const node of createdNodes) {\n node.remove();\n }\n };\n }, [injectionKey]);\n\n return null;\n}\n\nfunction ManagedScript({ entry }: { entry: ReploScriptEntry }) {\n const consent = useConsent();\n const requiredConsent = requiredConsentFor(entry);\n const id = scriptId(entry);\n const consentKey = requiredConsent.join(\",\");\n\n // eslint-disable-next-line replo/no-use-effect -- register/unregister with the singleton so the analytics provider can gate sinks by what's on the page\n useEffect(() => {\n registerScript({ id, type: entry.type, requiredConsent });\n return () => unregisterScript(id);\n }, [id, entry.type, consentKey]);\n\n // Under a consent platform the tag is injected and the platform decides when\n // it runs.\n if (\n !findDelegatedPlatform() &&\n !isConsentAllowed({ state: consent, requiredConsent })\n ) {\n return null;\n }\n return (\n <InjectedScript\n baseId={id}\n descriptors={descriptorsFor(entry)}\n requiredConsent={requiredConsent}\n />\n );\n}\n\n/**\n * The single registry component for all managed tracking scripts on the site.\n * Authored once in `app/layout.tsx`; the agent/miniapp only edit the `scripts`\n * array. Each entry registers itself and is gated + injected per consent.\n *\n * A consent-platform entry (`Cookiebot`, or a generic `consentPlatform`) takes\n * over consent site-wide: scripts carry the platform's blocking attributes, its\n * loader injects after them so its startup scan sees the full set, and Replo's\n * own gates follow the platform instead of the native banner.\n */\nexport function ReploScripts({ scripts }: { scripts: ReploScriptEntry[] }) {\n // eslint-disable-next-line replo/no-use-effect -- install the window.Replo.customerPrivacy API + change event once the consent runtime mounts\n useEffect(() => installConsentWindowApi(), []);\n\n // During render, not an effect: the first-party pixel mounts earlier and its\n // injection effect must see this.\n const platform = findConsentPlatform(scripts);\n if (platform) {\n enableConsentDelegation(platform);\n }\n\n // The loader renders LAST: children's effects run in order, so every blocked\n // tag is in the DOM when the loader's startup scan runs \u2014 generic platforms\n // need no Cookiebot-style re-scan API.\n return (\n <>\n {scripts\n .filter((entry) => !isConsentPlatformEntry(entry))\n .map((entry) => (\n <ManagedScript key={scriptId(entry)} entry={entry} />\n ))}\n {platform &&\n !platform.isCookiebot &&\n OPTIONAL_CONSENT_CATEGORIES.map((category) => (\n <InjectedScript\n key={category}\n baseId={`consent-platform:${platform.id}:grant:${category}`}\n descriptors={[buildGrantMarker(category)]}\n requiredConsent={[category]}\n />\n ))}\n {platform && (\n <InjectedScript\n baseId={`consent-platform:${platform.id}`}\n descriptors={platform.loader}\n />\n )}\n </>\n );\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\";\n\nimport type {\n ConsentCategory,\n ReploScriptEntry,\n ScriptTagDescriptor,\n} from \"schemas/generated/consent\";\nimport type { RegisteredScript } from \"./script-registration-store\";\n\nimport { Component } from \"react\";\n\nimport {\n activateBlockedScripts,\n buildGrantMarker,\n enableConsentDelegation,\n findConsentPlatform,\n findDelegatedPlatform,\n getBlockingAttributes,\n isConsentPlatformEntry,\n OPTIONAL_CONSENT_CATEGORIES,\n} from \"./consent-platform\";\nimport { isConsentAllowed, useConsent } from \"./consent-store\";\nimport { injectScriptDescriptors } from \"./inject-script-descriptors\";\nimport { registerScript, unregisterScript } from \"./script-registration-store\";\nimport { buildScriptTags, defaultConsentFor } from \"./script-snippets\";\nimport { installConsentWindowApi } from \"./window-api\";\n\n/**\n * Stable identity for an entry, used as React key and DOM dedupe key. `snippet`\n * and `custom` entries key on their `id`; identifier providers on type+id.\n */\nfunction scriptId(entry: ReploScriptEntry): string {\n if (entry.type === \"custom\") {\n return `custom:${entry.id}`;\n }\n if (entry.type === \"snippet\") {\n return `snippet:${entry.id}`;\n }\n if (entry.type === \"consentPlatform\") {\n return `consent-platform:${entry.id}`;\n }\n return `${entry.type}:${entry.identifier}`;\n}\n\nfunction requiredConsentFor(entry: ReploScriptEntry): ConsentCategory[] {\n if (entry.type === \"custom\" || entry.type === \"snippet\") {\n return entry.requiredConsent;\n }\n // Platform entries are filtered out before render; arms exist for the types.\n if (entry.type === \"consentPlatform\" || entry.type === \"Cookiebot\") {\n return [];\n }\n return entry.requiredConsent ?? defaultConsentFor(entry.type);\n}\n\n/**\n * Resolves an entry to the concrete tags to inject. An identifier provider may\n * resolve to multiple tags (GA4 = external loader + inline config); a `snippet`\n * entry injects its pasted body inline; a `custom` entry is a single external\n * `src` or inline `body`.\n */\nfunction descriptorsFor(entry: ReploScriptEntry): ScriptTagDescriptor[] {\n if (entry.type === \"custom\") {\n if (entry.src) {\n return [{ kind: \"external\", src: entry.src }];\n }\n if (entry.body) {\n return [{ kind: \"inline\", body: entry.body }];\n }\n return [];\n }\n if (entry.type === \"snippet\") {\n return [{ kind: \"inline\", body: entry.body, module: entry.module }];\n }\n // Platform entries load from their platform config rather than per-entry.\n if (entry.type === \"consentPlatform\" || entry.type === \"Cookiebot\") {\n return [];\n }\n return buildScriptTags({ type: entry.type, identifier: entry.identifier });\n}\n\n/**\n * Injects the resolved tags into `document.head` once a caller has decided\n * consent allows it. Reused both for author-managed `ReploScripts` entries and\n * for the implicit Replo first-party pixel.\n *\n * DOM insertion (not JSX) is required because inline `<script>` bodies set via\n * React's `dangerouslySetInnerHTML` never execute. Each node is tagged with\n * `data-replo-script-id` (plus a per-tag index) so re-renders and React Strict\n * Mode double-invokes do not double-inject.\n */\ntype InjectedScriptProps = {\n baseId: string;\n descriptors: ScriptTagDescriptor[];\n /** Used under a delegated platform to derive the attributes that gate the tag. */\n requiredConsent?: ConsentCategory[];\n};\n\nexport class InjectedScript extends Component<InjectedScriptProps> {\n componentDidMount() {\n const { baseId, descriptors, requiredConsent = [] } = this.props;\n const platform = findDelegatedPlatform();\n const blockingAttributes = platform\n ? getBlockingAttributes(platform, requiredConsent)\n : {};\n const blockedByPlatform = \"type\" in blockingAttributes;\n const createdNodes = injectScriptDescriptors({\n baseId,\n descriptors,\n extraAttributes: blockingAttributes,\n });\n\n // Cookiebot may finish its startup scan before late consent-gated tags mount.\n if (blockedByPlatform && createdNodes.length > 0) {\n activateBlockedScripts();\n }\n // NOTE (Ryan, 2026-09-02): Executed scripts stay mounted because removing their nodes cannot unload the vendor.\n }\n\n render() {\n return null;\n }\n}\n\nclass ScriptRegistration extends Component<{ script: RegisteredScript }> {\n componentDidMount() {\n registerScript(this.props.script);\n }\n\n componentWillUnmount() {\n unregisterScript(this.props.script.id);\n }\n\n render() {\n return null;\n }\n}\n\nfunction ManagedScript({ entry }: { entry: ReploScriptEntry }) {\n const consent = useConsent();\n const requiredConsent = requiredConsentFor(entry);\n const id = scriptId(entry);\n const isBlockedByNativeConsent =\n !findDelegatedPlatform() &&\n !isConsentAllowed({ state: consent, requiredConsent });\n\n return (\n <>\n <ScriptRegistration\n key={`${id}:${requiredConsent.join(\",\")}`}\n script={{ id, type: entry.type, requiredConsent }}\n />\n {!isBlockedByNativeConsent && (\n <InjectedScript\n baseId={id}\n descriptors={descriptorsFor(entry)}\n requiredConsent={requiredConsent}\n />\n )}\n </>\n );\n}\n\nclass ConsentWindowApi extends Component {\n private uninstall = () => {};\n\n componentDidMount() {\n this.uninstall = installConsentWindowApi();\n }\n\n componentWillUnmount() {\n this.uninstall();\n }\n\n render() {\n return null;\n }\n}\n\n/**\n * The single registry component for all managed tracking scripts on the site.\n * Authored once in `app/layout.tsx`; the agent/miniapp only edit the `scripts`\n * array. Each entry registers itself and is gated + injected per consent.\n *\n * A consent-platform entry (`Cookiebot`, or a generic `consentPlatform`) takes\n * over consent site-wide: scripts carry the platform's blocking attributes, its\n * loader injects after them so its startup scan sees the full set, and Replo's\n * own gates follow the platform instead of the native banner.\n */\nexport function ReploScripts({ scripts }: { scripts: ReploScriptEntry[] }) {\n // The earlier-mounted first-party pixel needs delegation before mount lifecycles run.\n const platform = findConsentPlatform(scripts);\n if (platform) {\n enableConsentDelegation(platform);\n }\n\n // The loader renders last so every blocked tag mounts before its startup scan.\n return (\n <>\n <ConsentWindowApi />\n {scripts\n .filter((entry) => !isConsentPlatformEntry(entry))\n .map((entry) => (\n <ManagedScript key={scriptId(entry)} entry={entry} />\n ))}\n {platform &&\n !platform.isCookiebot &&\n OPTIONAL_CONSENT_CATEGORIES.map((category) => (\n <InjectedScript\n key={category}\n baseId={`consent-platform:${platform.id}:grant:${category}`}\n descriptors={[buildGrantMarker(category)]}\n requiredConsent={[category]}\n />\n ))}\n {platform && (\n <InjectedScript\n baseId={`consent-platform:${platform.id}`}\n descriptors={platform.loader}\n />\n )}\n </>\n );\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -110,19 +110,15 @@ gtag('config', '${id}');`
110
110
  },
111
111
  Northbeam: {
112
112
  defaultConsent: ["analytics", "marketing"],
113
- // No trackPageViewInitial self-fire: NorthbeamSink owns every pageview
114
- // (initial + soft navigations) via the NavigationTracker broadcast.
115
113
  buildTags: (id) => [
116
114
  {
117
115
  kind: "inline",
118
- body: `(function(){var t;(n=t=t||{}).A="identify",n.B="trackPageView",n.C="fireEmailCaptureEvent",n.D="fireCustomGoal",n.E="firePurchaseEvent",n.F="trackPageViewInitial",n.G="fireSlimPurchaseEvent",n.H="identifyCustomerId";var n="https://j.northbeam.io/ota-sp/${id}.js";function r(n){for(var e=[],t=1;t<arguments.length;t++)e[t-1]=arguments[t];i.push({fnName:n,args:e})}var e,i=[],a=((e={})[t.F]=function(n){r(t.F,n)},(a={_q:i})[t.A]=function(n,e){return r(t.A,n,e)},a[t.B]=function(){return r(t.B)},a[t.C]=function(n,e){return r(t.C,n,e)},a[t.D]=function(n,e){return r(t.D,n,e)},a[t.E]=function(n){return r(t.E,n)},a[t.G]=function(n){return r(t.G,n)},a[t.H]=function(n,e){return r(t.H,n,e)},Object.assign(function(n){for(var e=[],t=1;t<arguments.length;t++)e.push(arguments[t]);return r.apply(null,[n].concat(e))},a));window.Northbeam=a,(a=document.createElement("script")).async=!0,a.src=n,document.head.appendChild(a);})()`
116
+ body: `(function(){var t;(n=t=t||{}).A="identify",n.B="trackPageView",n.C="fireEmailCaptureEvent",n.D="fireCustomGoal",n.E="firePurchaseEvent",n.F="trackPageViewInitial",n.G="fireSlimPurchaseEvent",n.H="identifyCustomerId";var n="https://j.northbeam.io/ota-sp/${id}.js";function r(n){for(var e=[],t=1;t<arguments.length;t++)e[t-1]=arguments[t];i.push({fnName:n,args:e})}var e,i=[],a=((e={})[t.F]=function(n){r(t.F,n)},(a={_q:i})[t.A]=function(n,e){return r(t.A,n,e)},a[t.B]=function(){return r(t.B)},a[t.C]=function(n,e){return r(t.C,n,e)},a[t.D]=function(n,e){return r(t.D,n,e)},a[t.E]=function(n){return r(t.E,n)},a[t.G]=function(n){return r(t.G,n)},a[t.H]=function(n,e){return r(t.H,n,e)},Object.assign(function(n){for(var e=[],t=1;t<arguments.length;t++)e.push(arguments[t]);return r.apply(null,[n].concat(e))},a));window.Northbeam=a,(a=document.createElement("script")).async=!0,a.src=n,document.head.appendChild(a),e.trackPageViewInitial(window.location.href);})()`
119
117
  }
120
118
  ]
121
119
  },
122
120
  Converge: {
123
121
  defaultConsent: ["analytics", "marketing"],
124
- // No $page_load self-fire: ConvergeSink owns every pageview (initial +
125
- // soft navigations) via the NavigationTracker broadcast.
126
122
  buildTags: (id) => [
127
123
  {
128
124
  kind: "external",
@@ -131,7 +127,7 @@ gtag('config', '${id}');`
131
127
  },
132
128
  {
133
129
  kind: "inline",
134
- body: `window.cvg||(c=window.cvg=function(){c.process?c.process.apply(c,arguments):c.queue.push(arguments)},c.queue=[]);`
130
+ body: `window.cvg||(c=window.cvg=function(){c.process?c.process.apply(c,arguments):c.queue.push(arguments)},c.queue=[]);cvg({method:"track",eventName:"$page_load"});`
135
131
  }
136
132
  ]
137
133
  },