@replohq/sdk 1.14.1 → 1.15.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.
@@ -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;
@@ -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,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
+ };
@@ -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;
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "branch": "HEAD",
5
- "commit": "19094c206182dc6886a38bbc6fcd1541c8123b64",
6
- "builtAt": "2026-10-08T22:22:26.957Z"
5
+ "commit": "3759f7d83c0c4991b7ccbafed0d1b55da1ed48b4",
6
+ "builtAt": "2026-10-09T21:52:34.432Z"
7
7
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.14.1",
6
+ "version": "1.15.0",
7
7
  "branch": "HEAD",
8
- "commit": "19094c206182dc6886a38bbc6fcd1541c8123b64",
9
- "builtAt": "2026-10-08T22:22:26.957Z"
8
+ "commit": "3759f7d83c0c4991b7ccbafed0d1b55da1ed48b4",
9
+ "builtAt": "2026-10-09T21:52:34.432Z"
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",