@amboras-dev/snapchat-ads 2.0.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.
- package/dist/chunk-PK23QMZT.mjs +30 -0
- package/dist/chunk-PK23QMZT.mjs.map +1 -0
- package/dist/get-config-DmAUgsFJ.d.mts +108 -0
- package/dist/get-config-DmAUgsFJ.d.ts +108 -0
- package/dist/get-config.d.mts +1 -0
- package/dist/get-config.d.ts +1 -0
- package/dist/get-config.js +54 -0
- package/dist/get-config.js.map +1 -0
- package/dist/get-config.mjs +7 -0
- package/dist/get-config.mjs.map +1 -0
- package/dist/index.d.mts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.js +235 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +177 -0
- package/dist/index.mjs.map +1 -0
- package/dist/slot-components/index.d.mts +128 -0
- package/dist/slot-components/index.d.ts +128 -0
- package/dist/slot-components/index.js +657 -0
- package/dist/slot-components/index.js.map +1 -0
- package/dist/slot-components/index.mjs +627 -0
- package/dist/slot-components/index.mjs.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
deriveMinuteBucket: () => deriveMinuteBucket,
|
|
24
|
+
getSnapchatAdsConfig: () => getSnapchatAdsConfig,
|
|
25
|
+
makeEventId: () => makeEventId,
|
|
26
|
+
minuteBucketNow: () => minuteBucketNow,
|
|
27
|
+
sendSnapchatCapiEvent: () => sendSnapchatCapiEvent,
|
|
28
|
+
trackAddCart: () => trackAddCart,
|
|
29
|
+
trackPageView: () => trackPageView,
|
|
30
|
+
trackPurchase: () => trackPurchase,
|
|
31
|
+
trackStartCheckout: () => trackStartCheckout,
|
|
32
|
+
trackViewContent: () => trackViewContent
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(src_exports);
|
|
35
|
+
|
|
36
|
+
// src/lib/snap-pixel-loader.ts
|
|
37
|
+
function safeSnaptr(...args) {
|
|
38
|
+
if (typeof window === "undefined") return false;
|
|
39
|
+
const fn = window.snaptr;
|
|
40
|
+
if (typeof fn !== "function") return false;
|
|
41
|
+
try {
|
|
42
|
+
fn(...args);
|
|
43
|
+
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/lib/dedup.ts
|
|
50
|
+
var MS_PER_MINUTE = 60 * 1e3;
|
|
51
|
+
function deriveMinuteBucket(nowMs) {
|
|
52
|
+
if (!Number.isFinite(nowMs)) return 0;
|
|
53
|
+
return Math.floor(nowMs / MS_PER_MINUTE);
|
|
54
|
+
}
|
|
55
|
+
function minuteBucketNow() {
|
|
56
|
+
return deriveMinuteBucket(Date.now());
|
|
57
|
+
}
|
|
58
|
+
function safeKey(primaryKey) {
|
|
59
|
+
if (!primaryKey) return "unknown";
|
|
60
|
+
return primaryKey.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
61
|
+
}
|
|
62
|
+
function makeEventId(eventName, primaryKey, minuteBucket = minuteBucketNow()) {
|
|
63
|
+
return `${eventName}_${safeKey(primaryKey)}_${minuteBucket}`;
|
|
64
|
+
}
|
|
65
|
+
function deriveOrderIdForPurchase(eventName, orderId) {
|
|
66
|
+
if (eventName !== "PURCHASE") return void 0;
|
|
67
|
+
if (typeof orderId !== "string") return void 0;
|
|
68
|
+
const trimmed = orderId.trim();
|
|
69
|
+
return trimmed ? trimmed : void 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/hooks/useSnapchatEvents.ts
|
|
73
|
+
var CAPI_PROXY_ENDPOINT = "/api/snapchat-ads/events";
|
|
74
|
+
function currentUrl() {
|
|
75
|
+
return typeof window !== "undefined" ? window.location.href : "";
|
|
76
|
+
}
|
|
77
|
+
async function sendSnapchatCapiEvent(payload) {
|
|
78
|
+
try {
|
|
79
|
+
await fetch(CAPI_PROXY_ENDPOINT, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
body: JSON.stringify(payload)
|
|
83
|
+
});
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function trackPageView(primaryKey, userData = {}, eventId) {
|
|
88
|
+
const eid = eventId ?? makeEventId("PAGE_VIEW", primaryKey, minuteBucketNow());
|
|
89
|
+
safeSnaptr("track", "PAGE_VIEW", { client_dedup_id: eid });
|
|
90
|
+
void sendSnapchatCapiEvent({
|
|
91
|
+
eventName: "PAGE_VIEW",
|
|
92
|
+
eventId: eid,
|
|
93
|
+
eventSourceUrl: currentUrl(),
|
|
94
|
+
userData
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function trackViewContent(productId, options = {}) {
|
|
98
|
+
const eid = options.eventId ?? makeEventId("VIEW_CONTENT", productId, minuteBucketNow());
|
|
99
|
+
const contentIds = options.contentIds ?? [productId];
|
|
100
|
+
const customData = {
|
|
101
|
+
...options.currency ? { currency: options.currency } : {},
|
|
102
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
103
|
+
...contentIds.length ? { contentIds } : {},
|
|
104
|
+
...options.contents?.length ? { contents: options.contents } : {},
|
|
105
|
+
...typeof options.numItems === "number" ? { numItems: options.numItems } : {}
|
|
106
|
+
};
|
|
107
|
+
safeSnaptr("track", "VIEW_CONTENT", {
|
|
108
|
+
...contentIds.length ? { item_ids: contentIds } : {},
|
|
109
|
+
...options.currency ? { currency: options.currency } : {},
|
|
110
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
111
|
+
client_dedup_id: eid
|
|
112
|
+
});
|
|
113
|
+
void sendSnapchatCapiEvent({
|
|
114
|
+
eventName: "VIEW_CONTENT",
|
|
115
|
+
eventId: eid,
|
|
116
|
+
eventSourceUrl: currentUrl(),
|
|
117
|
+
userData: options.userData ?? {},
|
|
118
|
+
customData
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function trackAddCart(cartId, options = {}) {
|
|
122
|
+
const eid = options.eventId ?? makeEventId("ADD_CART", cartId, minuteBucketNow());
|
|
123
|
+
const customData = {
|
|
124
|
+
...options.currency ? { currency: options.currency } : {},
|
|
125
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
126
|
+
...options.contentIds?.length ? { contentIds: options.contentIds } : {},
|
|
127
|
+
...options.contents?.length ? { contents: options.contents } : {},
|
|
128
|
+
...typeof options.numItems === "number" ? { numItems: options.numItems } : {}
|
|
129
|
+
};
|
|
130
|
+
safeSnaptr("track", "ADD_CART", {
|
|
131
|
+
...options.contentIds?.length ? { item_ids: options.contentIds } : {},
|
|
132
|
+
...options.currency ? { currency: options.currency } : {},
|
|
133
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
134
|
+
...typeof options.numItems === "number" ? { number_items: options.numItems } : {},
|
|
135
|
+
client_dedup_id: eid
|
|
136
|
+
});
|
|
137
|
+
void sendSnapchatCapiEvent({
|
|
138
|
+
eventName: "ADD_CART",
|
|
139
|
+
eventId: eid,
|
|
140
|
+
eventSourceUrl: currentUrl(),
|
|
141
|
+
userData: options.userData ?? {},
|
|
142
|
+
customData
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function trackStartCheckout(cartId, options = {}) {
|
|
146
|
+
const eid = options.eventId ?? makeEventId("START_CHECKOUT", cartId, minuteBucketNow());
|
|
147
|
+
const customData = {
|
|
148
|
+
...options.currency ? { currency: options.currency } : {},
|
|
149
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
150
|
+
...options.contentIds?.length ? { contentIds: options.contentIds } : {},
|
|
151
|
+
...options.contents?.length ? { contents: options.contents } : {},
|
|
152
|
+
...typeof options.numItems === "number" ? { numItems: options.numItems } : {}
|
|
153
|
+
};
|
|
154
|
+
safeSnaptr("track", "START_CHECKOUT", {
|
|
155
|
+
...options.contentIds?.length ? { item_ids: options.contentIds } : {},
|
|
156
|
+
...options.currency ? { currency: options.currency } : {},
|
|
157
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
158
|
+
...typeof options.numItems === "number" ? { number_items: options.numItems } : {},
|
|
159
|
+
client_dedup_id: eid
|
|
160
|
+
});
|
|
161
|
+
void sendSnapchatCapiEvent({
|
|
162
|
+
eventName: "START_CHECKOUT",
|
|
163
|
+
eventId: eid,
|
|
164
|
+
eventSourceUrl: currentUrl(),
|
|
165
|
+
userData: options.userData ?? {},
|
|
166
|
+
customData
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function trackPurchase(orderId, options = {}) {
|
|
170
|
+
const eid = options.eventId ?? makeEventId("PURCHASE", orderId, 0);
|
|
171
|
+
const derivedOrderId = deriveOrderIdForPurchase("PURCHASE", orderId);
|
|
172
|
+
const customData = {
|
|
173
|
+
...options.currency ? { currency: options.currency } : {},
|
|
174
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
175
|
+
...options.contentIds?.length ? { contentIds: options.contentIds } : {},
|
|
176
|
+
...options.contents?.length ? { contents: options.contents } : {},
|
|
177
|
+
...typeof options.numItems === "number" ? { numItems: options.numItems } : {},
|
|
178
|
+
...derivedOrderId ? { orderId: derivedOrderId } : {}
|
|
179
|
+
};
|
|
180
|
+
safeSnaptr("track", "PURCHASE", {
|
|
181
|
+
...options.contentIds?.length ? { item_ids: options.contentIds } : {},
|
|
182
|
+
...options.currency ? { currency: options.currency } : {},
|
|
183
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
184
|
+
...typeof options.numItems === "number" ? { number_items: options.numItems } : {},
|
|
185
|
+
...derivedOrderId ? { transaction_id: derivedOrderId } : {},
|
|
186
|
+
client_dedup_id: eid
|
|
187
|
+
});
|
|
188
|
+
void sendSnapchatCapiEvent({
|
|
189
|
+
eventName: "PURCHASE",
|
|
190
|
+
eventId: eid,
|
|
191
|
+
eventSourceUrl: currentUrl(),
|
|
192
|
+
userData: options.userData ?? {},
|
|
193
|
+
customData
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/get-config.ts
|
|
198
|
+
function resolveEnv(key) {
|
|
199
|
+
if (key === "NEXT_PUBLIC_MEDUSA_BACKEND_URL") return process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL;
|
|
200
|
+
if (key === "NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY") return process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY;
|
|
201
|
+
if (key === "NEXT_PUBLIC_STORE_ID") return process.env.NEXT_PUBLIC_STORE_ID;
|
|
202
|
+
return void 0;
|
|
203
|
+
}
|
|
204
|
+
async function getSnapchatAdsConfig(options = {}) {
|
|
205
|
+
const baseUrl = options.baseUrl ?? resolveEnv("NEXT_PUBLIC_MEDUSA_BACKEND_URL") ?? "http://localhost:9000";
|
|
206
|
+
const publishableKey = options.publishableApiKey ?? resolveEnv("NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY");
|
|
207
|
+
const storeId = options.storeId ?? resolveEnv("NEXT_PUBLIC_STORE_ID");
|
|
208
|
+
try {
|
|
209
|
+
const res = await fetch(`${baseUrl}/store/snapchat-ads/config`, {
|
|
210
|
+
headers: {
|
|
211
|
+
...storeId ? { "X-Store-Environment-ID": storeId } : {},
|
|
212
|
+
...publishableKey ? { "x-publishable-api-key": publishableKey } : {}
|
|
213
|
+
},
|
|
214
|
+
next: { revalidate: 60 }
|
|
215
|
+
});
|
|
216
|
+
if (!res.ok) return null;
|
|
217
|
+
return await res.json();
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
223
|
+
0 && (module.exports = {
|
|
224
|
+
deriveMinuteBucket,
|
|
225
|
+
getSnapchatAdsConfig,
|
|
226
|
+
makeEventId,
|
|
227
|
+
minuteBucketNow,
|
|
228
|
+
sendSnapchatCapiEvent,
|
|
229
|
+
trackAddCart,
|
|
230
|
+
trackPageView,
|
|
231
|
+
trackPurchase,
|
|
232
|
+
trackStartCheckout,
|
|
233
|
+
trackViewContent
|
|
234
|
+
});
|
|
235
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/lib/snap-pixel-loader.ts","../src/lib/dedup.ts","../src/hooks/useSnapchatEvents.ts","../src/get-config.ts"],"sourcesContent":["export {\n trackPageView,\n trackViewContent,\n trackAddCart,\n trackStartCheckout,\n trackPurchase,\n sendSnapchatCapiEvent,\n} from './hooks/useSnapchatEvents'\n\nexport { getSnapchatAdsConfig } from './get-config'\n\nexport { makeEventId, deriveMinuteBucket, minuteBucketNow } from './lib/dedup'\n\n// `SnapchatAdsConfig` (the secret-bearing full shape with\n// `snap_conversion_api_token` / `snap_ad_account_id`) is intentionally NOT\n// exported: those fields never leave the Medusa side, and re-exporting the\n// type from a browser package invites callers to write handling that\n// assumes a secret is present. Storefront code consumes\n// `SnapchatAdsPublicConfig` only.\nexport type {\n SnapchatAdsPublicConfig,\n SnapEventName,\n SnapUserData,\n SnapCustomData,\n SnapEventPayload,\n} from './types'\n","/**\n * Snap Pixel loader - installs the `snaptr` shim and injects\n * `https://sc-static.net/scevent.min.js`. Snap does NOT ship an\n * npm-installable browser SDK (research memo Section 10), so we inline\n * the same IIFE shape the official Snap install snippet emits. Once the\n * script loads, Snap reconciles the shim queue and subsequent calls\n * flow directly through.\n *\n * The shim mirrors Snap's canonical shape so their loaded pixel finds\n * the expected `handleRequest` / `queue` fields and picks up buffered\n * calls made before the network load completes.\n *\n * Idempotent: subsequent calls no-op if `window.snaptr` is already set,\n * so consent flips or SPA nav doesn't stack multiple scripts.\n */\n\ndeclare global {\n interface Window {\n snaptr?: SnaptrFunction\n }\n}\n\nexport type SnaptrFunction = ((...args: unknown[]) => void) & {\n handleRequest?: (...args: unknown[]) => void\n push?: (...args: unknown[]) => void\n queue?: unknown[][]\n version?: string\n loaded?: boolean\n}\n\nconst SCRIPT_SRC = 'https://sc-static.net/scevent.min.js'\n\n/**\n * Install the snaptr shim + inject `sc-static.net/scevent.min.js`.\n * Idempotent - safe to call multiple times.\n */\nexport function installSnapPixelShim(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') return\n if (window.snaptr) return\n\n const queue: unknown[][] = []\n const shim: SnaptrFunction = function (...args: unknown[]): void {\n if (typeof shim.handleRequest === 'function') {\n shim.handleRequest(...args)\n return\n }\n queue.push(args)\n } as SnaptrFunction\n shim.push = function (...args: unknown[]): void {\n queue.push(args)\n }\n shim.queue = queue\n shim.version = '1.0'\n shim.loaded = true\n window.snaptr = shim\n\n const script = document.createElement('script')\n script.async = true\n script.src = SCRIPT_SRC\n document.head.appendChild(script)\n}\n\n/**\n * Best-effort synchronous call. Returns whether snaptr was ready. Slot\n * components use this so events queued before `scevent.min.js` finishes\n * loading still land - Snap's script drains the shim queue on load.\n */\nexport function safeSnaptr(...args: unknown[]): boolean {\n if (typeof window === 'undefined') return false\n const fn = window.snaptr\n if (typeof fn !== 'function') return false\n try {\n fn(...args)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Poll `window.snaptr` on `requestAnimationFrame` up to `maxAttempts`\n * times, invoking the callback on the first tick where snaptr exists.\n * Escape hatch for callers that must run the moment snaptr becomes\n * ready. The standard slot components rely on the shim queue via\n * `safeSnaptr()`.\n */\nexport function whenSnaptrReady(cb: (fn: SnaptrFunction) => void, maxAttempts = 20): () => void {\n if (typeof window === 'undefined') return () => {}\n let cancelled = false\n let attempts = 0\n\n const tick = (): void => {\n if (cancelled) return\n attempts += 1\n const fn = window.snaptr\n if (typeof fn === 'function') {\n cb(fn)\n return\n }\n if (attempts >= maxAttempts) return\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(tick)\n } else {\n setTimeout(tick, 50)\n }\n }\n\n tick()\n return () => {\n cancelled = true\n }\n}\n","import type { SnapEventName } from '../types'\n\n/**\n * Deterministic event-ID builder for Snap browser <-> CAPI dedup.\n *\n * Event ID contract (research memo Section 5, encoded verbatim):\n *\n * > For the 5 events (PAGE_VIEW, VIEW_CONTENT, ADD_CART, START_CHECKOUT,\n * > PURCHASE), the browser Pixel call and the CAPI server call MUST share a\n * > top-level `event_id` (Pixel side: `client_dedup_id`), matched by Snap\n * > within a 48-hour window. For PURCHASE ONLY, they MUST additionally share\n * > `custom_data.order_id` (CAPI) / `transaction_id` (Pixel), matched within\n * > a 30-day window. Reuse the Amboras cart/order id as both `order_id` and\n * > `transaction_id`; mint a deterministic UUID from `(event_name,\n * > cart_id_or_session_id, minute_bucket)` for `event_id`/`client_dedup_id`.\n * > Never let the Pixel SDK auto-mint `client_dedup_id` - it prefixes with\n * > `@-` and never matches CAPI (research memo Section 5).\n *\n * Determinism matters because the browser Pixel call and the CAPI relay\n * call happen in parallel from the same user action - if the IDs don't\n * match, Snap counts the event twice.\n *\n * Contract:\n * - Same `(eventName, primaryKey, minuteBucket)` always produces the\n * same ID.\n * - Different event names with the same primary key produce different\n * IDs (a `VIEW_CONTENT` and `ADD_CART` for the same product shouldn't\n * collide).\n * - The minute-bucket component keeps browser retries within the same\n * minute stable (both sides land on the same ID), but rotates the ID\n * for user actions in a subsequent minute so we don't accidentally\n * collapse two genuine events.\n */\n\nconst MS_PER_MINUTE = 60 * 1000\n\n/** Bucket a Unix millisecond timestamp into whole minutes. */\nexport function deriveMinuteBucket(nowMs: number): number {\n if (!Number.isFinite(nowMs)) return 0\n return Math.floor(nowMs / MS_PER_MINUTE)\n}\n\n/** Convenience wrapper for `deriveMinuteBucket(Date.now())`. */\nexport function minuteBucketNow(): number {\n return deriveMinuteBucket(Date.now())\n}\n\n/**\n * Sanitize a primary key for inclusion in an event id. Snap accepts\n * alphanumerics + `_` + `-`; anything else gets folded to `_` so a\n * merchant using URLs with query strings as page-view keys doesn't\n * produce malformed ids.\n */\nfunction safeKey(primaryKey: string): string {\n if (!primaryKey) return 'unknown'\n return primaryKey.replace(/[^a-zA-Z0-9_-]/g, '_')\n}\n\n/**\n * Mint a deterministic event id for `event_id` (CAPI) and\n * `client_dedup_id` (Pixel). See contract above.\n *\n * Callers that want a stable id across a longer window (e.g. tying two\n * page-view fires back to back to the same URL) can pin `minuteBucket` to\n * a well-known value; the default is `minuteBucketNow()`. For PURCHASE,\n * pin `minuteBucket` to a constant per order (recommend `0`) so a\n * failing browser fire that retries in a later minute still dedups\n * against the server event.\n */\nexport function makeEventId(\n eventName: SnapEventName,\n primaryKey: string,\n minuteBucket: number = minuteBucketNow(),\n): string {\n return `${eventName}_${safeKey(primaryKey)}_${minuteBucket}`\n}\n\n/**\n * Derivation helper for the PURCHASE event's 30-day dedup pair. Reuses\n * the Amboras order id as both `custom_data.order_id` (CAPI) and\n * `transaction_id` (Pixel) so Snap can dedup on the 30-day window even\n * if the 48h `event_id` window has expired.\n *\n * Returns `undefined` for non-PURCHASE events (encodes the \"PURCHASE\n * only\" leg of the dual-dedup scheme).\n */\nexport function deriveOrderIdForPurchase(\n eventName: SnapEventName,\n orderId: string | undefined,\n): string | undefined {\n if (eventName !== 'PURCHASE') return undefined\n if (typeof orderId !== 'string') return undefined\n const trimmed = orderId.trim()\n return trimmed ? trimmed : undefined\n}\n","import type { SnapEventName, SnapEventPayload, SnapUserData, SnapCustomData } from '../types'\nimport { safeSnaptr } from '../lib/snap-pixel-loader'\nimport { makeEventId, minuteBucketNow, deriveOrderIdForPurchase } from '../lib/dedup'\n\n/**\n * Client-side dual-fire hooks for Snap. Every event flows two ways from\n * the same user action:\n *\n * 1. Browser pixel: `snaptr('track', <EVENT_NAME>, { ..., client_dedup_id })`\n * via the loaded `scevent.min.js`.\n * 2. Server-side CAPI: POST to the storefront proxy at\n * `/api/snapchat-ads/events` which enriches with\n * `client_ip_address` + `client_user_agent` and forwards to Medusa\n * `/store/snapchat-ads/capi`.\n *\n * Both sides carry the SAME `event_id` / `client_dedup_id` so Snap can\n * dedup within 48h. For PURCHASE only, both sides also share\n * `custom_data.order_id` / `transaction_id` for the 30-day dedup window\n * (research memo Section 5).\n *\n * Snap v3 wire caveats encoded here:\n * - Event names are UPPER_SNAKE_CASE (PAGE_VIEW / VIEW_CONTENT /\n * ADD_CART / START_CHECKOUT / PURCHASE).\n * - `value`, `item_price`, `quantity` are STRINGS in the CAPI payload.\n * The browser pixel is more lenient but we mirror the same string\n * serialization so the two sides stay symmetric and dedup diffs read\n * cleanly in logs.\n * - PII (`em`, `ph`, `madid`, `external_id`) is passed RAW from the\n * browser and hashed on the Medusa side by `sendSnapchatCapiEvent`.\n * The proxy strips raw PII before forwarding to Snap.\n *\n * All fires are non-throwing - a failed CAPI relay is logged, never\n * bubbled, so a merchant's storefront cannot break because Snap's edge\n * had a hiccup.\n */\n\nconst CAPI_PROXY_ENDPOINT = '/api/snapchat-ads/events'\n\nfunction currentUrl(): string {\n return typeof window !== 'undefined' ? window.location.href : ''\n}\n\n/**\n * Send a server-side Snap CAPI event via the storefront's proxy. Fire-\n * and-forget from the caller's perspective; internal errors are swallowed\n * because analytics loss is preferable to storefront breakage.\n *\n * Intentionally NOT gated on analytics consent - Snap CAPI operates on a\n * different legal basis (order-tracking legitimate interest) than the\n * browser pixel. The `SnapPixelProvider` handles browser-side consent\n * gating separately.\n */\nexport async function sendSnapchatCapiEvent(payload: SnapEventPayload): Promise<void> {\n try {\n await fetch(CAPI_PROXY_ENDPOINT, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch {\n // non-fatal\n }\n}\n\n/**\n * Fire PAGE_VIEW on both sides. The `SnapPixelProvider` already fires\n * this on mount + route change; this hook is exposed for callers that\n * want to trigger a page-view manually (e.g. modal opens counted as\n * page views).\n */\nexport function trackPageView(\n primaryKey: string,\n userData: SnapUserData = {},\n eventId?: string,\n): void {\n const eid = eventId ?? makeEventId('PAGE_VIEW', primaryKey, minuteBucketNow())\n safeSnaptr('track', 'PAGE_VIEW', { client_dedup_id: eid })\n void sendSnapchatCapiEvent({\n eventName: 'PAGE_VIEW',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData,\n })\n}\n\nexport interface TrackViewContentOptions {\n currency?: string\n /** Major-unit value STRING (e.g. `\"10.50\"`). */\n value?: string\n /** Product / variant id passed to Snap as `content_ids`. */\n contentIds?: string[]\n /** Optional per-line breakdown. `item_price` + `quantity` are STRINGS. */\n contents?: Array<{ id: string; quantity: string; item_price?: string }>\n numItems?: number\n userData?: SnapUserData\n eventId?: string\n}\n\nexport function trackViewContent(productId: string, options: TrackViewContentOptions = {}): void {\n const eid = options.eventId ?? makeEventId('VIEW_CONTENT', productId, minuteBucketNow())\n const contentIds = options.contentIds ?? [productId]\n const customData: SnapCustomData = {\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(contentIds.length ? { contentIds } : {}),\n ...(options.contents?.length ? { contents: options.contents } : {}),\n ...(typeof options.numItems === 'number' ? { numItems: options.numItems } : {}),\n }\n safeSnaptr('track', 'VIEW_CONTENT', {\n ...(contentIds.length ? { item_ids: contentIds } : {}),\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n client_dedup_id: eid,\n })\n void sendSnapchatCapiEvent({\n eventName: 'VIEW_CONTENT',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData: options.userData ?? {},\n customData,\n })\n}\n\nexport interface TrackAddCartOptions {\n currency?: string\n /** Major-unit value STRING. */\n value?: string\n /** Product / variant ids. */\n contentIds?: string[]\n contents?: Array<{ id: string; quantity: string; item_price?: string }>\n /** Top-level `num_items` - number-typed per Snap docs. */\n numItems?: number\n userData?: SnapUserData\n eventId?: string\n}\n\nexport function trackAddCart(cartId: string, options: TrackAddCartOptions = {}): void {\n const eid = options.eventId ?? makeEventId('ADD_CART', cartId, minuteBucketNow())\n const customData: SnapCustomData = {\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(options.contentIds?.length ? { contentIds: options.contentIds } : {}),\n ...(options.contents?.length ? { contents: options.contents } : {}),\n ...(typeof options.numItems === 'number' ? { numItems: options.numItems } : {}),\n }\n safeSnaptr('track', 'ADD_CART', {\n ...(options.contentIds?.length ? { item_ids: options.contentIds } : {}),\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(typeof options.numItems === 'number' ? { number_items: options.numItems } : {}),\n client_dedup_id: eid,\n })\n void sendSnapchatCapiEvent({\n eventName: 'ADD_CART',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData: options.userData ?? {},\n customData,\n })\n}\n\nexport interface TrackStartCheckoutOptions {\n currency?: string\n value?: string\n contentIds?: string[]\n contents?: Array<{ id: string; quantity: string; item_price?: string }>\n numItems?: number\n userData?: SnapUserData\n eventId?: string\n}\n\nexport function trackStartCheckout(cartId: string, options: TrackStartCheckoutOptions = {}): void {\n const eid = options.eventId ?? makeEventId('START_CHECKOUT', cartId, minuteBucketNow())\n const customData: SnapCustomData = {\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(options.contentIds?.length ? { contentIds: options.contentIds } : {}),\n ...(options.contents?.length ? { contents: options.contents } : {}),\n ...(typeof options.numItems === 'number' ? { numItems: options.numItems } : {}),\n }\n safeSnaptr('track', 'START_CHECKOUT', {\n ...(options.contentIds?.length ? { item_ids: options.contentIds } : {}),\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(typeof options.numItems === 'number' ? { number_items: options.numItems } : {}),\n client_dedup_id: eid,\n })\n void sendSnapchatCapiEvent({\n eventName: 'START_CHECKOUT',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData: options.userData ?? {},\n customData,\n })\n}\n\nexport interface TrackPurchaseOptions {\n currency?: string\n value?: string\n contentIds?: string[]\n contents?: Array<{ id: string; quantity: string; item_price?: string }>\n numItems?: number\n userData?: SnapUserData\n /**\n * When present, pins `event_id` / `client_dedup_id` explicitly; otherwise\n * a stable id is derived from `(PURCHASE, orderId, 0)` - minute-bucket 0\n * so retries within a longer window still dedup against the server event.\n */\n eventId?: string\n}\n\n/**\n * PURCHASE gets the dual-dedup treatment:\n * - Top-level `event_id` / Pixel `client_dedup_id` shared for 48h window.\n * - `custom_data.order_id` / Pixel `transaction_id` shared for 30-day\n * window. Reuses the Amboras order id as both.\n */\nexport function trackPurchase(orderId: string, options: TrackPurchaseOptions = {}): void {\n // Pin minute-bucket to 0 for PURCHASE so a browser retry in a later\n // minute still lands on the same event_id as the initial server fire.\n const eid = options.eventId ?? makeEventId('PURCHASE', orderId, 0)\n const derivedOrderId = deriveOrderIdForPurchase('PURCHASE', orderId)\n const customData: SnapCustomData = {\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(options.contentIds?.length ? { contentIds: options.contentIds } : {}),\n ...(options.contents?.length ? { contents: options.contents } : {}),\n ...(typeof options.numItems === 'number' ? { numItems: options.numItems } : {}),\n ...(derivedOrderId ? { orderId: derivedOrderId } : {}),\n }\n safeSnaptr('track', 'PURCHASE', {\n ...(options.contentIds?.length ? { item_ids: options.contentIds } : {}),\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(typeof options.numItems === 'number' ? { number_items: options.numItems } : {}),\n ...(derivedOrderId ? { transaction_id: derivedOrderId } : {}),\n client_dedup_id: eid,\n })\n void sendSnapchatCapiEvent({\n eventName: 'PURCHASE',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData: options.userData ?? {},\n customData,\n })\n}\n","import type { SnapchatAdsPublicConfig } from './types'\n\nfunction resolveEnv(key: string): string | undefined {\n if (key === 'NEXT_PUBLIC_MEDUSA_BACKEND_URL') return process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL\n if (key === 'NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY') return process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY\n if (key === 'NEXT_PUBLIC_STORE_ID') return process.env.NEXT_PUBLIC_STORE_ID\n return undefined\n}\n\ninterface GetSnapchatAdsConfigOptions {\n baseUrl?: string\n publishableApiKey?: string\n storeId?: string\n}\n\n/**\n * Fetch the non-secret Snapchat Ads config from the Medusa Store API. Returns\n * `null` when the store isn't wired up, when the publishable key is missing,\n * or on any transport error - callers should treat `null` as \"plugin\n * off\" and skip event firing.\n *\n * NEVER surfaces `snap_conversion_api_token` or `snap_ad_account_id` -\n * those live only on the Medusa side and never leave the server.\n */\nexport async function getSnapchatAdsConfig(\n options: GetSnapchatAdsConfigOptions = {},\n): Promise<SnapchatAdsPublicConfig | null> {\n const baseUrl = options.baseUrl ?? resolveEnv('NEXT_PUBLIC_MEDUSA_BACKEND_URL') ?? 'http://localhost:9000'\n const publishableKey = options.publishableApiKey ?? resolveEnv('NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY')\n const storeId = options.storeId ?? resolveEnv('NEXT_PUBLIC_STORE_ID')\n\n try {\n // `next` is a Next.js fetch extension for cache revalidation - ignored outside Next.js\n const res = await fetch(`${baseUrl}/store/snapchat-ads/config`, {\n headers: {\n ...(storeId ? { 'X-Store-Environment-ID': storeId } : {}),\n ...(publishableKey ? { 'x-publishable-api-key': publishableKey } : {}),\n },\n next: { revalidate: 60 },\n } as RequestInit)\n if (!res.ok) return null\n return (await res.json()) as SnapchatAdsPublicConfig\n } catch {\n return null\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSnapchatAdsConfig
|
|
3
|
+
} from "./chunk-PK23QMZT.mjs";
|
|
4
|
+
|
|
5
|
+
// src/lib/snap-pixel-loader.ts
|
|
6
|
+
function safeSnaptr(...args) {
|
|
7
|
+
if (typeof window === "undefined") return false;
|
|
8
|
+
const fn = window.snaptr;
|
|
9
|
+
if (typeof fn !== "function") return false;
|
|
10
|
+
try {
|
|
11
|
+
fn(...args);
|
|
12
|
+
return true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/lib/dedup.ts
|
|
19
|
+
var MS_PER_MINUTE = 60 * 1e3;
|
|
20
|
+
function deriveMinuteBucket(nowMs) {
|
|
21
|
+
if (!Number.isFinite(nowMs)) return 0;
|
|
22
|
+
return Math.floor(nowMs / MS_PER_MINUTE);
|
|
23
|
+
}
|
|
24
|
+
function minuteBucketNow() {
|
|
25
|
+
return deriveMinuteBucket(Date.now());
|
|
26
|
+
}
|
|
27
|
+
function safeKey(primaryKey) {
|
|
28
|
+
if (!primaryKey) return "unknown";
|
|
29
|
+
return primaryKey.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
30
|
+
}
|
|
31
|
+
function makeEventId(eventName, primaryKey, minuteBucket = minuteBucketNow()) {
|
|
32
|
+
return `${eventName}_${safeKey(primaryKey)}_${minuteBucket}`;
|
|
33
|
+
}
|
|
34
|
+
function deriveOrderIdForPurchase(eventName, orderId) {
|
|
35
|
+
if (eventName !== "PURCHASE") return void 0;
|
|
36
|
+
if (typeof orderId !== "string") return void 0;
|
|
37
|
+
const trimmed = orderId.trim();
|
|
38
|
+
return trimmed ? trimmed : void 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/hooks/useSnapchatEvents.ts
|
|
42
|
+
var CAPI_PROXY_ENDPOINT = "/api/snapchat-ads/events";
|
|
43
|
+
function currentUrl() {
|
|
44
|
+
return typeof window !== "undefined" ? window.location.href : "";
|
|
45
|
+
}
|
|
46
|
+
async function sendSnapchatCapiEvent(payload) {
|
|
47
|
+
try {
|
|
48
|
+
await fetch(CAPI_PROXY_ENDPOINT, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify(payload)
|
|
52
|
+
});
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function trackPageView(primaryKey, userData = {}, eventId) {
|
|
57
|
+
const eid = eventId ?? makeEventId("PAGE_VIEW", primaryKey, minuteBucketNow());
|
|
58
|
+
safeSnaptr("track", "PAGE_VIEW", { client_dedup_id: eid });
|
|
59
|
+
void sendSnapchatCapiEvent({
|
|
60
|
+
eventName: "PAGE_VIEW",
|
|
61
|
+
eventId: eid,
|
|
62
|
+
eventSourceUrl: currentUrl(),
|
|
63
|
+
userData
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function trackViewContent(productId, options = {}) {
|
|
67
|
+
const eid = options.eventId ?? makeEventId("VIEW_CONTENT", productId, minuteBucketNow());
|
|
68
|
+
const contentIds = options.contentIds ?? [productId];
|
|
69
|
+
const customData = {
|
|
70
|
+
...options.currency ? { currency: options.currency } : {},
|
|
71
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
72
|
+
...contentIds.length ? { contentIds } : {},
|
|
73
|
+
...options.contents?.length ? { contents: options.contents } : {},
|
|
74
|
+
...typeof options.numItems === "number" ? { numItems: options.numItems } : {}
|
|
75
|
+
};
|
|
76
|
+
safeSnaptr("track", "VIEW_CONTENT", {
|
|
77
|
+
...contentIds.length ? { item_ids: contentIds } : {},
|
|
78
|
+
...options.currency ? { currency: options.currency } : {},
|
|
79
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
80
|
+
client_dedup_id: eid
|
|
81
|
+
});
|
|
82
|
+
void sendSnapchatCapiEvent({
|
|
83
|
+
eventName: "VIEW_CONTENT",
|
|
84
|
+
eventId: eid,
|
|
85
|
+
eventSourceUrl: currentUrl(),
|
|
86
|
+
userData: options.userData ?? {},
|
|
87
|
+
customData
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function trackAddCart(cartId, options = {}) {
|
|
91
|
+
const eid = options.eventId ?? makeEventId("ADD_CART", cartId, minuteBucketNow());
|
|
92
|
+
const customData = {
|
|
93
|
+
...options.currency ? { currency: options.currency } : {},
|
|
94
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
95
|
+
...options.contentIds?.length ? { contentIds: options.contentIds } : {},
|
|
96
|
+
...options.contents?.length ? { contents: options.contents } : {},
|
|
97
|
+
...typeof options.numItems === "number" ? { numItems: options.numItems } : {}
|
|
98
|
+
};
|
|
99
|
+
safeSnaptr("track", "ADD_CART", {
|
|
100
|
+
...options.contentIds?.length ? { item_ids: options.contentIds } : {},
|
|
101
|
+
...options.currency ? { currency: options.currency } : {},
|
|
102
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
103
|
+
...typeof options.numItems === "number" ? { number_items: options.numItems } : {},
|
|
104
|
+
client_dedup_id: eid
|
|
105
|
+
});
|
|
106
|
+
void sendSnapchatCapiEvent({
|
|
107
|
+
eventName: "ADD_CART",
|
|
108
|
+
eventId: eid,
|
|
109
|
+
eventSourceUrl: currentUrl(),
|
|
110
|
+
userData: options.userData ?? {},
|
|
111
|
+
customData
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function trackStartCheckout(cartId, options = {}) {
|
|
115
|
+
const eid = options.eventId ?? makeEventId("START_CHECKOUT", cartId, minuteBucketNow());
|
|
116
|
+
const customData = {
|
|
117
|
+
...options.currency ? { currency: options.currency } : {},
|
|
118
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
119
|
+
...options.contentIds?.length ? { contentIds: options.contentIds } : {},
|
|
120
|
+
...options.contents?.length ? { contents: options.contents } : {},
|
|
121
|
+
...typeof options.numItems === "number" ? { numItems: options.numItems } : {}
|
|
122
|
+
};
|
|
123
|
+
safeSnaptr("track", "START_CHECKOUT", {
|
|
124
|
+
...options.contentIds?.length ? { item_ids: options.contentIds } : {},
|
|
125
|
+
...options.currency ? { currency: options.currency } : {},
|
|
126
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
127
|
+
...typeof options.numItems === "number" ? { number_items: options.numItems } : {},
|
|
128
|
+
client_dedup_id: eid
|
|
129
|
+
});
|
|
130
|
+
void sendSnapchatCapiEvent({
|
|
131
|
+
eventName: "START_CHECKOUT",
|
|
132
|
+
eventId: eid,
|
|
133
|
+
eventSourceUrl: currentUrl(),
|
|
134
|
+
userData: options.userData ?? {},
|
|
135
|
+
customData
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function trackPurchase(orderId, options = {}) {
|
|
139
|
+
const eid = options.eventId ?? makeEventId("PURCHASE", orderId, 0);
|
|
140
|
+
const derivedOrderId = deriveOrderIdForPurchase("PURCHASE", orderId);
|
|
141
|
+
const customData = {
|
|
142
|
+
...options.currency ? { currency: options.currency } : {},
|
|
143
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
144
|
+
...options.contentIds?.length ? { contentIds: options.contentIds } : {},
|
|
145
|
+
...options.contents?.length ? { contents: options.contents } : {},
|
|
146
|
+
...typeof options.numItems === "number" ? { numItems: options.numItems } : {},
|
|
147
|
+
...derivedOrderId ? { orderId: derivedOrderId } : {}
|
|
148
|
+
};
|
|
149
|
+
safeSnaptr("track", "PURCHASE", {
|
|
150
|
+
...options.contentIds?.length ? { item_ids: options.contentIds } : {},
|
|
151
|
+
...options.currency ? { currency: options.currency } : {},
|
|
152
|
+
...options.value !== void 0 ? { value: options.value } : {},
|
|
153
|
+
...typeof options.numItems === "number" ? { number_items: options.numItems } : {},
|
|
154
|
+
...derivedOrderId ? { transaction_id: derivedOrderId } : {},
|
|
155
|
+
client_dedup_id: eid
|
|
156
|
+
});
|
|
157
|
+
void sendSnapchatCapiEvent({
|
|
158
|
+
eventName: "PURCHASE",
|
|
159
|
+
eventId: eid,
|
|
160
|
+
eventSourceUrl: currentUrl(),
|
|
161
|
+
userData: options.userData ?? {},
|
|
162
|
+
customData
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
deriveMinuteBucket,
|
|
167
|
+
getSnapchatAdsConfig,
|
|
168
|
+
makeEventId,
|
|
169
|
+
minuteBucketNow,
|
|
170
|
+
sendSnapchatCapiEvent,
|
|
171
|
+
trackAddCart,
|
|
172
|
+
trackPageView,
|
|
173
|
+
trackPurchase,
|
|
174
|
+
trackStartCheckout,
|
|
175
|
+
trackViewContent
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/snap-pixel-loader.ts","../src/lib/dedup.ts","../src/hooks/useSnapchatEvents.ts"],"sourcesContent":["/**\n * Snap Pixel loader - installs the `snaptr` shim and injects\n * `https://sc-static.net/scevent.min.js`. Snap does NOT ship an\n * npm-installable browser SDK (research memo Section 10), so we inline\n * the same IIFE shape the official Snap install snippet emits. Once the\n * script loads, Snap reconciles the shim queue and subsequent calls\n * flow directly through.\n *\n * The shim mirrors Snap's canonical shape so their loaded pixel finds\n * the expected `handleRequest` / `queue` fields and picks up buffered\n * calls made before the network load completes.\n *\n * Idempotent: subsequent calls no-op if `window.snaptr` is already set,\n * so consent flips or SPA nav doesn't stack multiple scripts.\n */\n\ndeclare global {\n interface Window {\n snaptr?: SnaptrFunction\n }\n}\n\nexport type SnaptrFunction = ((...args: unknown[]) => void) & {\n handleRequest?: (...args: unknown[]) => void\n push?: (...args: unknown[]) => void\n queue?: unknown[][]\n version?: string\n loaded?: boolean\n}\n\nconst SCRIPT_SRC = 'https://sc-static.net/scevent.min.js'\n\n/**\n * Install the snaptr shim + inject `sc-static.net/scevent.min.js`.\n * Idempotent - safe to call multiple times.\n */\nexport function installSnapPixelShim(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') return\n if (window.snaptr) return\n\n const queue: unknown[][] = []\n const shim: SnaptrFunction = function (...args: unknown[]): void {\n if (typeof shim.handleRequest === 'function') {\n shim.handleRequest(...args)\n return\n }\n queue.push(args)\n } as SnaptrFunction\n shim.push = function (...args: unknown[]): void {\n queue.push(args)\n }\n shim.queue = queue\n shim.version = '1.0'\n shim.loaded = true\n window.snaptr = shim\n\n const script = document.createElement('script')\n script.async = true\n script.src = SCRIPT_SRC\n document.head.appendChild(script)\n}\n\n/**\n * Best-effort synchronous call. Returns whether snaptr was ready. Slot\n * components use this so events queued before `scevent.min.js` finishes\n * loading still land - Snap's script drains the shim queue on load.\n */\nexport function safeSnaptr(...args: unknown[]): boolean {\n if (typeof window === 'undefined') return false\n const fn = window.snaptr\n if (typeof fn !== 'function') return false\n try {\n fn(...args)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Poll `window.snaptr` on `requestAnimationFrame` up to `maxAttempts`\n * times, invoking the callback on the first tick where snaptr exists.\n * Escape hatch for callers that must run the moment snaptr becomes\n * ready. The standard slot components rely on the shim queue via\n * `safeSnaptr()`.\n */\nexport function whenSnaptrReady(cb: (fn: SnaptrFunction) => void, maxAttempts = 20): () => void {\n if (typeof window === 'undefined') return () => {}\n let cancelled = false\n let attempts = 0\n\n const tick = (): void => {\n if (cancelled) return\n attempts += 1\n const fn = window.snaptr\n if (typeof fn === 'function') {\n cb(fn)\n return\n }\n if (attempts >= maxAttempts) return\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(tick)\n } else {\n setTimeout(tick, 50)\n }\n }\n\n tick()\n return () => {\n cancelled = true\n }\n}\n","import type { SnapEventName } from '../types'\n\n/**\n * Deterministic event-ID builder for Snap browser <-> CAPI dedup.\n *\n * Event ID contract (research memo Section 5, encoded verbatim):\n *\n * > For the 5 events (PAGE_VIEW, VIEW_CONTENT, ADD_CART, START_CHECKOUT,\n * > PURCHASE), the browser Pixel call and the CAPI server call MUST share a\n * > top-level `event_id` (Pixel side: `client_dedup_id`), matched by Snap\n * > within a 48-hour window. For PURCHASE ONLY, they MUST additionally share\n * > `custom_data.order_id` (CAPI) / `transaction_id` (Pixel), matched within\n * > a 30-day window. Reuse the Amboras cart/order id as both `order_id` and\n * > `transaction_id`; mint a deterministic UUID from `(event_name,\n * > cart_id_or_session_id, minute_bucket)` for `event_id`/`client_dedup_id`.\n * > Never let the Pixel SDK auto-mint `client_dedup_id` - it prefixes with\n * > `@-` and never matches CAPI (research memo Section 5).\n *\n * Determinism matters because the browser Pixel call and the CAPI relay\n * call happen in parallel from the same user action - if the IDs don't\n * match, Snap counts the event twice.\n *\n * Contract:\n * - Same `(eventName, primaryKey, minuteBucket)` always produces the\n * same ID.\n * - Different event names with the same primary key produce different\n * IDs (a `VIEW_CONTENT` and `ADD_CART` for the same product shouldn't\n * collide).\n * - The minute-bucket component keeps browser retries within the same\n * minute stable (both sides land on the same ID), but rotates the ID\n * for user actions in a subsequent minute so we don't accidentally\n * collapse two genuine events.\n */\n\nconst MS_PER_MINUTE = 60 * 1000\n\n/** Bucket a Unix millisecond timestamp into whole minutes. */\nexport function deriveMinuteBucket(nowMs: number): number {\n if (!Number.isFinite(nowMs)) return 0\n return Math.floor(nowMs / MS_PER_MINUTE)\n}\n\n/** Convenience wrapper for `deriveMinuteBucket(Date.now())`. */\nexport function minuteBucketNow(): number {\n return deriveMinuteBucket(Date.now())\n}\n\n/**\n * Sanitize a primary key for inclusion in an event id. Snap accepts\n * alphanumerics + `_` + `-`; anything else gets folded to `_` so a\n * merchant using URLs with query strings as page-view keys doesn't\n * produce malformed ids.\n */\nfunction safeKey(primaryKey: string): string {\n if (!primaryKey) return 'unknown'\n return primaryKey.replace(/[^a-zA-Z0-9_-]/g, '_')\n}\n\n/**\n * Mint a deterministic event id for `event_id` (CAPI) and\n * `client_dedup_id` (Pixel). See contract above.\n *\n * Callers that want a stable id across a longer window (e.g. tying two\n * page-view fires back to back to the same URL) can pin `minuteBucket` to\n * a well-known value; the default is `minuteBucketNow()`. For PURCHASE,\n * pin `minuteBucket` to a constant per order (recommend `0`) so a\n * failing browser fire that retries in a later minute still dedups\n * against the server event.\n */\nexport function makeEventId(\n eventName: SnapEventName,\n primaryKey: string,\n minuteBucket: number = minuteBucketNow(),\n): string {\n return `${eventName}_${safeKey(primaryKey)}_${minuteBucket}`\n}\n\n/**\n * Derivation helper for the PURCHASE event's 30-day dedup pair. Reuses\n * the Amboras order id as both `custom_data.order_id` (CAPI) and\n * `transaction_id` (Pixel) so Snap can dedup on the 30-day window even\n * if the 48h `event_id` window has expired.\n *\n * Returns `undefined` for non-PURCHASE events (encodes the \"PURCHASE\n * only\" leg of the dual-dedup scheme).\n */\nexport function deriveOrderIdForPurchase(\n eventName: SnapEventName,\n orderId: string | undefined,\n): string | undefined {\n if (eventName !== 'PURCHASE') return undefined\n if (typeof orderId !== 'string') return undefined\n const trimmed = orderId.trim()\n return trimmed ? trimmed : undefined\n}\n","import type { SnapEventName, SnapEventPayload, SnapUserData, SnapCustomData } from '../types'\nimport { safeSnaptr } from '../lib/snap-pixel-loader'\nimport { makeEventId, minuteBucketNow, deriveOrderIdForPurchase } from '../lib/dedup'\n\n/**\n * Client-side dual-fire hooks for Snap. Every event flows two ways from\n * the same user action:\n *\n * 1. Browser pixel: `snaptr('track', <EVENT_NAME>, { ..., client_dedup_id })`\n * via the loaded `scevent.min.js`.\n * 2. Server-side CAPI: POST to the storefront proxy at\n * `/api/snapchat-ads/events` which enriches with\n * `client_ip_address` + `client_user_agent` and forwards to Medusa\n * `/store/snapchat-ads/capi`.\n *\n * Both sides carry the SAME `event_id` / `client_dedup_id` so Snap can\n * dedup within 48h. For PURCHASE only, both sides also share\n * `custom_data.order_id` / `transaction_id` for the 30-day dedup window\n * (research memo Section 5).\n *\n * Snap v3 wire caveats encoded here:\n * - Event names are UPPER_SNAKE_CASE (PAGE_VIEW / VIEW_CONTENT /\n * ADD_CART / START_CHECKOUT / PURCHASE).\n * - `value`, `item_price`, `quantity` are STRINGS in the CAPI payload.\n * The browser pixel is more lenient but we mirror the same string\n * serialization so the two sides stay symmetric and dedup diffs read\n * cleanly in logs.\n * - PII (`em`, `ph`, `madid`, `external_id`) is passed RAW from the\n * browser and hashed on the Medusa side by `sendSnapchatCapiEvent`.\n * The proxy strips raw PII before forwarding to Snap.\n *\n * All fires are non-throwing - a failed CAPI relay is logged, never\n * bubbled, so a merchant's storefront cannot break because Snap's edge\n * had a hiccup.\n */\n\nconst CAPI_PROXY_ENDPOINT = '/api/snapchat-ads/events'\n\nfunction currentUrl(): string {\n return typeof window !== 'undefined' ? window.location.href : ''\n}\n\n/**\n * Send a server-side Snap CAPI event via the storefront's proxy. Fire-\n * and-forget from the caller's perspective; internal errors are swallowed\n * because analytics loss is preferable to storefront breakage.\n *\n * Intentionally NOT gated on analytics consent - Snap CAPI operates on a\n * different legal basis (order-tracking legitimate interest) than the\n * browser pixel. The `SnapPixelProvider` handles browser-side consent\n * gating separately.\n */\nexport async function sendSnapchatCapiEvent(payload: SnapEventPayload): Promise<void> {\n try {\n await fetch(CAPI_PROXY_ENDPOINT, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch {\n // non-fatal\n }\n}\n\n/**\n * Fire PAGE_VIEW on both sides. The `SnapPixelProvider` already fires\n * this on mount + route change; this hook is exposed for callers that\n * want to trigger a page-view manually (e.g. modal opens counted as\n * page views).\n */\nexport function trackPageView(\n primaryKey: string,\n userData: SnapUserData = {},\n eventId?: string,\n): void {\n const eid = eventId ?? makeEventId('PAGE_VIEW', primaryKey, minuteBucketNow())\n safeSnaptr('track', 'PAGE_VIEW', { client_dedup_id: eid })\n void sendSnapchatCapiEvent({\n eventName: 'PAGE_VIEW',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData,\n })\n}\n\nexport interface TrackViewContentOptions {\n currency?: string\n /** Major-unit value STRING (e.g. `\"10.50\"`). */\n value?: string\n /** Product / variant id passed to Snap as `content_ids`. */\n contentIds?: string[]\n /** Optional per-line breakdown. `item_price` + `quantity` are STRINGS. */\n contents?: Array<{ id: string; quantity: string; item_price?: string }>\n numItems?: number\n userData?: SnapUserData\n eventId?: string\n}\n\nexport function trackViewContent(productId: string, options: TrackViewContentOptions = {}): void {\n const eid = options.eventId ?? makeEventId('VIEW_CONTENT', productId, minuteBucketNow())\n const contentIds = options.contentIds ?? [productId]\n const customData: SnapCustomData = {\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(contentIds.length ? { contentIds } : {}),\n ...(options.contents?.length ? { contents: options.contents } : {}),\n ...(typeof options.numItems === 'number' ? { numItems: options.numItems } : {}),\n }\n safeSnaptr('track', 'VIEW_CONTENT', {\n ...(contentIds.length ? { item_ids: contentIds } : {}),\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n client_dedup_id: eid,\n })\n void sendSnapchatCapiEvent({\n eventName: 'VIEW_CONTENT',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData: options.userData ?? {},\n customData,\n })\n}\n\nexport interface TrackAddCartOptions {\n currency?: string\n /** Major-unit value STRING. */\n value?: string\n /** Product / variant ids. */\n contentIds?: string[]\n contents?: Array<{ id: string; quantity: string; item_price?: string }>\n /** Top-level `num_items` - number-typed per Snap docs. */\n numItems?: number\n userData?: SnapUserData\n eventId?: string\n}\n\nexport function trackAddCart(cartId: string, options: TrackAddCartOptions = {}): void {\n const eid = options.eventId ?? makeEventId('ADD_CART', cartId, minuteBucketNow())\n const customData: SnapCustomData = {\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(options.contentIds?.length ? { contentIds: options.contentIds } : {}),\n ...(options.contents?.length ? { contents: options.contents } : {}),\n ...(typeof options.numItems === 'number' ? { numItems: options.numItems } : {}),\n }\n safeSnaptr('track', 'ADD_CART', {\n ...(options.contentIds?.length ? { item_ids: options.contentIds } : {}),\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(typeof options.numItems === 'number' ? { number_items: options.numItems } : {}),\n client_dedup_id: eid,\n })\n void sendSnapchatCapiEvent({\n eventName: 'ADD_CART',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData: options.userData ?? {},\n customData,\n })\n}\n\nexport interface TrackStartCheckoutOptions {\n currency?: string\n value?: string\n contentIds?: string[]\n contents?: Array<{ id: string; quantity: string; item_price?: string }>\n numItems?: number\n userData?: SnapUserData\n eventId?: string\n}\n\nexport function trackStartCheckout(cartId: string, options: TrackStartCheckoutOptions = {}): void {\n const eid = options.eventId ?? makeEventId('START_CHECKOUT', cartId, minuteBucketNow())\n const customData: SnapCustomData = {\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(options.contentIds?.length ? { contentIds: options.contentIds } : {}),\n ...(options.contents?.length ? { contents: options.contents } : {}),\n ...(typeof options.numItems === 'number' ? { numItems: options.numItems } : {}),\n }\n safeSnaptr('track', 'START_CHECKOUT', {\n ...(options.contentIds?.length ? { item_ids: options.contentIds } : {}),\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(typeof options.numItems === 'number' ? { number_items: options.numItems } : {}),\n client_dedup_id: eid,\n })\n void sendSnapchatCapiEvent({\n eventName: 'START_CHECKOUT',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData: options.userData ?? {},\n customData,\n })\n}\n\nexport interface TrackPurchaseOptions {\n currency?: string\n value?: string\n contentIds?: string[]\n contents?: Array<{ id: string; quantity: string; item_price?: string }>\n numItems?: number\n userData?: SnapUserData\n /**\n * When present, pins `event_id` / `client_dedup_id` explicitly; otherwise\n * a stable id is derived from `(PURCHASE, orderId, 0)` - minute-bucket 0\n * so retries within a longer window still dedup against the server event.\n */\n eventId?: string\n}\n\n/**\n * PURCHASE gets the dual-dedup treatment:\n * - Top-level `event_id` / Pixel `client_dedup_id` shared for 48h window.\n * - `custom_data.order_id` / Pixel `transaction_id` shared for 30-day\n * window. Reuses the Amboras order id as both.\n */\nexport function trackPurchase(orderId: string, options: TrackPurchaseOptions = {}): void {\n // Pin minute-bucket to 0 for PURCHASE so a browser retry in a later\n // minute still lands on the same event_id as the initial server fire.\n const eid = options.eventId ?? makeEventId('PURCHASE', orderId, 0)\n const derivedOrderId = deriveOrderIdForPurchase('PURCHASE', orderId)\n const customData: SnapCustomData = {\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(options.contentIds?.length ? { contentIds: options.contentIds } : {}),\n ...(options.contents?.length ? { contents: options.contents } : {}),\n ...(typeof options.numItems === 'number' ? { numItems: options.numItems } : {}),\n ...(derivedOrderId ? { orderId: derivedOrderId } : {}),\n }\n safeSnaptr('track', 'PURCHASE', {\n ...(options.contentIds?.length ? { item_ids: options.contentIds } : {}),\n ...(options.currency ? { currency: options.currency } : {}),\n ...(options.value !== undefined ? { value: options.value } : {}),\n ...(typeof options.numItems === 'number' ? { number_items: options.numItems } : {}),\n ...(derivedOrderId ? { transaction_id: derivedOrderId } : {}),\n client_dedup_id: eid,\n })\n void sendSnapchatCapiEvent({\n eventName: 'PURCHASE',\n eventId: eid,\n eventSourceUrl: currentUrl(),\n userData: options.userData ?? {},\n customData,\n })\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
interface SnapPixelProviderProps {
|
|
2
|
+
/** Snap Pixel ID (`snap_pixel_id`). When absent, this component is a no-op. */
|
|
3
|
+
pixelId?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Null-rendering side-effect component for the `rootProviders` plugin
|
|
7
|
+
* slot. Responsibilities:
|
|
8
|
+
*
|
|
9
|
+
* 1. Consent-gated install of the `snaptr` shim + injection of
|
|
10
|
+
* `https://sc-static.net/scevent.min.js`. Snap does NOT ship an
|
|
11
|
+
* npm-installable browser SDK (research memo Section 10) so we inline
|
|
12
|
+
* the canonical IIFE via `installSnapPixelShim`.
|
|
13
|
+
* 2. Consent-gated `snaptr('init', pixelId)` - runs at most once per
|
|
14
|
+
* pixel id even across SPA nav (guarded by `initedForPixelId`).
|
|
15
|
+
* 3. SPA PAGE_VIEW on every route change. `scevent.min.js` only fires
|
|
16
|
+
* PAGE_VIEW automatically on full page loads; SPA navs need an
|
|
17
|
+
* explicit `snaptr('track', 'PAGE_VIEW')`. Fires with an explicit
|
|
18
|
+
* `client_dedup_id` derived from the pathname so a browser retry in
|
|
19
|
+
* the same minute doesn't get counted twice.
|
|
20
|
+
*
|
|
21
|
+
* Everything is gated on `hasAnalyticsConsent()` and re-checked on the
|
|
22
|
+
* `amboras-consent-changed` event.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately does NOT relay PAGE_VIEW to CAPI - the storefront has no
|
|
25
|
+
* server counterpart for a page view (no route handler dispatches it,
|
|
26
|
+
* unlike PURCHASE / ADD_CART which the checkout / cart flow triggers).
|
|
27
|
+
* Merchants who want server-side PAGE_VIEW attribution can call
|
|
28
|
+
* `trackPageView` from their app; the default here is browser-only for
|
|
29
|
+
* PAGE_VIEW parity with Snap's canonical install snippet.
|
|
30
|
+
*/
|
|
31
|
+
declare function SnapPixelProvider({ pixelId }: SnapPixelProviderProps): null;
|
|
32
|
+
|
|
33
|
+
interface SnapViewContentProps {
|
|
34
|
+
productId?: string;
|
|
35
|
+
productName?: string;
|
|
36
|
+
sku?: string;
|
|
37
|
+
/** Major-unit numeric price (already converted from Medusa's minor units by the slot host). */
|
|
38
|
+
price?: number;
|
|
39
|
+
currency?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Null-rendering component for the `pdpAnalytics` plugin slot.
|
|
43
|
+
* Fires Snap `VIEW_CONTENT` on both browser (Pixel) and server (CAPI)
|
|
44
|
+
* on PDP mount.
|
|
45
|
+
*
|
|
46
|
+
* Guarded by a productId ref so PDPs that swap variants without a full
|
|
47
|
+
* remount don't fire once per prop tick - Snap counts double fires as
|
|
48
|
+
* two events even when `client_dedup_id` matches (dedup is only across
|
|
49
|
+
* browser vs server, not within the same channel).
|
|
50
|
+
*/
|
|
51
|
+
declare function SnapViewContent({ productId, price, currency }: SnapViewContentProps): null;
|
|
52
|
+
|
|
53
|
+
interface SnapAddCartProps {
|
|
54
|
+
cartId?: string;
|
|
55
|
+
itemCount?: number;
|
|
56
|
+
/** Kept for prop-shape compatibility with the plugin slot - not used directly. */
|
|
57
|
+
total?: number;
|
|
58
|
+
currency?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Null-rendering component for the `cartUpdate` plugin slot.
|
|
62
|
+
* Fires Snap `ADD_CART` per newly-added variant, self-hydrating from
|
|
63
|
+
* Medusa's `/store/carts/:id` so `value`/`currency` are always
|
|
64
|
+
* authoritative. Per-variant sessionStorage baseline persists across
|
|
65
|
+
* cart-drawer mount cycles; a 1.5s window dedup absorbs TanStack Query
|
|
66
|
+
* refetch races.
|
|
67
|
+
*
|
|
68
|
+
* First-mount seed: on a fresh session with a hydrated cart (page
|
|
69
|
+
* refresh loads a cart with N items already in it), the baseline is
|
|
70
|
+
* seeded to the current cart state and NO events fire. Only subsequent
|
|
71
|
+
* quantity increases fire events.
|
|
72
|
+
*
|
|
73
|
+
* Values are serialised to STRINGS per Snap v3 wire spec (research memo
|
|
74
|
+
* caveat #3).
|
|
75
|
+
*/
|
|
76
|
+
declare function SnapAddCart({ cartId, itemCount: _itemCount }: SnapAddCartProps): null;
|
|
77
|
+
|
|
78
|
+
interface SnapStartCheckoutProps {
|
|
79
|
+
cartId?: string;
|
|
80
|
+
itemCount?: number;
|
|
81
|
+
total?: number;
|
|
82
|
+
currency?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Null-rendering component for the `checkoutStart` plugin slot.
|
|
86
|
+
* Fires Snap `START_CHECKOUT` exactly once per cartId. SessionStorage
|
|
87
|
+
* dedup so back-nav to the checkout page doesn't double-fire.
|
|
88
|
+
*
|
|
89
|
+
* Self-fetches the cart from Medusa so `value`/`currency` are correct
|
|
90
|
+
* even when the merchant slot context is thin. The ref is set only
|
|
91
|
+
* AFTER a successful dispatch so a StrictMode cleanup between effect-
|
|
92
|
+
* run and fetch-complete does NOT poison the ref.
|
|
93
|
+
*/
|
|
94
|
+
declare function SnapStartCheckout({ cartId, itemCount, total, currency }: SnapStartCheckoutProps): null;
|
|
95
|
+
|
|
96
|
+
interface SnapPurchaseProps {
|
|
97
|
+
orderId?: string;
|
|
98
|
+
total?: number;
|
|
99
|
+
currency?: string;
|
|
100
|
+
itemCount?: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Null-rendering component for the `checkoutComplete` plugin slot.
|
|
104
|
+
* Fires Snap `PURCHASE` exactly once per orderId. Dual-dedup semantics
|
|
105
|
+
* per research memo Section 5:
|
|
106
|
+
*
|
|
107
|
+
* - Top-level `event_id` / Pixel `client_dedup_id` shared for 48h.
|
|
108
|
+
* - `custom_data.order_id` / Pixel `transaction_id` shared for 30d.
|
|
109
|
+
*
|
|
110
|
+
* Both derive from the orderId directly so browser retries in a later
|
|
111
|
+
* minute still line up with the server event.
|
|
112
|
+
*
|
|
113
|
+
* Primary path: self-fetch from `/store/orders/:id` and derive value +
|
|
114
|
+
* currency + contentIds from the authoritative Medusa response
|
|
115
|
+
* (currency-aware `toMajorUnits`, so JPY/KWD are correct).
|
|
116
|
+
*
|
|
117
|
+
* Fallback path (fetch failed): fire a MINIMAL browser PURCHASE using
|
|
118
|
+
* the incoming `total` + `currency` props, log a dev warning, and rely
|
|
119
|
+
* on Snap's dedup with the server-side `sendSnapchatCapiEvent` to
|
|
120
|
+
* reconcile.
|
|
121
|
+
*
|
|
122
|
+
* Two-layer dedup mirrors meta-ads: `useRef` guards StrictMode double-
|
|
123
|
+
* mount within the same instance; `sessionStorage` guards page refresh
|
|
124
|
+
* + remount. Both are set only AFTER successful dispatch.
|
|
125
|
+
*/
|
|
126
|
+
declare function SnapPurchase({ orderId, total, currency, itemCount: _itemCount }: SnapPurchaseProps): null;
|
|
127
|
+
|
|
128
|
+
export { SnapAddCart, type SnapAddCartProps, SnapPixelProvider, type SnapPixelProviderProps, SnapPurchase, type SnapPurchaseProps, SnapStartCheckout, type SnapStartCheckoutProps, SnapViewContent, type SnapViewContentProps };
|