@replohq/sdk 1.14.1 → 1.16.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 (36) hide show
  1. package/_vendor/schemas/generated/consent.d.ts +13 -1
  2. package/_vendor/schemas/loaderKeys.mjs +1 -0
  3. package/analytics/analytics-provider.d.ts +2 -0
  4. package/analytics/analytics-provider.js +38 -8
  5. package/analytics/analytics-sink.d.ts +9 -0
  6. package/analytics/combine-sinks.js +2 -1
  7. package/analytics/get-analytics-sinks.js +9 -0
  8. package/analytics/sinks/intelligems-sink.d.ts +29 -0
  9. package/analytics/sinks/intelligems-sink.js +68 -0
  10. package/analytics/sinks/x-sink.d.ts +32 -0
  11. package/analytics/sinks/x-sink.js +126 -0
  12. package/cart/buy-now-action.d.ts +4 -0
  13. package/cart/buy-now-action.js +2 -1
  14. package/cart/cart-actions.d.ts +4 -0
  15. package/cart/cart-actions.js +2 -0
  16. package/cart/cart-provider.d.ts +2 -0
  17. package/cart/cart-provider.js +1 -0
  18. package/cart/gateways/cart-gateway.d.ts +4 -0
  19. package/cart/gateways/resolver-cart-gateway.js +2 -0
  20. package/cart/hooks/use-buy-now.d.ts +5 -0
  21. package/cart/hooks/use-buy-now.js +1 -0
  22. package/consent/replo-scripts.js +19 -5
  23. package/consent/script-registration-store.d.ts +9 -4
  24. package/consent/script-snippets.d.ts +9 -9
  25. package/consent/script-snippets.js +31 -5
  26. package/consent/types.d.ts +13 -1
  27. package/gateways/canopy-api.d.ts +4 -0
  28. package/gateways/canopy-api.js +1 -0
  29. package/lib/buildMetadata.js +3 -3
  30. package/loaders/loader-keys.d.ts +1 -0
  31. package/loaders/loader-keys.js +1 -0
  32. package/loaders/loader-schemas.d.ts +5 -0
  33. package/loaders/loader-schemas.js +9 -0
  34. package/loaders/shopify-integration-details-loader.d.ts +9 -0
  35. package/loaders/shopify-integration-details-loader.js +33 -0
  36. package/package.json +12 -4
@@ -1,4 +1,4 @@
1
- export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Cookiebot" | "snippet" | "custom" | "consentPlatform";
1
+ export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "X" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Cookiebot" | "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 = {
@@ -12,10 +12,22 @@ export type ScriptTagDescriptor = {
12
12
  body: string;
13
13
  module?: boolean;
14
14
  };
15
+ export type XPixelEvents = {
16
+ pageView?: string;
17
+ productViewed?: string;
18
+ addToCart?: string;
19
+ startCheckout?: string;
20
+ purchase?: string;
21
+ };
15
22
  export type ReploScriptEntry = {
16
23
  type: "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge";
17
24
  identifier: string;
18
25
  requiredConsent?: ConsentCategory[];
26
+ } | {
27
+ type: "X";
28
+ identifier: string;
29
+ events?: Partial<XPixelEvents>;
30
+ requiredConsent?: ConsentCategory[];
19
31
  } | {
20
32
  type: "Cookiebot";
21
33
  identifier: string;
@@ -1,6 +1,7 @@
1
1
  import '../../chunk-UJCSKKID.mjs';
2
2
 
3
3
  var DATA_LOADER_KEYS = {
4
+ SHOPIFY_INTEGRATION_DETAILS: "shopify_storefront.integration_details",
4
5
  SHOPIFY_PRODUCT: "shopify_storefront.product",
5
6
  SHOPIFY_COLLECTION: "shopify_storefront.collection",
6
7
  SHOPIFY_COLLECTION_PRODUCTS: "shopify_storefront.collection_products",
@@ -4,6 +4,8 @@ import type { AnalyticsSink } from "./analytics-sink";
4
4
  export declare function AnalyticsProvider({ children, checkoutProvider, }: React.PropsWithChildren<{
5
5
  checkoutProvider?: CheckoutProvider;
6
6
  }>): import("react/jsx-runtime").JSX.Element;
7
+ /** Register one stable, consent-aware sink beneath ReploProvider. */
8
+ export declare function useAnalyticsSink(sink: AnalyticsSink): void;
7
9
  /**
8
10
  * Returns the AnalyticsSink from the nearest AnalyticsProvider.
9
11
  * Throws if used outside of an AnalyticsProvider.
@@ -1,6 +1,14 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { createContext, useContext, useEffect, useMemo, useRef } from "react";
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState
11
+ } from "react";
4
12
  import { useConsent } from "../consent/consent-store";
5
13
  import { useRegisteredScripts } from "../consent/script-registration-store";
6
14
  import { combineSinks } from "./combine-sinks";
@@ -13,9 +21,18 @@ function AnalyticsProvider({
13
21
  }) {
14
22
  const consent = useConsent();
15
23
  const registeredScripts = useRegisteredScripts();
24
+ const [additionalSinks, setAdditionalSinks] = useState([]);
25
+ const registerSink = useCallback((sink) => {
26
+ setAdditionalSinks((current) => [...current, sink]);
27
+ return () => {
28
+ setAdditionalSinks((current) => {
29
+ return current.filter((registered) => registered !== sink);
30
+ });
31
+ };
32
+ }, []);
16
33
  const analytics = useMemo(() => {
17
- return combineSinks(
18
- getAnalyticsSinks({
34
+ return combineSinks([
35
+ ...getAnalyticsSinks({
19
36
  consent: {
20
37
  cmp: consent.cmp,
21
38
  mode: consent.mode,
@@ -24,15 +41,17 @@ function AnalyticsProvider({
24
41
  },
25
42
  registeredScripts,
26
43
  checkoutProvider
27
- })
28
- );
44
+ }),
45
+ ...additionalSinks
46
+ ]);
29
47
  }, [
30
48
  consent.cmp,
31
49
  consent.mode,
32
50
  consent.categories,
33
51
  consent.decidedAt,
34
52
  registeredScripts,
35
- checkoutProvider
53
+ checkoutProvider,
54
+ additionalSinks
36
55
  ]);
37
56
  const analyticsRef = useRef(analytics);
38
57
  analyticsRef.current = analytics;
@@ -51,7 +70,17 @@ function AnalyticsProvider({
51
70
  navigationTracker.current?.cleanup();
52
71
  };
53
72
  }, []);
54
- return /* @__PURE__ */ jsx(AnalyticsContext.Provider, { value: { analytics }, children });
73
+ return /* @__PURE__ */ jsx(AnalyticsContext.Provider, { value: { analytics, registerSink }, children });
74
+ }
75
+ function useAnalyticsSink(sink) {
76
+ const context = useContext(AnalyticsContext);
77
+ if (!context) {
78
+ throw new AnalyticsContextError({
79
+ message: "useAnalyticsSink must be used within <AnalyticsProvider>"
80
+ });
81
+ }
82
+ const registerSink = context.registerSink;
83
+ useEffect(() => registerSink(sink), [registerSink, sink]);
55
84
  }
56
85
  class AnalyticsContextError extends Error {
57
86
  constructor({ message }) {
@@ -74,5 +103,6 @@ function useAnalyticsOptional() {
74
103
  export {
75
104
  AnalyticsProvider,
76
105
  useAnalytics,
77
- useAnalyticsOptional
106
+ useAnalyticsOptional,
107
+ useAnalyticsSink
78
108
  };
@@ -13,7 +13,16 @@ export type PurchasePayload = {
13
13
  currency: string;
14
14
  data: Record<string, any>;
15
15
  };
16
+ export type AnalyticsProperty = string | number | boolean | null | AnalyticsProperty[] | {
17
+ [key: string]: AnalyticsProperty;
18
+ };
19
+ export interface CustomEventPayload {
20
+ event: string;
21
+ properties?: Record<string, AnalyticsProperty>;
22
+ }
16
23
  export interface AnalyticsSink {
24
+ /** Explicit business goals; sinks without custom-event support may omit this. */
25
+ customEvent?(payload: CustomEventPayload): Promise<void>;
17
26
  pageView(metadata?: PageMetadata): Promise<void>;
18
27
  purchase(payload: PurchasePayload): Promise<void>;
19
28
  click(payload: DataPayload): Promise<void>;
@@ -12,7 +12,7 @@ function combineSinks(sinks) {
12
12
  sinks.map(async (sink) => {
13
13
  try {
14
14
  const sinkMethod = sink[method];
15
- await sinkMethod.apply(sink, args);
15
+ await sinkMethod?.apply(sink, args);
16
16
  } catch {
17
17
  if (typeof window !== "undefined") {
18
18
  console.warn("[analytics] sink error", { method });
@@ -22,6 +22,7 @@ function combineSinks(sinks) {
22
22
  );
23
23
  };
24
24
  return {
25
+ customEvent: (...args) => broadcast("customEvent", ...args),
25
26
  pageView: (...args) => broadcast("pageView", ...args),
26
27
  purchase: (...args) => broadcast("purchase", ...args),
27
28
  click: (...args) => broadcast("click", ...args),
@@ -11,6 +11,7 @@ import { ReploSink } from "./sinks/replo-sink";
11
11
  import { SnapchatSink } from "./sinks/snapchat-sink";
12
12
  import { TikTokSink } from "./sinks/tiktok-sink";
13
13
  import { WindowSink } from "./sinks/window-sink";
14
+ import { XSink } from "./sinks/x-sink";
14
15
  import { gateShopifyOwnedFunnelEvents } from "./utils/shopify-funnel-gate";
15
16
  const PROVIDER_SINK_FACTORIES = {
16
17
  GA4: () => new GA4Sink(),
@@ -26,6 +27,14 @@ const SNIPPET_PROVIDER_SINK_FACTORIES = {
26
27
  [snippetScriptId("Elevar")]: () => new ElevarSink()
27
28
  };
28
29
  function findSinkFactory(script) {
30
+ if (script.type === "X") {
31
+ return () => {
32
+ return new XSink({
33
+ pixelId: script.pixelId,
34
+ events: script.events
35
+ });
36
+ };
37
+ }
29
38
  if (script.type === "snippet") {
30
39
  return SNIPPET_PROVIDER_SINK_FACTORIES[script.id] ?? null;
31
40
  }
@@ -0,0 +1,29 @@
1
+ import type { AnalyticsSink, CustomEventPayload, DataPayload, PurchasePayload } from "../analytics-sink";
2
+ import type { PageMetadata } from "../utils/navigation-tracker";
3
+ export type IntelligemsEventMappings = {
4
+ [Method in Exclude<keyof AnalyticsSink, "customEvent">]?: (...args: Parameters<AnalyticsSink[Method]>) => CustomEventPayload | null;
5
+ };
6
+ /**
7
+ * Sends explicitly selected custom goals through the installed Intelligems SDK.
8
+ * Native page views, experiment assignment, and order attribution remain owned
9
+ * by useIgTrack and Shopify; duplicating them as custom events changes reports.
10
+ */
11
+ export declare class IntelligemsSink implements AnalyticsSink {
12
+ private readonly options;
13
+ constructor(options: {
14
+ /** Read live SDK readiness; do not accept events before assignment resolves. */
15
+ isReady: () => boolean;
16
+ events?: IntelligemsEventMappings;
17
+ });
18
+ customEvent(payload: CustomEventPayload): Promise<void>;
19
+ private send;
20
+ pageView(metadata?: PageMetadata): Promise<void>;
21
+ purchase(payload: PurchasePayload): Promise<void>;
22
+ click(payload: DataPayload): Promise<void>;
23
+ productViewed(payload: DataPayload): Promise<void>;
24
+ addToCart(payload: DataPayload): Promise<void>;
25
+ removeFromCart(payload: DataPayload): Promise<void>;
26
+ viewCart(payload: DataPayload): Promise<void>;
27
+ startCheckout(payload: DataPayload): Promise<void>;
28
+ viewCollection(payload: DataPayload): Promise<void>;
29
+ }
@@ -0,0 +1,68 @@
1
+ "use client";
2
+ import { consentStore, isConsentAllowed } from "../../consent/consent-store";
3
+ class IntelligemsSink {
4
+ constructor(options) {
5
+ this.options = options;
6
+ }
7
+ async customEvent(payload) {
8
+ if (typeof window === "undefined" || !this.options.isReady()) {
9
+ return;
10
+ }
11
+ if (!isConsentAllowed({
12
+ state: consentStore.getSnapshot(),
13
+ requiredConsent: ["analytics"]
14
+ })) {
15
+ return;
16
+ }
17
+ if (!payload.event.trim() || /[,']/.test(payload.event) || payload.event !== payload.event.trim()) {
18
+ throw new IntelligemsEventNameError(
19
+ "Intelligems event names must be nonempty, trimmed, and contain no commas or single quotes"
20
+ );
21
+ }
22
+ if (!("igEvents" in window)) {
23
+ return;
24
+ }
25
+ const transport = window.igEvents;
26
+ if (!transport || typeof transport !== "object" || Array.isArray(transport) || !("push" in transport) || typeof transport.push !== "function") {
27
+ return;
28
+ }
29
+ await transport.push(payload);
30
+ }
31
+ async send(payload) {
32
+ if (payload) {
33
+ await this.customEvent(payload);
34
+ }
35
+ }
36
+ async pageView(metadata) {
37
+ await this.send(this.options.events?.pageView?.(metadata));
38
+ }
39
+ async purchase(payload) {
40
+ await this.send(this.options.events?.purchase?.(payload));
41
+ }
42
+ async click(payload) {
43
+ await this.send(this.options.events?.click?.(payload));
44
+ }
45
+ async productViewed(payload) {
46
+ await this.send(this.options.events?.productViewed?.(payload));
47
+ }
48
+ async addToCart(payload) {
49
+ await this.send(this.options.events?.addToCart?.(payload));
50
+ }
51
+ async removeFromCart(payload) {
52
+ await this.send(this.options.events?.removeFromCart?.(payload));
53
+ }
54
+ async viewCart(payload) {
55
+ await this.send(this.options.events?.viewCart?.(payload));
56
+ }
57
+ async startCheckout(payload) {
58
+ await this.send(this.options.events?.startCheckout?.(payload));
59
+ }
60
+ async viewCollection(payload) {
61
+ await this.send(this.options.events?.viewCollection?.(payload));
62
+ }
63
+ }
64
+ class IntelligemsEventNameError extends Error {
65
+ }
66
+ export {
67
+ IntelligemsSink
68
+ };
@@ -0,0 +1,32 @@
1
+ import type { XPixelEvents } from "../../_vendor/schemas/generated/consent";
2
+ import type { AnalyticsSink, DataPayload, PurchasePayload } from "../analytics-sink";
3
+ import type { PageMetadata } from "../utils/navigation-tracker";
4
+ declare global {
5
+ interface Window {
6
+ twq?: (command: "config" | "event", ...args: unknown[]) => void;
7
+ }
8
+ }
9
+ /**
10
+ * Sends storefront events to X as the conversion events the merchant created
11
+ * in X Ads Events Manager. X has no named events in code, so an event without
12
+ * a mapped ID sends nothing. The consent-gated base code's `twq('config')`
13
+ * already records the site visit and landing page view on load.
14
+ */
15
+ export declare class XSink implements AnalyticsSink {
16
+ private readonly events;
17
+ private send;
18
+ constructor({ pixelId, events }: {
19
+ pixelId: string;
20
+ events: XPixelEvents;
21
+ });
22
+ private track;
23
+ pageView(metadata: PageMetadata | undefined): Promise<void>;
24
+ purchase({ amount, currency, data }: PurchasePayload): Promise<void>;
25
+ click(): Promise<void>;
26
+ productViewed({ data }: DataPayload): Promise<void>;
27
+ addToCart({ data }: DataPayload): Promise<void>;
28
+ removeFromCart(): Promise<void>;
29
+ viewCart(): Promise<void>;
30
+ startCheckout({ data }: DataPayload): Promise<void>;
31
+ viewCollection(): Promise<void>;
32
+ }
@@ -0,0 +1,126 @@
1
+ import { filterUndefined } from "../../_vendor/replo-utils/lib/object.mjs";
2
+ import { z } from "zod";
3
+ import { isXEventIdForPixel } from "../../consent/script-snippets";
4
+ import { legacyResourceId } from "../utils/analytics-products";
5
+ import { createEventQueue } from "../utils/analytics-utils";
6
+ const analyticsRecordSchema = z.record(z.string(), z.unknown());
7
+ const nonEmptyStringSchema = z.union([
8
+ z.string().min(1),
9
+ z.number().finite().transform(String)
10
+ ]);
11
+ const finiteNumberSchema = z.union([
12
+ z.number().finite(),
13
+ z.string().trim().min(1).transform(Number).pipe(z.number().finite())
14
+ ]);
15
+ function parseRecord(value) {
16
+ const result = analyticsRecordSchema.safeParse(value);
17
+ return result.success ? result.data : {};
18
+ }
19
+ function parseString(value) {
20
+ const result = nonEmptyStringSchema.safeParse(value);
21
+ return result.success ? result.data : void 0;
22
+ }
23
+ function parseNumber(value) {
24
+ const result = finiteNumberSchema.safeParse(value);
25
+ return result.success ? result.data : void 0;
26
+ }
27
+ function mapContent(value, defaultQuantity) {
28
+ const line = parseRecord(value);
29
+ const variant = parseRecord(line.variant);
30
+ const product = parseRecord(variant.product);
31
+ const productId = parseString(line.productId) ?? parseString(product.id);
32
+ if (!productId) {
33
+ return null;
34
+ }
35
+ return filterUndefined({
36
+ content_id: legacyResourceId(productId),
37
+ content_name: parseString(line.productTitle) ?? parseString(product.title) ?? parseString(line.title),
38
+ content_price: parseNumber(line.price) ?? parseNumber(parseRecord(variant.price).amount),
39
+ num_items: parseNumber(line.quantity) ?? defaultQuantity
40
+ });
41
+ }
42
+ function mapContents(lines) {
43
+ const result = z.array(z.unknown()).safeParse(lines);
44
+ if (!result.success) {
45
+ return void 0;
46
+ }
47
+ const contents = result.data.map((line) => mapContent(line, void 0)).filter((content) => content !== null);
48
+ return contents.length > 0 ? contents : void 0;
49
+ }
50
+ function lineValue(content) {
51
+ if (content?.content_price === void 0) {
52
+ return void 0;
53
+ }
54
+ return content.content_price * (content.num_items ?? 1);
55
+ }
56
+ class XSink {
57
+ events;
58
+ send = createEventQueue(
59
+ () => typeof window !== "undefined" && Boolean(window.twq),
60
+ (eventId, parameters) => window.twq?.("event", eventId, parameters),
61
+ "X"
62
+ );
63
+ constructor({ pixelId, events }) {
64
+ this.events = Object.fromEntries(
65
+ Object.entries(events).flatMap(([event, eventId]) => {
66
+ const trimmed = eventId?.trim();
67
+ return trimmed && isXEventIdForPixel({ eventId: trimmed, pixelId }) ? [[event, trimmed]] : [];
68
+ })
69
+ );
70
+ }
71
+ track(event, parameters) {
72
+ const eventId = this.events[event];
73
+ if (eventId) {
74
+ this.send(eventId, filterUndefined(parameters));
75
+ }
76
+ }
77
+ async pageView(metadata) {
78
+ if (metadata?.navigationType === "initial") {
79
+ return;
80
+ }
81
+ this.track("pageView", {});
82
+ }
83
+ async purchase({ amount, currency, data }) {
84
+ this.track("purchase", {
85
+ value: amount,
86
+ currency,
87
+ conversion_id: parseString(data.order_id) ?? parseString(data.orderId) ?? parseString(data.id),
88
+ contents: mapContents(data.lineItems)
89
+ });
90
+ }
91
+ async click() {
92
+ }
93
+ async productViewed({ data }) {
94
+ const content = mapContent(data, 1);
95
+ this.track("productViewed", {
96
+ value: lineValue(content),
97
+ currency: parseString(data.currency),
98
+ contents: content ? [content] : void 0
99
+ });
100
+ }
101
+ async addToCart({ data }) {
102
+ const content = mapContent(data, 1);
103
+ this.track("addToCart", {
104
+ value: lineValue(content),
105
+ currency: parseString(data.currency),
106
+ contents: content ? [content] : void 0
107
+ });
108
+ }
109
+ async removeFromCart() {
110
+ }
111
+ async viewCart() {
112
+ }
113
+ async startCheckout({ data }) {
114
+ const totalPrice = parseRecord(data.totalPrice);
115
+ this.track("startCheckout", {
116
+ value: parseNumber(totalPrice.amount),
117
+ currency: parseString(data.currencyCode) ?? parseString(totalPrice.currencyCode),
118
+ contents: mapContents(data.lineItems)
119
+ });
120
+ }
121
+ async viewCollection() {
122
+ }
123
+ }
124
+ export {
125
+ XSink
126
+ };
@@ -28,6 +28,10 @@ export declare function buyNowWithRecoveryAction(input: {
28
28
  properties: CartLineAttribute[];
29
29
  }[];
30
30
  discountCodes: string[];
31
+ attributes?: {
32
+ key: string;
33
+ value: string;
34
+ }[];
31
35
  pageUrl: string;
32
36
  }): Promise<{
33
37
  result: "created";
@@ -122,7 +122,8 @@ async function buyNowWithRecoveryAction(input) {
122
122
  lines: cartLines,
123
123
  skipStoringCart: true,
124
124
  country: input.country,
125
- discountCodes: discountCodes.length > 0 ? discountCodes : void 0
125
+ discountCodes: discountCodes.length > 0 ? discountCodes : void 0,
126
+ attributes: input.attributes
126
127
  });
127
128
  if (!cart?.checkoutUrl) {
128
129
  throw new BuyNowCartCreationError({
@@ -32,6 +32,10 @@ export declare function createCartAction(options?: {
32
32
  lines?: CartLinePayload[];
33
33
  skipStoringCart?: boolean;
34
34
  discountCodes?: string[];
35
+ attributes?: {
36
+ key: string;
37
+ value: string;
38
+ }[];
35
39
  country?: StorefrontCountryCode;
36
40
  }): Promise<Cart | null>;
37
41
  /**
@@ -65,6 +65,7 @@ async function createCartAction(options) {
65
65
  lines,
66
66
  skipStoringCart = false,
67
67
  discountCodes,
68
+ attributes,
68
69
  country
69
70
  } = options ?? {};
70
71
  const checkoutProvider = await getCheckoutProvider();
@@ -72,6 +73,7 @@ async function createCartAction(options) {
72
73
  const cart = await cartGateway.createCart({
73
74
  lines,
74
75
  discountCodes,
76
+ attributes,
75
77
  buyerIdentity: country ? { country } : void 0
76
78
  });
77
79
  if (cart && !skipStoringCart) {
@@ -66,6 +66,8 @@ export interface CartContextTypeInternal {
66
66
  * For buy now functionality, use useBuyNow.
67
67
  */
68
68
  export interface CartContextType {
69
+ /** Current cart ID for integrations that track checkout attribution. */
70
+ cartId: string | null;
69
71
  /** Total number of items in cart */
70
72
  itemsCount: number;
71
73
  /** Whether the cart UI is open */
@@ -675,6 +675,7 @@ function useCart() {
675
675
  });
676
676
  }
677
677
  return {
678
+ cartId: context.cartData?.id ?? null,
678
679
  itemsCount: context.itemsCount,
679
680
  isCartOpen: context.isCartOpen,
680
681
  openCart: context.openCart,
@@ -11,6 +11,10 @@ export interface CartGateway {
11
11
  createCart(options?: {
12
12
  lines?: CartLinePayload[];
13
13
  discountCodes?: string[];
14
+ attributes?: {
15
+ key: string;
16
+ value: string;
17
+ }[];
14
18
  buyerIdentity?: CartBuyerIdentity;
15
19
  }): Promise<Cart | null>;
16
20
  addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;
@@ -11,6 +11,7 @@ class ResolverCartGateway {
11
11
  async createCart({
12
12
  lines,
13
13
  discountCodes,
14
+ attributes,
14
15
  buyerIdentity
15
16
  } = {}) {
16
17
  try {
@@ -26,6 +27,7 @@ class ResolverCartGateway {
26
27
  sellingPlanId: line.sellingPlanId
27
28
  })),
28
29
  discountCodes,
30
+ attributes,
29
31
  country: buyerIdentity?.country
30
32
  });
31
33
  } catch (error) {
@@ -81,5 +81,10 @@ export interface VariantBuyNowInfo {
81
81
  export declare function useBuyNow(): {
82
82
  buyNow: (variants: VariantBuyNowInfo[], options?: {
83
83
  discountCodes?: string[];
84
+ /** Order-level Shopify cart attributes, including experiment attribution. */
85
+ attributes?: {
86
+ key: string;
87
+ value: string;
88
+ }[];
84
89
  }) => Promise<BuyNowOutcome>;
85
90
  };
@@ -35,6 +35,7 @@ function useBuyNow() {
35
35
  properties: variant.properties ?? []
36
36
  })),
37
37
  discountCodes: options?.discountCodes ?? [],
38
+ attributes: options?.attributes ?? [],
38
39
  pageUrl: window.location.href
39
40
  };
40
41
  const attemptStorageKey = `replo-checkout-attempt:${JSON.stringify(checkoutInput)}`;
@@ -57,7 +57,7 @@ function descriptorsFor(entry) {
57
57
  if (entry.type === "consentPlatform" || entry.type === "Cookiebot") {
58
58
  return [];
59
59
  }
60
- return buildScriptTags({ type: entry.type, identifier: entry.identifier });
60
+ return buildScriptTags(entry);
61
61
  }
62
62
  class InjectedScript extends Component {
63
63
  componentDidMount() {
@@ -89,17 +89,31 @@ class ScriptRegistration extends Component {
89
89
  return null;
90
90
  }
91
91
  }
92
- function ManagedScript({ entry }) {
93
- const requiredConsent = requiredConsentFor(entry);
92
+ function registrationFor(entry) {
94
93
  const id = scriptId(entry);
94
+ const requiredConsent = requiredConsentFor(entry);
95
+ if (entry.type === "X") {
96
+ return {
97
+ id,
98
+ type: "X",
99
+ requiredConsent,
100
+ pixelId: entry.identifier,
101
+ events: entry.events ?? {}
102
+ };
103
+ }
104
+ return { id, type: entry.type, requiredConsent };
105
+ }
106
+ function ManagedScript({ entry }) {
107
+ const registration = registrationFor(entry);
108
+ const { id, requiredConsent } = registration;
95
109
  const isBlockedByNativeConsent = useIsBlockedByNativeConsent(requiredConsent);
96
110
  return /* @__PURE__ */ jsxs(Fragment, { children: [
97
111
  /* @__PURE__ */ jsx(
98
112
  ScriptRegistration,
99
113
  {
100
- script: { id, type: entry.type, requiredConsent }
114
+ script: registration
101
115
  },
102
- `${id}:${requiredConsent.join(",")}`
116
+ JSON.stringify(registration)
103
117
  ),
104
118
  !isBlockedByNativeConsent && /* @__PURE__ */ jsx(
105
119
  InjectedScript,
@@ -1,4 +1,4 @@
1
- import type { ConsentCategory, ReploScriptType } from "../_vendor/schemas/generated/consent";
1
+ import type { ConsentCategory, ReploScriptType, XPixelEvents } from "../_vendor/schemas/generated/consent";
2
2
  /**
3
3
  * Tracks which managed scripts are currently mounted on the page. Each
4
4
  * `ReploScripts` entry registers itself here on mount, so the analytics provider
@@ -6,11 +6,16 @@ import type { ConsentCategory, ReploScriptType } from "../_vendor/schemas/genera
6
6
  * without importing a static list. Like the consent store, this is a singleton
7
7
  * rather than React context so it works regardless of tree position.
8
8
  */
9
- export interface RegisteredScript {
9
+ export type RegisteredScript = {
10
10
  id: string;
11
- type: ReploScriptType;
12
11
  requiredConsent: ConsentCategory[];
13
- }
12
+ } & ({
13
+ type: Exclude<ReploScriptType, "X">;
14
+ } | {
15
+ type: "X";
16
+ pixelId: string;
17
+ events: XPixelEvents;
18
+ });
14
19
  /**
15
20
  * Registration id of a `snippet` entry. Snippet providers have no distinct
16
21
  * `type`, so both `ReploScripts` (registering) and the analytics sink factory
@@ -1,19 +1,19 @@
1
- import type { ConsentCategory, ReploScriptType, ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
1
+ import type { ConsentCategory, ReploScriptEntry, ReploScriptType, ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
2
2
  type ProviderType = Exclude<ReploScriptType, "custom" | "snippet" | "consentPlatform" | "Cookiebot">;
3
3
  /**
4
4
  * Snippet providers don't synthesize tags (the user pastes the provider-issued
5
5
  * snippet as the entry's `body`), but they still get a first-class default
6
6
  * consent categorization like identifier providers.
7
7
  */
8
- /**
9
- * Returns the structured tag descriptors for a named provider. A single provider
10
- * can produce multiple tags (e.g. GA4 = external loader + inline config), which
11
- * the runtime injects in order.
12
- */
13
- export declare function buildScriptTags({ type, identifier, }: {
8
+ /** IDs for another pixel would credit a different advertiser. */
9
+ export declare function isXEventIdForPixel({ eventId, pixelId, }: {
10
+ eventId: string;
11
+ pixelId: string;
12
+ }): boolean;
13
+ /** The runtime injects these tags in order after consent allows tracking. */
14
+ export declare function buildScriptTags(entry: Extract<ReploScriptEntry, {
14
15
  type: ProviderType;
15
- identifier: string;
16
- }): ScriptTagDescriptor[];
16
+ }>): ScriptTagDescriptor[];
17
17
  /**
18
18
  * The default consent categories that gate an identifier-provider script when an
19
19
  * entry does not specify `requiredConsent`. `snippet` and `custom` entries carry
@@ -69,6 +69,15 @@ gtag('config', '${id}');`
69
69
  }
70
70
  ]
71
71
  },
72
+ X: {
73
+ defaultConsent: ["marketing"],
74
+ buildTags: (id) => [
75
+ {
76
+ kind: "inline",
77
+ body: `!function(e,t,n,s,u,a){e.twq||(s=e.twq=function(){s.exe?s.exe.apply(s,arguments):s.queue.push(arguments);},s.version='1.1',s.queue=[],u=t.createElement(n),u.async=!0,u.src='https://static.ads-twitter.com/uwt.js',a=t.getElementsByTagName(n)[0],a.parentNode.insertBefore(u,a))}(window,document,'script');twq('config','${id}');`
78
+ }
79
+ ]
80
+ },
72
81
  Hotjar: {
73
82
  defaultConsent: ["analytics"],
74
83
  buildTags: (id) => [
@@ -140,16 +149,33 @@ gtag('config', '${id}');`
140
149
  ]
141
150
  }
142
151
  };
143
- function buildScriptTags({
144
- type,
145
- identifier
152
+ function isXEventIdForPixel({
153
+ eventId,
154
+ pixelId
146
155
  }) {
147
- return PROVIDER_SNIPPETS[type].buildTags(identifier);
156
+ const match = /^tw-([a-z0-9]+)-[a-z0-9]+$/i.exec(eventId);
157
+ return match?.[1]?.toLowerCase() === pixelId.toLowerCase();
158
+ }
159
+ function buildScriptTags(entry) {
160
+ const tags = PROVIDER_SNIPPETS[entry.type].buildTags(entry.identifier);
161
+ if (entry.type === "X") {
162
+ const pageViewId = entry.events?.pageView?.trim();
163
+ if (pageViewId && isXEventIdForPixel({ eventId: pageViewId, pixelId: entry.identifier })) {
164
+ return tags.map((tag) => {
165
+ return tag.kind === "inline" ? {
166
+ ...tag,
167
+ body: `${tag.body}twq('event',${JSON.stringify(pageViewId)},{});`
168
+ } : tag;
169
+ });
170
+ }
171
+ }
172
+ return tags;
148
173
  }
149
174
  function defaultConsentFor(type) {
150
175
  return PROVIDER_SNIPPETS[type].defaultConsent;
151
176
  }
152
177
  export {
153
178
  buildScriptTags,
154
- defaultConsentFor
179
+ defaultConsentFor,
180
+ isXEventIdForPixel
155
181
  };
@@ -1,4 +1,4 @@
1
- export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Cookiebot" | "snippet" | "custom" | "consentPlatform";
1
+ export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "X" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Cookiebot" | "snippet" | "custom" | "consentPlatform";
2
2
  export type ConsentCategory = "necessary" | "analytics" | "marketing" | "preferences" | "sale_of_data";
3
3
  export type ConsentMode = "off" | "simple" | "per-category";
4
4
  declare module "react" {
@@ -16,10 +16,22 @@ export type ScriptTagDescriptor = {
16
16
  kind: "inline";
17
17
  body: string;
18
18
  };
19
+ export type XPixelEvents = {
20
+ pageView?: string;
21
+ productViewed?: string;
22
+ addToCart?: string;
23
+ startCheckout?: string;
24
+ purchase?: string;
25
+ };
19
26
  export type ReploScriptEntry = {
20
27
  type: "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Cookiebot";
21
28
  identifier: string;
22
29
  requiredConsent?: ConsentCategory[];
30
+ } | {
31
+ type: "X";
32
+ identifier: string;
33
+ events?: XPixelEvents;
34
+ requiredConsent?: ConsentCategory[];
23
35
  } | {
24
36
  type: "snippet";
25
37
  id: string;
@@ -72,6 +72,10 @@ declare class CanopyApiGateway {
72
72
  sellingPlanId?: string | null;
73
73
  }[];
74
74
  discountCodes?: string[];
75
+ attributes?: {
76
+ key: string;
77
+ value: string;
78
+ }[];
75
79
  country?: StorefrontCountryCode;
76
80
  }): Promise<Cart>;
77
81
  cartAddLines({ cartId, lines, }: {
@@ -195,6 +195,7 @@ If you're running locally, make sure the canopy-api cloudflare worker is running
195
195
  return this._cartPost("/create", {
196
196
  lines: options?.lines,
197
197
  discountCodes: options?.discountCodes,
198
+ attributes: options?.attributes,
198
199
  country: options?.country
199
200
  });
200
201
  }
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.14.1",
3
+ "version": "1.16.0",
4
4
  "branch": "HEAD",
5
- "commit": "19094c206182dc6886a38bbc6fcd1541c8123b64",
6
- "builtAt": "2026-10-08T22:22:26.957Z"
5
+ "commit": "702e4d053e7d73e648c62dce85f5d14634d87bd8",
6
+ "builtAt": "2026-10-09T23:28:16.932Z"
7
7
  };
@@ -10,6 +10,7 @@
10
10
  * `<integration>.<objectType>_<qualifier>`.
11
11
  */
12
12
  export declare const DATA_LOADER_KEYS: {
13
+ readonly SHOPIFY_INTEGRATION_DETAILS: "shopify_storefront.integration_details";
13
14
  readonly SHOPIFY_PRODUCT: "shopify_storefront.product";
14
15
  readonly SHOPIFY_COLLECTION: "shopify_storefront.collection";
15
16
  readonly SHOPIFY_COLLECTION_PRODUCTS: "shopify_storefront.collection_products";
@@ -1,4 +1,5 @@
1
1
  const DATA_LOADER_KEYS = {
2
+ SHOPIFY_INTEGRATION_DETAILS: "shopify_storefront.integration_details",
2
3
  SHOPIFY_PRODUCT: "shopify_storefront.product",
3
4
  SHOPIFY_COLLECTION: "shopify_storefront.collection",
4
5
  SHOPIFY_COLLECTION_PRODUCTS: "shopify_storefront.collection_products",
@@ -1098,3 +1098,8 @@ export type ShopifyKachingBundlesLoaderOutput = z.infer<typeof shopifyKachingBun
1098
1098
  export type StorefrontMetaobject = z.infer<typeof storefrontMetaobjectSchema>;
1099
1099
  export type ShopifyMetaobjectLoaderOutput = z.infer<typeof shopifyMetaobjectLoaderOutputSchema>;
1100
1100
  export type ShopifyMetaobjectsLoaderOutput = z.infer<typeof shopifyMetaobjectsLoaderOutputSchema>;
1101
+ export declare const shopifyIntegrationDetailsLoaderOutputSchema: z.ZodObject<{
1102
+ shopifyDomain: z.ZodString;
1103
+ storefrontAccessToken: z.ZodString;
1104
+ }, z.core.$strip>;
1105
+ export type ShopifyIntegrationDetails = z.infer<typeof shopifyIntegrationDetailsLoaderOutputSchema>;
@@ -362,6 +362,14 @@ const shopifyMetaobjectsLoaderOutputSchema = z.object({
362
362
  const shopifyKachingBundlesLoaderOutputSchema = z.object({
363
363
  bundles: z.record(z.string(), z.unknown()).nullable()
364
364
  }).describe("ShopifyKachingBundles");
365
+ const shopifyIntegrationDetailsLoaderOutputSchema = z.object({
366
+ shopifyDomain: z.string().min(1).describe(
367
+ "The connected store's myshopify.com domain, without a protocol."
368
+ ),
369
+ storefrontAccessToken: z.string().min(1).describe(
370
+ "Public Shopify Storefront API token intended for browser use. Never an Admin API or private Storefront token."
371
+ )
372
+ });
365
373
  export {
366
374
  contentfulAssetObjectSchema,
367
375
  contentfulEntriesLoaderOutputSchema,
@@ -400,6 +408,7 @@ export {
400
408
  sellingPlanOptionValueSchema,
401
409
  sellingPlanPriceAdjustmentSchema,
402
410
  sellingPlanSchema,
411
+ shopifyIntegrationDetailsLoaderOutputSchema,
403
412
  shopifyKachingBundlesLoaderOutputSchema,
404
413
  shopifyMetaobjectLoaderOutputSchema,
405
414
  shopifyMetaobjectsLoaderOutputSchema,
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ShopifyIntegrationDetails } from "./loader-schemas";
3
+ type ShopifyIntegrationDetailsLoaderProps = {
4
+ loaderKey: string;
5
+ children: (data: ShopifyIntegrationDetails) => ReactNode;
6
+ fallback?: ReactNode;
7
+ };
8
+ export declare function ShopifyIntegrationDetailsLoader({ loaderKey, children, fallback, }: ShopifyIntegrationDetailsLoaderProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { Fragment, jsx } from "react/jsx-runtime";
3
+ import { Suspense } from "react";
4
+ import { useSuspenseQuery } from "@tanstack/react-query";
5
+ import { invokeLoader } from "./invoke-loader";
6
+ import { LoaderErrorBoundary } from "./loader-error-boundary";
7
+ import { shopifyIntegrationDetailsLoaderOutputSchema } from "./loader-schemas";
8
+ function ShopifyIntegrationDetailsLoaderInner({
9
+ loaderKey,
10
+ children
11
+ }) {
12
+ const { data } = useSuspenseQuery({
13
+ queryKey: [loaderKey, {}],
14
+ queryFn: async () => {
15
+ const loadedData = await invokeLoader({
16
+ loaderKey,
17
+ args: {}
18
+ });
19
+ return shopifyIntegrationDetailsLoaderOutputSchema.parse(loadedData);
20
+ }
21
+ });
22
+ return /* @__PURE__ */ jsx(Fragment, { children: children(data) });
23
+ }
24
+ function ShopifyIntegrationDetailsLoader({
25
+ loaderKey,
26
+ children,
27
+ fallback
28
+ }) {
29
+ return /* @__PURE__ */ jsx(LoaderErrorBoundary, { fallback: fallback ?? null, resetKey: loaderKey, children: /* @__PURE__ */ jsx(Suspense, { fallback: fallback ?? null, children: /* @__PURE__ */ jsx(ShopifyIntegrationDetailsLoaderInner, { loaderKey, children }) }) });
30
+ }
31
+ export {
32
+ ShopifyIntegrationDetailsLoader
33
+ };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.14.1",
3
+ "version": "1.16.0",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.14.1",
6
+ "version": "1.16.0",
7
7
  "branch": "HEAD",
8
- "commit": "19094c206182dc6886a38bbc6fcd1541c8123b64",
9
- "builtAt": "2026-10-08T22:22:26.957Z"
8
+ "commit": "702e4d053e7d73e648c62dce85f5d14634d87bd8",
9
+ "builtAt": "2026-10-09T23:28:16.932Z"
10
10
  },
11
11
  "description": "Replo SDK — cart, analytics, and data loaders for agent-built Next.js sites.",
12
12
  "license": "SEE LICENSE IN LICENSE",
@@ -85,6 +85,10 @@
85
85
  "types": "./analytics/analytics-provider.d.ts",
86
86
  "default": "./analytics/analytics-provider.js"
87
87
  },
88
+ "./analytics/sinks/intelligems-sink": {
89
+ "types": "./analytics/sinks/intelligems-sink.d.ts",
90
+ "default": "./analytics/sinks/intelligems-sink.js"
91
+ },
88
92
  "./analytics/hooks/use-product-viewed-analytics": {
89
93
  "types": "./analytics/hooks/use-product-viewed-analytics.d.ts",
90
94
  "default": "./analytics/hooks/use-product-viewed-analytics.js"
@@ -165,6 +169,10 @@
165
169
  "types": "./loaders/reviews-io-reviews-loader.d.ts",
166
170
  "default": "./loaders/reviews-io-reviews-loader.js"
167
171
  },
172
+ "./loaders/shopify-integration-details-loader": {
173
+ "types": "./loaders/shopify-integration-details-loader.d.ts",
174
+ "default": "./loaders/shopify-integration-details-loader.js"
175
+ },
168
176
  "./loaders/smile-earning-rules-loader": {
169
177
  "types": "./loaders/smile-earning-rules-loader.d.ts",
170
178
  "default": "./loaders/smile-earning-rules-loader.js"