@tracelog/lib 2.8.5-rc.106.8 → 2.8.5-rc.106.9

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 +1 @@
1
- var TraceLogShopifyPixel=function(t){"use strict";function e(t,e){if(!Array.isArray(t))return null;for(const n of t)if(n.key===e&&"string"==typeof n.value&&n.value.length>0)return n.value;return null}function n(t){return"number"==typeof t&&Number.isFinite(t)?t:void 0}function i(t){return"string"==typeof t&&t.length>0?t:void 0}function o(t,e,n){void 0!==n&&(t[e]=n)}function c(t,e){const c=t.data?.checkout,a=t.data?.cart,f={};o(f,"shopify_client_id",i(t.clientId));const _=i(c?.token);if(void 0!==_&&(f.checkout_token=_),"checkout_completed"===e){o(f,"value",n(c?.totalPrice?.amount??c?.subtotalPrice?.amount)),o(f,"currency",i(c?.currencyCode??c?.totalPrice?.currencyCode));const t=c?.order?.id;"string"!=typeof t&&"number"!=typeof t||(f.orderId=String(t));const e=c?.lineItems??[],r=function(t){if(!t||0===t.length)return[];const e=t.slice(0,100);return e.map(t=>{const e={},c=t.variant?.id;return"string"!=typeof c&&"number"!=typeof c||(e.id=String(c)),o(e,"title",d(l(i(t.title??t.variant?.product?.title)),u)),o(e,"quantity",n(t.quantity)),o(e,"price",n(t.variant?.price?.amount??t.finalLinePrice?.amount)),o(e,"sku",d(l(i(t.variant?.sku??void 0)),s)),o(e,"vendor",d(l(i(t.variant?.product?.vendor)),u)),e})}(e);r.length>0&&(f.items=r,e.length>100&&(f.items_truncated=!0))}else if("cart_viewed"===e){o(f,"cart_total",n(a?.cost?.totalAmount?.amount??a?.totalAmount?.amount));o(f,"item_count",r(a?.lines)??r(a?.lineItems))}else if("checkout_started"===e){o(f,"cart_total",n(c?.totalPrice?.amount??c?.subtotalPrice?.amount)),o(f,"currency",i(c?.currencyCode??c?.totalPrice?.currencyCode));o(f,"item_count",r(c?.lineItems)??r(t.data?.checkoutLineItems))}else{o(f,"currency",i(c?.currencyCode??c?.totalPrice?.currencyCode));const t=c?.totalPrice?.amount;o(f,"cart_total",n(t))}return f}function r(t){if(!t||0===t.length)return;let e=0,i=!1;for(const o of t){const t=n(o.quantity);void 0!==t&&(i=!0,e+=t)}return i?e:t.length}const u=255,s=128,a=[/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,/javascript:/gi,/on\w+\s*=/gi,/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,/<embed\b[^>]*>/gi,/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi];function l(t){if(void 0===t)return;let e=t;for(const n of a)e=e.replace(n,"");return e}function d(t,e){if(void 0!==t)return t.length>e?t.slice(0,e):t}function f(t){const e=i(t.context?.window?.location?.href)??i(t.context?.document?.location?.href);return void 0!==e?function(t){const e=t.indexOf("?"),n=t.indexOf("#");let i=t.length;return-1!==e&&(i=e),-1!==n&&n<i&&(i=n),t.slice(0,i)}(e):"unknown"}function _(t,n){if(!t)return null;const o=t.data?.checkout?.attributes,r=t.data?.cart?.attributes,u=e(o,"tracelog_session_id")??e(r,"tracelog_session_id"),s=e(o,"tracelog_user_id")??e(r,"tracelog_user_id");if(null===u||null===s)return null;const a=function(t){if("string"!=typeof t||0===t.length)return Date.now();const e=Date.parse(t);return Number.isFinite(e)?e:Date.now()}(t.timestamp),l=function(t,e){return`${e}-001-${i(t.id)?.slice(-6)??Math.random().toString(36).slice(2,8)}`}(t,a),d=c(t,n);return{user_id:s,session_id:u,device:{type:"unknown",os:"unknown",browser:"unknown"},events:[{id:l,type:"custom",page_url:f(t),timestamp:a,custom_event:{name:`shopify_${n}`,metadata:d}}],_metadata:{client_version:"shopify-web-pixel-1",timestamp:a}}}function m(t,e){if(!t.projectId)return;const n=`https://ingest.tracelog.io/p/${encodeURIComponent(t.projectId)}/collect`;try{fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},keepalive:!0,body:JSON.stringify(e)}).catch(()=>{})}catch{}}return t.SHOPIFY_EVENTS=["cart_viewed","checkout_started","checkout_contact_info_submitted","checkout_address_info_submitted","checkout_shipping_info_submitted","payment_info_submitted","checkout_completed"],t.mapEventToBody=_,t.registerShopifyPixel=function(t,e){const n=(t,n)=>{const i=_(n,t);i&&m(e,i)};t.analytics.subscribe("cart_viewed",t=>{n("cart_viewed",t)}),t.analytics.subscribe("checkout_started",t=>{n("checkout_started",t)}),t.analytics.subscribe("checkout_contact_info_submitted",t=>{n("checkout_contact_info_submitted",t)}),t.analytics.subscribe("checkout_address_info_submitted",t=>{n("checkout_address_info_submitted",t)}),t.analytics.subscribe("checkout_shipping_info_submitted",t=>{n("checkout_shipping_info_submitted",t)}),t.analytics.subscribe("payment_info_submitted",t=>{n("payment_info_submitted",t)}),t.analytics.subscribe("checkout_completed",t=>{n("checkout_completed",t)})},t.sendBatch=m,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t}({});
1
+ var TraceLogShopifyPixel=function(t){"use strict";const e=["cart_viewed","checkout_started","checkout_contact_info_submitted","checkout_address_info_submitted","checkout_shipping_info_submitted","payment_info_submitted","checkout_completed"],n=e.map(t=>`shopify_${t}`);function i(t,e){if(!Array.isArray(t))return null;for(const n of t)if(n.key===e&&"string"==typeof n.value&&n.value.length>0)return n.value;return null}function o(t){return"number"==typeof t&&Number.isFinite(t)?t:void 0}function c(t){return"string"==typeof t&&t.length>0?t:void 0}function r(t,e,n){void 0!==n&&(t[e]=n)}function u(t,e){const n=t.data?.checkout,i=t.data?.cart,u={};r(u,"shopify_client_id",c(t.clientId));const d=c(n?.token);if(void 0!==d&&(u.checkout_token=d),"checkout_completed"===e){r(u,"value",o(n?.totalPrice?.amount??n?.subtotalPrice?.amount)),r(u,"currency",c(n?.currencyCode??n?.totalPrice?.currencyCode));const t=n?.order?.id;"string"!=typeof t&&"number"!=typeof t||(u.orderId=String(t));const e=n?.lineItems??[],i=function(t){if(!t||0===t.length)return[];const e=t.slice(0,100);return e.map(t=>{const e={},n=t.variant?.id;return"string"!=typeof n&&"number"!=typeof n||(e.id=String(n)),r(e,"title",f(_(c(t.title??t.variant?.product?.title)),a)),r(e,"quantity",o(t.quantity)),r(e,"price",o(t.variant?.price?.amount??t.finalLinePrice?.amount)),r(e,"sku",f(_(c(t.variant?.sku??void 0)),l)),r(e,"vendor",f(_(c(t.variant?.product?.vendor)),a)),e})}(e);i.length>0&&(u.items=i,e.length>100&&(u.items_truncated=!0))}else if("cart_viewed"===e){r(u,"cart_total",o(i?.cost?.totalAmount?.amount??i?.totalAmount?.amount));r(u,"item_count",s(i?.lines)??s(i?.lineItems))}else if("checkout_started"===e){r(u,"cart_total",o(n?.totalPrice?.amount??n?.subtotalPrice?.amount)),r(u,"currency",c(n?.currencyCode??n?.totalPrice?.currencyCode));r(u,"item_count",s(n?.lineItems)??s(t.data?.checkoutLineItems))}else{r(u,"currency",c(n?.currencyCode??n?.totalPrice?.currencyCode));const t=n?.totalPrice?.amount;r(u,"cart_total",o(t))}return u}function s(t){if(!t||0===t.length)return;let e=0,n=!1;for(const i of t){const t=o(i.quantity);void 0!==t&&(n=!0,e+=t)}return n?e:t.length}const a=255,l=128,d=[/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,/javascript:/gi,/on\w+\s*=/gi,/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,/<embed\b[^>]*>/gi,/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi];function _(t){if(void 0===t)return;let e=t;for(const n of d)e=e.replace(n,"");return e}function f(t,e){if(void 0!==t)return t.length>e?t.slice(0,e):t}function m(t){const e=c(t.context?.window?.location?.href)??c(t.context?.document?.location?.href);return void 0!==e?function(t){const e=t.indexOf("?"),n=t.indexOf("#");let i=t.length;return-1!==e&&(i=e),-1!==n&&n<i&&(i=n),t.slice(0,i)}(e):"unknown"}function p(t,e){if(!t)return null;const n=t.data?.checkout?.attributes,o=t.data?.cart?.attributes,r=i(n,"tracelog_session_id")??i(o,"tracelog_session_id"),s=i(n,"tracelog_user_id")??i(o,"tracelog_user_id");if(null===r||null===s)return null;const a=function(t){if("string"!=typeof t||0===t.length)return Date.now();const e=Date.parse(t);return Number.isFinite(e)?e:Date.now()}(t.timestamp),l=function(t,e){return`${e}-001-${c(t.id)?.slice(-6)??Math.random().toString(36).slice(2,8)}`}(t,a),d=u(t,e);return{user_id:s,session_id:r,device:{type:"unknown",os:"unknown",browser:"unknown"},events:[{id:l,type:"custom",page_url:m(t),timestamp:a,custom_event:{name:`shopify_${e}`,metadata:d}}],_metadata:{client_version:"shopify-web-pixel-1",timestamp:a}}}function h(t,e){if(!t.projectId)return;const n=`https://ingest.tracelog.io/p/${encodeURIComponent(t.projectId)}/collect`;try{fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},keepalive:!0,body:JSON.stringify(e)}).catch(()=>{})}catch{}}return t.SHOPIFY_EVENTS=e,t.SHOPIFY_PIXEL_EVENT_NAMES=n,t.mapEventToBody=p,t.registerShopifyPixel=function(t,e){const n=(t,n)=>{const i=p(n,t);i&&h(e,i)};t.analytics.subscribe("cart_viewed",t=>{n("cart_viewed",t)}),t.analytics.subscribe("checkout_started",t=>{n("checkout_started",t)}),t.analytics.subscribe("checkout_contact_info_submitted",t=>{n("checkout_contact_info_submitted",t)}),t.analytics.subscribe("checkout_address_info_submitted",t=>{n("checkout_address_info_submitted",t)}),t.analytics.subscribe("checkout_shipping_info_submitted",t=>{n("checkout_shipping_info_submitted",t)}),t.analytics.subscribe("payment_info_submitted",t=>{n("payment_info_submitted",t)}),t.analytics.subscribe("checkout_completed",t=>{n("checkout_completed",t)})},t.sendBatch=h,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t}({});
@@ -1 +1 @@
1
- {"version":3,"file":"tracelog-shopify-pixel.iife.js","sources":["../../src/pixel/event-mapper.ts","../../src/pixel/pixel-sender.ts","../../src/pixel/shopify-pixel.ts"],"sourcesContent":["import type { PixelEventBody } from './pixel-sender';\n\nconst PIXEL_CLIENT_VERSION = 'shopify-web-pixel-1';\n\nconst MAX_LINE_ITEMS = 100;\n\nexport const SHOPIFY_EVENTS = [\n 'cart_viewed',\n 'checkout_started',\n 'checkout_contact_info_submitted',\n 'checkout_address_info_submitted',\n 'checkout_shipping_info_submitted',\n 'payment_info_submitted',\n 'checkout_completed',\n] as const;\n\nexport type ShopifyEventName = (typeof SHOPIFY_EVENTS)[number];\n\n/**\n * Spike-confirmed shape ([04-spike-report.md](../../docs/tasks/shopify-hybrid-capture/04-spike-report.md)):\n * `event.data.{cart,checkout}.attributes` is `Array<{key, value, __typename?}>`,\n * NOT a plain object. `__typename: 'NoteAttribute'` is added on `checkout_completed`\n * only — harmless because we lookup by `key`.\n */\ninterface ShopifyAttribute {\n key?: string;\n value?: string;\n __typename?: string;\n}\n\ninterface ShopifyMoney {\n amount?: number;\n currencyCode?: string;\n}\n\ninterface ShopifyLineItemVariantProduct {\n id?: string | number;\n title?: string;\n vendor?: string;\n}\n\ninterface ShopifyLineItemVariant {\n id?: string | number;\n sku?: string | null;\n price?: ShopifyMoney;\n product?: ShopifyLineItemVariantProduct;\n}\n\ninterface ShopifyLineItem {\n id?: string | number;\n title?: string;\n quantity?: number;\n variant?: ShopifyLineItemVariant;\n finalLinePrice?: ShopifyMoney;\n}\n\ninterface ShopifyCheckout {\n token?: string;\n currencyCode?: string;\n totalPrice?: ShopifyMoney;\n subtotalPrice?: ShopifyMoney;\n attributes?: ShopifyAttribute[];\n lineItems?: ShopifyLineItem[];\n order?: { id?: string | number };\n}\n\ninterface ShopifyCart {\n attributes?: ShopifyAttribute[];\n totalAmount?: ShopifyMoney;\n cost?: { totalAmount?: ShopifyMoney };\n lines?: ShopifyLineItem[];\n lineItems?: ShopifyLineItem[];\n}\n\ninterface ShopifyCheckoutLineItem {\n quantity?: number;\n}\n\ninterface ShopifyEvent {\n id?: string;\n timestamp?: string;\n clientId?: string;\n data?: {\n checkout?: ShopifyCheckout;\n cart?: ShopifyCart;\n checkoutLineItems?: ShopifyCheckoutLineItem[];\n };\n context?: {\n window?: { location?: { href?: string } };\n document?: { location?: { href?: string } };\n };\n}\n\nfunction attrLookup(attrs: ShopifyAttribute[] | undefined, key: string): string | null {\n if (!Array.isArray(attrs)) return null;\n for (const a of attrs) {\n if (a.key === key && typeof a.value === 'string' && a.value.length > 0) return a.value;\n }\n return null;\n}\n\nfunction safeNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction safeString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction setIfDefined<T>(target: Record<string, unknown>, key: string, value: T | undefined): void {\n if (value !== undefined) target[key] = value;\n}\n\nfunction buildMetadata(event: ShopifyEvent, name: ShopifyEventName): Record<string, unknown> {\n const checkout = event.data?.checkout;\n const cart = event.data?.cart;\n const meta: Record<string, unknown> = {};\n\n setIfDefined(meta, 'shopify_client_id', safeString(event.clientId));\n\n const checkoutToken = safeString(checkout?.token);\n if (checkoutToken !== undefined) meta['checkout_token'] = checkoutToken;\n\n if (name === 'checkout_completed') {\n const total = checkout?.totalPrice?.amount ?? checkout?.subtotalPrice?.amount;\n setIfDefined(meta, 'value', safeNumber(total));\n setIfDefined(meta, 'currency', safeString(checkout?.currencyCode ?? checkout?.totalPrice?.currencyCode));\n const orderId = checkout?.order?.id;\n if (typeof orderId === 'string' || typeof orderId === 'number') meta['orderId'] = String(orderId);\n const rawItems = checkout?.lineItems ?? [];\n const items = mapLineItems(rawItems);\n if (items.length > 0) {\n meta['items'] = items;\n if (rawItems.length > MAX_LINE_ITEMS) meta['items_truncated'] = true;\n }\n } else if (name === 'cart_viewed') {\n const cartTotal = cart?.cost?.totalAmount?.amount ?? cart?.totalAmount?.amount;\n setIfDefined(meta, 'cart_total', safeNumber(cartTotal));\n const itemCount = countLineItems(cart?.lines) ?? countLineItems(cart?.lineItems);\n setIfDefined(meta, 'item_count', itemCount);\n } else if (name === 'checkout_started') {\n const total = checkout?.totalPrice?.amount ?? checkout?.subtotalPrice?.amount;\n setIfDefined(meta, 'cart_total', safeNumber(total));\n setIfDefined(meta, 'currency', safeString(checkout?.currencyCode ?? checkout?.totalPrice?.currencyCode));\n const itemCount = countLineItems(checkout?.lineItems) ?? countLineItems(event.data?.checkoutLineItems);\n setIfDefined(meta, 'item_count', itemCount);\n } else {\n setIfDefined(meta, 'currency', safeString(checkout?.currencyCode ?? checkout?.totalPrice?.currencyCode));\n const total = checkout?.totalPrice?.amount;\n setIfDefined(meta, 'cart_total', safeNumber(total));\n }\n\n return meta;\n}\n\nfunction countLineItems(items: { quantity?: number }[] | undefined): number | undefined {\n if (!items || items.length === 0) return undefined;\n let count = 0;\n let hasNumericQuantity = false;\n for (const item of items) {\n const q = safeNumber(item.quantity);\n if (q !== undefined) {\n hasNumericQuantity = true;\n count += q;\n }\n }\n // Fallback to row count only when no line had a numeric quantity. An explicit\n // sum of zero (e.g. `[{ quantity: 0 }]`) must be preserved, not replaced with\n // `items.length`, otherwise empty carts over-report `item_count`.\n return hasNumericQuantity ? count : items.length;\n}\n\n// Caps merchant-controlled free-text to keep payloads under the API's per-string DTO limit.\n// IDs, tokens, and currency codes are naturally bounded by Shopify and skip capping.\nconst MAX_TEXT_LEN = 255;\nconst MAX_SKU_LEN = 128;\n\n// Mirrors `src/constants/config.constants.ts` `XSS_PATTERNS`. Inlined because\n// the constants module pulls in unrelated dependencies that would blow the\n// pixel bundle's 5KB budget. Defense-in-depth: Shopify product titles/SKUs/\n// vendors are merchant-controlled; a compromised admin could inject HTML/JS\n// that lands in dashboards or logs.\nconst XSS_PATTERNS: readonly RegExp[] = [\n /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n /javascript:/gi,\n /on\\w+\\s*=/gi,\n /<iframe\\b[^<]*(?:(?!<\\/iframe>)<[^<]*)*<\\/iframe>/gi,\n /<embed\\b[^>]*>/gi,\n /<object\\b[^<]*(?:(?!<\\/object>)<[^<]*)*<\\/object>/gi,\n];\n\nfunction sanitize(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n let out = value;\n for (const pattern of XSS_PATTERNS) out = out.replace(pattern, '');\n return out;\n}\n\nfunction cap(value: string | undefined, max: number): string | undefined {\n if (value === undefined) return undefined;\n return value.length > max ? value.slice(0, max) : value;\n}\n\nfunction mapLineItems(items: ShopifyLineItem[] | undefined): Record<string, unknown>[] {\n if (!items || items.length === 0) return [];\n const capped = items.slice(0, MAX_LINE_ITEMS);\n return capped.map((item) => {\n const out: Record<string, unknown> = {};\n const variantId = item.variant?.id;\n if (typeof variantId === 'string' || typeof variantId === 'number') {\n out['id'] = String(variantId);\n }\n setIfDefined(out, 'title', cap(sanitize(safeString(item.title ?? item.variant?.product?.title)), MAX_TEXT_LEN));\n setIfDefined(out, 'quantity', safeNumber(item.quantity));\n setIfDefined(out, 'price', safeNumber(item.variant?.price?.amount ?? item.finalLinePrice?.amount));\n setIfDefined(out, 'sku', cap(sanitize(safeString(item.variant?.sku ?? undefined)), MAX_SKU_LEN));\n setIfDefined(out, 'vendor', cap(sanitize(safeString(item.variant?.product?.vendor)), MAX_TEXT_LEN));\n return out;\n });\n}\n\nfunction generateEventId(event: ShopifyEvent, ts: number): string {\n const suffix = safeString(event.id)?.slice(-6) ?? Math.random().toString(36).slice(2, 8);\n return `${ts}-001-${suffix}`;\n}\n\nfunction resolveTimestamp(value: string | undefined): number {\n if (typeof value !== 'string' || value.length === 0) return Date.now();\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? parsed : Date.now();\n}\n\nfunction stripUrlParams(href: string): string {\n // Pixel runs in the checkout sandbox where href can carry recovery tokens\n // (`?recovery=...`, `?key=...`). Path alone identifies the funnel stage; drop\n // query and hash to keep tokens out of telemetry. Mirrors the policy in\n // `src/utils/network/url.utils.ts` (which is too heavy to import into the\n // sub-5KB pixel bundle).\n const queryIdx = href.indexOf('?');\n const hashIdx = href.indexOf('#');\n let cutoff = href.length;\n if (queryIdx !== -1) cutoff = queryIdx;\n if (hashIdx !== -1 && hashIdx < cutoff) cutoff = hashIdx;\n return href.slice(0, cutoff);\n}\n\nfunction resolvePageUrl(event: ShopifyEvent): string {\n const href = safeString(event.context?.window?.location?.href) ?? safeString(event.context?.document?.location?.href);\n return href !== undefined ? stripUrlParams(href) : 'unknown';\n}\n\nexport function mapEventToBody(\n shopifyEvent: ShopifyEvent | null | undefined,\n shopifyEventName: ShopifyEventName,\n): PixelEventBody | null {\n if (!shopifyEvent) return null;\n\n const checkoutAttrs = shopifyEvent.data?.checkout?.attributes;\n const cartAttrs = shopifyEvent.data?.cart?.attributes;\n\n const sessionId = attrLookup(checkoutAttrs, 'tracelog_session_id') ?? attrLookup(cartAttrs, 'tracelog_session_id');\n const userId = attrLookup(checkoutAttrs, 'tracelog_user_id') ?? attrLookup(cartAttrs, 'tracelog_user_id');\n\n if (sessionId === null || userId === null) return null;\n\n const ts = resolveTimestamp(shopifyEvent.timestamp);\n const eventId = generateEventId(shopifyEvent, ts);\n const metadata = buildMetadata(shopifyEvent, shopifyEventName);\n\n return {\n user_id: userId,\n session_id: sessionId,\n device: { type: 'unknown', os: 'unknown', browser: 'unknown' },\n events: [\n {\n id: eventId,\n type: 'custom',\n page_url: resolvePageUrl(shopifyEvent),\n timestamp: ts,\n custom_event: {\n name: `shopify_${shopifyEventName}`,\n metadata,\n },\n },\n ],\n _metadata: {\n client_version: PIXEL_CLIENT_VERSION,\n timestamp: ts,\n },\n };\n}\n","/**\n * Standalone HTTP sender for the Shopify Web Pixel Extension bundle.\n *\n * Posts to the path-based ingress (`ingest.tracelog.io/p/<id>/collect`) — NOT\n * `api.tracelog.io/events/collect` — because the middleware has the only CORS\n * handler that accepts `Origin: null` from sandboxed iframes.\n *\n * Best-effort: failures are silently swallowed. The webhook (Task 03) carries\n * the revenue contract; pixel events are funnel-only and accept ~5-30% loss.\n */\n\nconst INGEST_HOST = 'https://ingest.tracelog.io';\n\nexport interface PixelSenderSettings {\n /** TraceLog project identifier (e.g. `t756edc0pnn17ha7`). Provided by Shopify init payload. */\n projectId: string;\n}\n\nexport interface PixelEventBody {\n user_id: string;\n session_id: string;\n device: { type: string; os: string; browser: string };\n events: Array<{\n id: string;\n type: 'custom';\n page_url: string;\n timestamp: number;\n custom_event: { name: string; metadata: Record<string, unknown> };\n }>;\n _metadata: { client_version: string; timestamp: number };\n}\n\nexport function sendBatch(settings: PixelSenderSettings, body: PixelEventBody): void {\n // Trust boundary is the Shopify extension settings form, but a misconfigured\n // (empty) projectId would yield `https://ingest.tracelog.io/p//collect` and\n // 404 every event. Drop silently so it can be diagnosed via Shopify pixel logs.\n if (!settings.projectId) return;\n\n // Encode in case the merchant pastes whitespace, slashes, or other unsafe\n // characters into the Shopify extension settings form.\n const url = `${INGEST_HOST}/p/${encodeURIComponent(settings.projectId)}/collect`;\n try {\n void fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n keepalive: true,\n body: JSON.stringify(body),\n }).catch(() => {});\n } catch {\n // Pixel runs inside Shopify's strict sandbox; fetch() may be unavailable\n // or throw synchronously. Funnel events are best-effort — drop silently.\n }\n}\n","import { mapEventToBody, type ShopifyEventName } from './event-mapper';\nimport { sendBatch, type PixelSenderSettings } from './pixel-sender';\n\ninterface ShopifyAnalyticsApi {\n subscribe: (event: ShopifyEventName, callback: (event: unknown) => void) => void;\n}\n\ninterface ShopifyPixelApi {\n analytics: ShopifyAnalyticsApi;\n}\n\n/**\n * Registers the TraceLog Shopify Web Pixel: subscribes to the 7 standard\n * Customer Events and forwards each one to `ingest.tracelog.io/p/<id>/collect`.\n *\n * Each subscription is declared explicitly because Shopify's static analyzer\n * inspects the bundle for `analytics.subscribe('event_name', ...)` calls;\n * loop-based subscriptions are flagged and the pixel is treated as inactive.\n */\nexport function registerShopifyPixel(api: ShopifyPixelApi, settings: PixelSenderSettings): void {\n const handle = (eventName: ShopifyEventName, payload: unknown): void => {\n const body = mapEventToBody(payload as Parameters<typeof mapEventToBody>[0], eventName);\n if (!body) return;\n sendBatch(settings, body);\n };\n\n api.analytics.subscribe('cart_viewed', (event) => {\n handle('cart_viewed', event);\n });\n api.analytics.subscribe('checkout_started', (event) => {\n handle('checkout_started', event);\n });\n api.analytics.subscribe('checkout_contact_info_submitted', (event) => {\n handle('checkout_contact_info_submitted', event);\n });\n api.analytics.subscribe('checkout_address_info_submitted', (event) => {\n handle('checkout_address_info_submitted', event);\n });\n api.analytics.subscribe('checkout_shipping_info_submitted', (event) => {\n handle('checkout_shipping_info_submitted', event);\n });\n api.analytics.subscribe('payment_info_submitted', (event) => {\n handle('payment_info_submitted', event);\n });\n api.analytics.subscribe('checkout_completed', (event) => {\n handle('checkout_completed', event);\n });\n}\n"],"names":["attrLookup","attrs","key","Array","isArray","a","value","length","safeNumber","Number","isFinite","safeString","setIfDefined","target","buildMetadata","event","name","checkout","data","cart","meta","clientId","checkoutToken","token","totalPrice","amount","subtotalPrice","currencyCode","orderId","order","id","String","rawItems","lineItems","items","capped","slice","map","item","out","variantId","variant","cap","sanitize","title","product","MAX_TEXT_LEN","quantity","price","finalLinePrice","sku","MAX_SKU_LEN","vendor","mapLineItems","cost","totalAmount","countLineItems","lines","checkoutLineItems","total","count","hasNumericQuantity","q","XSS_PATTERNS","pattern","replace","max","resolvePageUrl","href","context","window","location","document","queryIdx","indexOf","hashIdx","cutoff","stripUrlParams","mapEventToBody","shopifyEvent","shopifyEventName","checkoutAttrs","attributes","cartAttrs","sessionId","userId","ts","Date","now","parsed","parse","resolveTimestamp","timestamp","eventId","Math","random","toString","generateEventId","metadata","user_id","session_id","device","type","os","browser","events","page_url","custom_event","_metadata","client_version","sendBatch","settings","body","projectId","url","encodeURIComponent","fetch","method","headers","keepalive","JSON","stringify","catch","api","handle","eventName","payload","analytics","subscribe"],"mappings":"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"}
1
+ {"version":3,"file":"tracelog-shopify-pixel.iife.js","sources":["../../src/pixel/event-mapper.ts","../../src/pixel/pixel-sender.ts","../../src/pixel/shopify-pixel.ts"],"sourcesContent":["import type { PixelEventBody } from './pixel-sender';\n\nconst PIXEL_CLIENT_VERSION = 'shopify-web-pixel-1';\n\nconst MAX_LINE_ITEMS = 100;\n\nexport const SHOPIFY_EVENTS = [\n 'cart_viewed',\n 'checkout_started',\n 'checkout_contact_info_submitted',\n 'checkout_address_info_submitted',\n 'checkout_shipping_info_submitted',\n 'payment_info_submitted',\n 'checkout_completed',\n] as const;\n\nexport type ShopifyEventName = (typeof SHOPIFY_EVENTS)[number];\n\n/**\n * Final event names emitted by `mapEventToBody()`. Exported so consumers\n * (tracelog-api event-catalog discovery, dashboards, contract validators)\n * can recognise pixel events without re-deriving the `shopify_` prefix.\n */\nexport const SHOPIFY_PIXEL_EVENT_NAMES = SHOPIFY_EVENTS.map((name) => `shopify_${name}` as const);\n\nexport type ShopifyPixelEventName = (typeof SHOPIFY_PIXEL_EVENT_NAMES)[number];\n\n/**\n * Spike-confirmed shape ([04-spike-report.md](../../docs/tasks/shopify-hybrid-capture/04-spike-report.md)):\n * `event.data.{cart,checkout}.attributes` is `Array<{key, value, __typename?}>`,\n * NOT a plain object. `__typename: 'NoteAttribute'` is added on `checkout_completed`\n * only — harmless because we lookup by `key`.\n */\ninterface ShopifyAttribute {\n key?: string;\n value?: string;\n __typename?: string;\n}\n\ninterface ShopifyMoney {\n amount?: number;\n currencyCode?: string;\n}\n\ninterface ShopifyLineItemVariantProduct {\n id?: string | number;\n title?: string;\n vendor?: string;\n}\n\ninterface ShopifyLineItemVariant {\n id?: string | number;\n sku?: string | null;\n price?: ShopifyMoney;\n product?: ShopifyLineItemVariantProduct;\n}\n\ninterface ShopifyLineItem {\n id?: string | number;\n title?: string;\n quantity?: number;\n variant?: ShopifyLineItemVariant;\n finalLinePrice?: ShopifyMoney;\n}\n\ninterface ShopifyCheckout {\n token?: string;\n currencyCode?: string;\n totalPrice?: ShopifyMoney;\n subtotalPrice?: ShopifyMoney;\n attributes?: ShopifyAttribute[];\n lineItems?: ShopifyLineItem[];\n order?: { id?: string | number };\n}\n\ninterface ShopifyCart {\n attributes?: ShopifyAttribute[];\n totalAmount?: ShopifyMoney;\n cost?: { totalAmount?: ShopifyMoney };\n lines?: ShopifyLineItem[];\n lineItems?: ShopifyLineItem[];\n}\n\ninterface ShopifyCheckoutLineItem {\n quantity?: number;\n}\n\ninterface ShopifyEvent {\n id?: string;\n timestamp?: string;\n clientId?: string;\n data?: {\n checkout?: ShopifyCheckout;\n cart?: ShopifyCart;\n checkoutLineItems?: ShopifyCheckoutLineItem[];\n };\n context?: {\n window?: { location?: { href?: string } };\n document?: { location?: { href?: string } };\n };\n}\n\nfunction attrLookup(attrs: ShopifyAttribute[] | undefined, key: string): string | null {\n if (!Array.isArray(attrs)) return null;\n for (const a of attrs) {\n if (a.key === key && typeof a.value === 'string' && a.value.length > 0) return a.value;\n }\n return null;\n}\n\nfunction safeNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction safeString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction setIfDefined<T>(target: Record<string, unknown>, key: string, value: T | undefined): void {\n if (value !== undefined) target[key] = value;\n}\n\nfunction buildMetadata(event: ShopifyEvent, name: ShopifyEventName): Record<string, unknown> {\n const checkout = event.data?.checkout;\n const cart = event.data?.cart;\n const meta: Record<string, unknown> = {};\n\n setIfDefined(meta, 'shopify_client_id', safeString(event.clientId));\n\n const checkoutToken = safeString(checkout?.token);\n if (checkoutToken !== undefined) meta['checkout_token'] = checkoutToken;\n\n if (name === 'checkout_completed') {\n const total = checkout?.totalPrice?.amount ?? checkout?.subtotalPrice?.amount;\n setIfDefined(meta, 'value', safeNumber(total));\n setIfDefined(meta, 'currency', safeString(checkout?.currencyCode ?? checkout?.totalPrice?.currencyCode));\n const orderId = checkout?.order?.id;\n if (typeof orderId === 'string' || typeof orderId === 'number') meta['orderId'] = String(orderId);\n const rawItems = checkout?.lineItems ?? [];\n const items = mapLineItems(rawItems);\n if (items.length > 0) {\n meta['items'] = items;\n if (rawItems.length > MAX_LINE_ITEMS) meta['items_truncated'] = true;\n }\n } else if (name === 'cart_viewed') {\n const cartTotal = cart?.cost?.totalAmount?.amount ?? cart?.totalAmount?.amount;\n setIfDefined(meta, 'cart_total', safeNumber(cartTotal));\n const itemCount = countLineItems(cart?.lines) ?? countLineItems(cart?.lineItems);\n setIfDefined(meta, 'item_count', itemCount);\n } else if (name === 'checkout_started') {\n const total = checkout?.totalPrice?.amount ?? checkout?.subtotalPrice?.amount;\n setIfDefined(meta, 'cart_total', safeNumber(total));\n setIfDefined(meta, 'currency', safeString(checkout?.currencyCode ?? checkout?.totalPrice?.currencyCode));\n const itemCount = countLineItems(checkout?.lineItems) ?? countLineItems(event.data?.checkoutLineItems);\n setIfDefined(meta, 'item_count', itemCount);\n } else {\n setIfDefined(meta, 'currency', safeString(checkout?.currencyCode ?? checkout?.totalPrice?.currencyCode));\n const total = checkout?.totalPrice?.amount;\n setIfDefined(meta, 'cart_total', safeNumber(total));\n }\n\n return meta;\n}\n\nfunction countLineItems(items: { quantity?: number }[] | undefined): number | undefined {\n if (!items || items.length === 0) return undefined;\n let count = 0;\n let hasNumericQuantity = false;\n for (const item of items) {\n const q = safeNumber(item.quantity);\n if (q !== undefined) {\n hasNumericQuantity = true;\n count += q;\n }\n }\n // Fallback to row count only when no line had a numeric quantity. An explicit\n // sum of zero (e.g. `[{ quantity: 0 }]`) must be preserved, not replaced with\n // `items.length`, otherwise empty carts over-report `item_count`.\n return hasNumericQuantity ? count : items.length;\n}\n\n// Caps merchant-controlled free-text to keep payloads under the API's per-string DTO limit.\n// IDs, tokens, and currency codes are naturally bounded by Shopify and skip capping.\nconst MAX_TEXT_LEN = 255;\nconst MAX_SKU_LEN = 128;\n\n// Mirrors `src/constants/config.constants.ts` `XSS_PATTERNS`. Inlined because\n// the constants module pulls in unrelated dependencies that would blow the\n// pixel bundle's 5KB budget. Defense-in-depth: Shopify product titles/SKUs/\n// vendors are merchant-controlled; a compromised admin could inject HTML/JS\n// that lands in dashboards or logs.\nconst XSS_PATTERNS: readonly RegExp[] = [\n /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n /javascript:/gi,\n /on\\w+\\s*=/gi,\n /<iframe\\b[^<]*(?:(?!<\\/iframe>)<[^<]*)*<\\/iframe>/gi,\n /<embed\\b[^>]*>/gi,\n /<object\\b[^<]*(?:(?!<\\/object>)<[^<]*)*<\\/object>/gi,\n];\n\nfunction sanitize(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n let out = value;\n for (const pattern of XSS_PATTERNS) out = out.replace(pattern, '');\n return out;\n}\n\nfunction cap(value: string | undefined, max: number): string | undefined {\n if (value === undefined) return undefined;\n return value.length > max ? value.slice(0, max) : value;\n}\n\nfunction mapLineItems(items: ShopifyLineItem[] | undefined): Record<string, unknown>[] {\n if (!items || items.length === 0) return [];\n const capped = items.slice(0, MAX_LINE_ITEMS);\n return capped.map((item) => {\n const out: Record<string, unknown> = {};\n const variantId = item.variant?.id;\n if (typeof variantId === 'string' || typeof variantId === 'number') {\n out['id'] = String(variantId);\n }\n setIfDefined(out, 'title', cap(sanitize(safeString(item.title ?? item.variant?.product?.title)), MAX_TEXT_LEN));\n setIfDefined(out, 'quantity', safeNumber(item.quantity));\n setIfDefined(out, 'price', safeNumber(item.variant?.price?.amount ?? item.finalLinePrice?.amount));\n setIfDefined(out, 'sku', cap(sanitize(safeString(item.variant?.sku ?? undefined)), MAX_SKU_LEN));\n setIfDefined(out, 'vendor', cap(sanitize(safeString(item.variant?.product?.vendor)), MAX_TEXT_LEN));\n return out;\n });\n}\n\nfunction generateEventId(event: ShopifyEvent, ts: number): string {\n const suffix = safeString(event.id)?.slice(-6) ?? Math.random().toString(36).slice(2, 8);\n return `${ts}-001-${suffix}`;\n}\n\nfunction resolveTimestamp(value: string | undefined): number {\n if (typeof value !== 'string' || value.length === 0) return Date.now();\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? parsed : Date.now();\n}\n\nfunction stripUrlParams(href: string): string {\n // Pixel runs in the checkout sandbox where href can carry recovery tokens\n // (`?recovery=...`, `?key=...`). Path alone identifies the funnel stage; drop\n // query and hash to keep tokens out of telemetry. Mirrors the policy in\n // `src/utils/network/url.utils.ts` (which is too heavy to import into the\n // sub-5KB pixel bundle).\n const queryIdx = href.indexOf('?');\n const hashIdx = href.indexOf('#');\n let cutoff = href.length;\n if (queryIdx !== -1) cutoff = queryIdx;\n if (hashIdx !== -1 && hashIdx < cutoff) cutoff = hashIdx;\n return href.slice(0, cutoff);\n}\n\nfunction resolvePageUrl(event: ShopifyEvent): string {\n const href = safeString(event.context?.window?.location?.href) ?? safeString(event.context?.document?.location?.href);\n return href !== undefined ? stripUrlParams(href) : 'unknown';\n}\n\nexport function mapEventToBody(\n shopifyEvent: ShopifyEvent | null | undefined,\n shopifyEventName: ShopifyEventName,\n): PixelEventBody | null {\n if (!shopifyEvent) return null;\n\n const checkoutAttrs = shopifyEvent.data?.checkout?.attributes;\n const cartAttrs = shopifyEvent.data?.cart?.attributes;\n\n const sessionId = attrLookup(checkoutAttrs, 'tracelog_session_id') ?? attrLookup(cartAttrs, 'tracelog_session_id');\n const userId = attrLookup(checkoutAttrs, 'tracelog_user_id') ?? attrLookup(cartAttrs, 'tracelog_user_id');\n\n if (sessionId === null || userId === null) return null;\n\n const ts = resolveTimestamp(shopifyEvent.timestamp);\n const eventId = generateEventId(shopifyEvent, ts);\n const metadata = buildMetadata(shopifyEvent, shopifyEventName);\n\n return {\n user_id: userId,\n session_id: sessionId,\n device: { type: 'unknown', os: 'unknown', browser: 'unknown' },\n events: [\n {\n id: eventId,\n type: 'custom',\n page_url: resolvePageUrl(shopifyEvent),\n timestamp: ts,\n custom_event: {\n name: `shopify_${shopifyEventName}`,\n metadata,\n },\n },\n ],\n _metadata: {\n client_version: PIXEL_CLIENT_VERSION,\n timestamp: ts,\n },\n };\n}\n","/**\n * Standalone HTTP sender for the Shopify Web Pixel Extension bundle.\n *\n * Posts to the path-based ingress (`ingest.tracelog.io/p/<id>/collect`) — NOT\n * `api.tracelog.io/events/collect` — because the middleware has the only CORS\n * handler that accepts `Origin: null` from sandboxed iframes.\n *\n * Best-effort: failures are silently swallowed. The webhook (Task 03) carries\n * the revenue contract; pixel events are funnel-only and accept ~5-30% loss.\n */\n\nconst INGEST_HOST = 'https://ingest.tracelog.io';\n\nexport interface PixelSenderSettings {\n /** TraceLog project identifier (e.g. `t756edc0pnn17ha7`). Provided by Shopify init payload. */\n projectId: string;\n}\n\nexport interface PixelEventBody {\n user_id: string;\n session_id: string;\n device: { type: string; os: string; browser: string };\n events: Array<{\n id: string;\n type: 'custom';\n page_url: string;\n timestamp: number;\n custom_event: { name: string; metadata: Record<string, unknown> };\n }>;\n _metadata: { client_version: string; timestamp: number };\n}\n\nexport function sendBatch(settings: PixelSenderSettings, body: PixelEventBody): void {\n // Trust boundary is the Shopify extension settings form, but a misconfigured\n // (empty) projectId would yield `https://ingest.tracelog.io/p//collect` and\n // 404 every event. Drop silently so it can be diagnosed via Shopify pixel logs.\n if (!settings.projectId) return;\n\n // Encode in case the merchant pastes whitespace, slashes, or other unsafe\n // characters into the Shopify extension settings form.\n const url = `${INGEST_HOST}/p/${encodeURIComponent(settings.projectId)}/collect`;\n try {\n void fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n keepalive: true,\n body: JSON.stringify(body),\n }).catch(() => {});\n } catch {\n // Pixel runs inside Shopify's strict sandbox; fetch() may be unavailable\n // or throw synchronously. Funnel events are best-effort — drop silently.\n }\n}\n","import { mapEventToBody, type ShopifyEventName } from './event-mapper';\nimport { sendBatch, type PixelSenderSettings } from './pixel-sender';\n\ninterface ShopifyAnalyticsApi {\n subscribe: (event: ShopifyEventName, callback: (event: unknown) => void) => void;\n}\n\ninterface ShopifyPixelApi {\n analytics: ShopifyAnalyticsApi;\n}\n\n/**\n * Registers the TraceLog Shopify Web Pixel: subscribes to the 7 standard\n * Customer Events and forwards each one to `ingest.tracelog.io/p/<id>/collect`.\n *\n * Each subscription is declared explicitly because Shopify's static analyzer\n * inspects the bundle for `analytics.subscribe('event_name', ...)` calls;\n * loop-based subscriptions are flagged and the pixel is treated as inactive.\n */\nexport function registerShopifyPixel(api: ShopifyPixelApi, settings: PixelSenderSettings): void {\n const handle = (eventName: ShopifyEventName, payload: unknown): void => {\n const body = mapEventToBody(payload as Parameters<typeof mapEventToBody>[0], eventName);\n if (!body) return;\n sendBatch(settings, body);\n };\n\n api.analytics.subscribe('cart_viewed', (event) => {\n handle('cart_viewed', event);\n });\n api.analytics.subscribe('checkout_started', (event) => {\n handle('checkout_started', event);\n });\n api.analytics.subscribe('checkout_contact_info_submitted', (event) => {\n handle('checkout_contact_info_submitted', event);\n });\n api.analytics.subscribe('checkout_address_info_submitted', (event) => {\n handle('checkout_address_info_submitted', event);\n });\n api.analytics.subscribe('checkout_shipping_info_submitted', (event) => {\n handle('checkout_shipping_info_submitted', event);\n });\n api.analytics.subscribe('payment_info_submitted', (event) => {\n handle('payment_info_submitted', event);\n });\n api.analytics.subscribe('checkout_completed', (event) => {\n handle('checkout_completed', event);\n });\n}\n"],"names":["SHOPIFY_EVENTS","SHOPIFY_PIXEL_EVENT_NAMES","map","name","attrLookup","attrs","key","Array","isArray","a","value","length","safeNumber","Number","isFinite","safeString","setIfDefined","target","buildMetadata","event","checkout","data","cart","meta","clientId","checkoutToken","token","totalPrice","amount","subtotalPrice","currencyCode","orderId","order","id","String","rawItems","lineItems","items","capped","slice","item","out","variantId","variant","cap","sanitize","title","product","MAX_TEXT_LEN","quantity","price","finalLinePrice","sku","MAX_SKU_LEN","vendor","mapLineItems","cost","totalAmount","countLineItems","lines","checkoutLineItems","total","count","hasNumericQuantity","q","XSS_PATTERNS","pattern","replace","max","resolvePageUrl","href","context","window","location","document","queryIdx","indexOf","hashIdx","cutoff","stripUrlParams","mapEventToBody","shopifyEvent","shopifyEventName","checkoutAttrs","attributes","cartAttrs","sessionId","userId","ts","Date","now","parsed","parse","resolveTimestamp","timestamp","eventId","Math","random","toString","generateEventId","metadata","user_id","session_id","device","type","os","browser","events","page_url","custom_event","_metadata","client_version","sendBatch","settings","body","projectId","url","encodeURIComponent","fetch","method","headers","keepalive","JSON","stringify","catch","api","handle","eventName","payload","analytics","subscribe"],"mappings":"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"}
@@ -79,7 +79,7 @@ const p = {
79
79
  /<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,
80
80
  /<embed\b[^>]*>/gi,
81
81
  /<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi
82
- ], S = "tlog", X = `${S}:qa_mode`, Te = `${S}:uid`, rt = "tlog_mode", Ue = "qa", He = "qa_off", Ct = (r) => r ? `${S}:${r}:queue` : `${S}:queue`, Rt = (r) => r ? `${S}:${r}:rate_limit` : `${S}:rate_limit`, Nt = (r) => r ? `${S}:${r}:session` : `${S}:session`, Ot = (r) => r ? `${S}:${r}:broadcast` : `${S}:broadcast`, Fe = (r, e) => `${S}:${r}:session_counts:${e}`, xe = 10080 * 60 * 1e3, $e = `${S}:session_counts_last_cleanup`, Be = 3600 * 1e3, fe = (r) => r ? `${S}:${r}:identity` : `${S}:identity`, U = `${S}:pending_identity`;
82
+ ], S = "tlog", X = `${S}:qa_mode`, Te = `${S}:uid`, rt = "tlog_mode", Ue = "qa", Fe = "qa_off", Ct = (r) => r ? `${S}:${r}:queue` : `${S}:queue`, Rt = (r) => r ? `${S}:${r}:rate_limit` : `${S}:rate_limit`, Nt = (r) => r ? `${S}:${r}:session` : `${S}:session`, Ot = (r) => r ? `${S}:${r}:broadcast` : `${S}:broadcast`, He = (r, e) => `${S}:${r}:session_counts:${e}`, xe = 10080 * 60 * 1e3, $e = `${S}:session_counts_last_cleanup`, Be = 3600 * 1e3, fe = (r) => r ? `${S}:${r}:identity` : `${S}:identity`, U = `${S}:pending_identity`;
83
83
  var $ = /* @__PURE__ */ ((r) => (r.Localhost = "localhost:8080", r.Fail = "localhost:9999", r))($ || {}), A = /* @__PURE__ */ ((r) => (r.Mobile = "mobile", r.Tablet = "tablet", r.Desktop = "desktop", r.Unknown = "unknown", r))(A || {}), se = /* @__PURE__ */ ((r) => (r.EVENT = "event", r.QUEUE = "queue", r))(se || {});
84
84
  class O extends Error {
85
85
  constructor(e, t, s) {
@@ -164,9 +164,9 @@ const nt = "background: #ff9800; color: white; font-weight: bold; padding: 2px 8
164
164
  const { error: s, data: n, showToClient: i = !1, style: o, visibility: l } = t ?? {}, c = s ? kt(e, s) : `[TraceLog] ${e}`, d = r === "error" ? "error" : r === "warn" ? "warn" : "log";
165
165
  if (!Ut(l, i))
166
166
  return;
167
- const g = Ht(l, o), I = n !== void 0 ? Ie(n) : void 0;
168
- Ft(d, c, g, I);
169
- }, Ut = (r, e) => r === "critical" ? !0 : r === "qa" || e ? Vt() : !1, Ht = (r, e) => e !== void 0 && e !== "" ? e : r === "critical" ? Dt : "", Ft = (r, e, t, s) => {
167
+ const g = Ft(l, o), I = n !== void 0 ? Ie(n) : void 0;
168
+ Ht(d, c, g, I);
169
+ }, Ut = (r, e) => r === "critical" ? !0 : r === "qa" || e ? Vt() : !1, Ft = (r, e) => e !== void 0 && e !== "" ? e : r === "critical" ? Dt : "", Ht = (r, e, t, s) => {
170
170
  const n = t !== void 0 && t !== "", i = n ? `%c${e}` : e;
171
171
  s !== void 0 ? n ? console[r](i, t, s) : console[r](i, s) : n ? console[r](i, t) : console[r](i);
172
172
  }, Ie = (r) => {
@@ -319,10 +319,10 @@ const xt = () => {
319
319
  return e === Ue ? (s = !0, sessionStorage.setItem(X, "true"), a("info", "QA Mode ACTIVE", {
320
320
  visibility: "qa",
321
321
  style: nt
322
- })) : e === He && (s = !1, sessionStorage.setItem(X, "false"), a("info", "QA Mode DISABLED", {
322
+ })) : e === Fe && (s = !1, sessionStorage.setItem(X, "false"), a("info", "QA Mode DISABLED", {
323
323
  visibility: "qa",
324
324
  style: it
325
- })), (e === Ue || e === He) && ts(), s ?? t === "true";
325
+ })), (e === Ue || e === Fe) && ts(), s ?? t === "true";
326
326
  } catch {
327
327
  return !1;
328
328
  }
@@ -2887,7 +2887,7 @@ class vs extends _ {
2887
2887
  loadSessionCounts(e) {
2888
2888
  if (typeof window > "u" || typeof localStorage > "u")
2889
2889
  return this.getInitialCounts();
2890
- const t = this.get("userId") || "anonymous", s = Fe(t, e);
2890
+ const t = this.get("userId") || "anonymous", s = He(t, e);
2891
2891
  try {
2892
2892
  const n = localStorage.getItem(s);
2893
2893
  if (!n)
@@ -2997,7 +2997,7 @@ class vs extends _ {
2997
2997
  * @internal
2998
2998
  */
2999
2999
  saveSessionCounts(e) {
3000
- const t = this.get("userId") || "anonymous", s = Fe(t, e);
3000
+ const t = this.get("userId") || "anonymous", s = He(t, e);
3001
3001
  try {
3002
3002
  const n = {
3003
3003
  ...this.sessionEventCounts,
@@ -4184,7 +4184,6 @@ const Rs = "tracelog_session_id", Ns = "tracelog_user_id";
4184
4184
  class Os extends _ {
4185
4185
  visibilityHandler = null;
4186
4186
  pageshowHandler = null;
4187
- submitHandler = null;
4188
4187
  lastSyncedKey = null;
4189
4188
  activate() {
4190
4189
  this.cleanupListeners(), this.syncCartAttribute(), this.setupListeners();
@@ -4221,30 +4220,24 @@ class Os extends _ {
4221
4220
  }
4222
4221
  }
4223
4222
  /**
4224
- * Sync triggers (theme-agnostic, no monkey-patching):
4225
- *
4226
- * - `visibilitychange`: catches tab refocus (long sessions, OAuth round-trips).
4227
- * - `pageshow`: catches bfcache restore Shopify's "Buy It Now" redirects keep
4228
- * the original product page in bfcache; on back-navigation Shopify may reuse
4229
- * the cart without re-running scripts.
4230
- * - `submit` (capture-phase, document-level): fires before any product form
4231
- * submission (add-to-cart, buy-now). This maximizes the chance that the cart
4232
- * attribute write completes before the page navigates to /cart or /checkout.
4233
- *
4234
- * All triggers go through `syncCartAttribute()` which dedupes by
4235
- * `sessionId|userId`, so spurious calls are cheap (early-return).
4223
+ * Sync triggers (theme-agnostic):
4224
+ * - `visibilitychange`: catches tab refocus (long sessions, OAuth round-trips).
4225
+ * - `pageshow` with `event.persisted === true`: catches bfcache restore so a
4226
+ * user returning from an external checkout / Shop Pay window picks up the
4227
+ * current sessionId before any further interaction.
4228
+ *
4229
+ * Both triggers go through `syncCartAttribute()` which dedupes by
4230
+ * `sessionId|userId`, so spurious calls cost nothing.
4236
4231
  */
4237
4232
  setupListeners() {
4238
4233
  this.visibilityHandler = () => {
4239
4234
  document.hidden || this.syncCartAttribute();
4240
4235
  }, document.addEventListener("visibilitychange", this.visibilityHandler), this.pageshowHandler = (e) => {
4241
4236
  e.persisted && this.syncCartAttribute();
4242
- }, window.addEventListener("pageshow", this.pageshowHandler), this.submitHandler = () => {
4243
- this.syncCartAttribute();
4244
- }, document.addEventListener("submit", this.submitHandler, !0);
4237
+ }, window.addEventListener("pageshow", this.pageshowHandler);
4245
4238
  }
4246
4239
  cleanupListeners() {
4247
- this.visibilityHandler && (document.removeEventListener("visibilitychange", this.visibilityHandler), this.visibilityHandler = null), this.pageshowHandler && (window.removeEventListener("pageshow", this.pageshowHandler), this.pageshowHandler = null), this.submitHandler && (document.removeEventListener("submit", this.submitHandler, !0), this.submitHandler = null);
4240
+ this.visibilityHandler && (document.removeEventListener("visibilitychange", this.visibilityHandler), this.visibilityHandler = null), this.pageshowHandler && (window.removeEventListener("pageshow", this.pageshowHandler), this.pageshowHandler = null);
4248
4241
  }
4249
4242
  }
4250
4243
  class Ps {
@@ -5311,7 +5304,7 @@ const Vs = async (r) => typeof window > "u" || typeof document > "u" ? { session
5311
5304
  throw new Error("[TraceLog] Cannot send events while TraceLog is being destroyed");
5312
5305
  h.sendCustomEvent(r, e);
5313
5306
  }
5314
- }, Hs = (r, e) => {
5307
+ }, Fs = (r, e) => {
5315
5308
  if (!(typeof window > "u" || typeof document > "u")) {
5316
5309
  if (!h || R) {
5317
5310
  k.push({ event: r, callback: e });
@@ -5319,7 +5312,7 @@ const Vs = async (r) => typeof window > "u" || typeof document > "u" ? { session
5319
5312
  }
5320
5313
  h.on(r, e);
5321
5314
  }
5322
- }, Fs = (r, e) => {
5315
+ }, Hs = (r, e) => {
5323
5316
  if (!(typeof window > "u" || typeof document > "u")) {
5324
5317
  if (!h) {
5325
5318
  const t = k.findIndex((s) => s.event === r && s.callback === e);
@@ -5453,8 +5446,8 @@ const $s = (r) => {
5453
5446
  }, Lr = {
5454
5447
  init: Vs,
5455
5448
  event: Us,
5456
- on: Hs,
5457
- off: Fs,
5449
+ on: Fs,
5450
+ off: Hs,
5458
5451
  setTransformer: xs,
5459
5452
  removeTransformer: $s,
5460
5453
  setCustomHeaders: Bs,
@@ -5481,7 +5474,7 @@ var Le, C, G, pt, le, Et = -1, V = function(r) {
5481
5474
  }, y = function(r, e) {
5482
5475
  var t = Pe(), s = "navigate";
5483
5476
  return Et >= 0 ? s = "back-forward-cache" : t && (document.prerendering || de() > 0 ? s = "prerender" : document.wasDiscarded ? s = "restore" : t.type && (s = t.type.replace(/_/g, "-"))), { name: r, value: e === void 0 ? -1 : e, rating: "good", delta: 0, entries: [], id: "v4-".concat(Date.now(), "-").concat(Math.floor(8999999999999 * Math.random()) + 1e12), navigationType: s };
5484
- }, F = function(r, e, t) {
5477
+ }, H = function(r, e, t) {
5485
5478
  try {
5486
5479
  if (PerformanceObserver.supportedEntryTypes.includes(r)) {
5487
5480
  var s = new PerformanceObserver((function(n) {
@@ -5515,21 +5508,21 @@ var Le, C, G, pt, le, Et = -1, V = function(r) {
5515
5508
  return function() {
5516
5509
  e || (r(), e = !0);
5517
5510
  };
5518
- }, H = -1, et = function() {
5511
+ }, F = -1, et = function() {
5519
5512
  return document.visibilityState !== "hidden" || document.prerendering ? 1 / 0 : 0;
5520
5513
  }, ce = function(r) {
5521
- document.visibilityState === "hidden" && H > -1 && (H = r.type === "visibilitychange" ? r.timeStamp : 0, Js());
5514
+ document.visibilityState === "hidden" && F > -1 && (F = r.type === "visibilitychange" ? r.timeStamp : 0, Js());
5522
5515
  }, tt = function() {
5523
5516
  addEventListener("visibilitychange", ce, !0), addEventListener("prerenderingchange", ce, !0);
5524
5517
  }, Js = function() {
5525
5518
  removeEventListener("visibilitychange", ce, !0), removeEventListener("prerenderingchange", ce, !0);
5526
5519
  }, ke = function() {
5527
- return H < 0 && (H = et(), tt(), V((function() {
5520
+ return F < 0 && (F = et(), tt(), V((function() {
5528
5521
  setTimeout((function() {
5529
- H = et(), tt();
5522
+ F = et(), tt();
5530
5523
  }), 0);
5531
5524
  }))), { get firstHiddenTime() {
5532
- return H;
5525
+ return F;
5533
5526
  } };
5534
5527
  }, z = function(r) {
5535
5528
  document.prerendering ? addEventListener("prerenderingchange", (function() {
@@ -5537,7 +5530,7 @@ var Le, C, G, pt, le, Et = -1, V = function(r) {
5537
5530
  }), !0) : r();
5538
5531
  }, Ae = [1800, 3e3], St = function(r, e) {
5539
5532
  e = e || {}, z((function() {
5540
- var t, s = ke(), n = y("FCP"), i = F("paint", (function(o) {
5533
+ var t, s = ke(), n = y("FCP"), i = H("paint", (function(o) {
5541
5534
  o.forEach((function(l) {
5542
5535
  l.name === "first-contentful-paint" && (i.disconnect(), l.startTime < s.firstHiddenTime && (n.value = Math.max(l.startTime - de(), 0), n.entries.push(l), t(!0)));
5543
5536
  }));
@@ -5557,7 +5550,7 @@ var Le, C, G, pt, le, Et = -1, V = function(r) {
5557
5550
  n && d.startTime - g.startTime < 1e3 && d.startTime - f.startTime < 5e3 ? (n += d.value, i.push(d)) : (n = d.value, i = [d]);
5558
5551
  }
5559
5552
  })), n > s.value && (s.value = n, s.entries = i, t());
5560
- }, l = F("layout-shift", o);
5553
+ }, l = H("layout-shift", o);
5561
5554
  l && (t = w(r, s, Me, e.reportAllChanges), K((function() {
5562
5555
  o(l.takeRecords()), t(!0);
5563
5556
  })), V((function() {
@@ -5573,7 +5566,7 @@ var Le, C, G, pt, le, Et = -1, V = function(r) {
5573
5566
  }, It = function() {
5574
5567
  return Le ? Tt : performance.interactionCount || 0;
5575
5568
  }, tr = function() {
5576
- "interactionCount" in performance || Le || (Le = F("event", er, { type: "event", buffered: !0, durationThreshold: 0 }));
5569
+ "interactionCount" in performance || Le || (Le = H("event", er, { type: "event", buffered: !0, durationThreshold: 0 }));
5577
5570
  }, L = [], te = /* @__PURE__ */ new Map(), vt = 0, sr = function() {
5578
5571
  var r = Math.min(L.length - 1, Math.floor((It() - vt) / 50));
5579
5572
  return L[r];
@@ -5608,7 +5601,7 @@ var Le, C, G, pt, le, Et = -1, V = function(r) {
5608
5601
  var c = sr();
5609
5602
  c && c.latency !== n.value && (n.value = c.latency, n.entries = c.entries, s());
5610
5603
  }));
5611
- }, o = F("event", i, { durationThreshold: (t = e.durationThreshold) !== null && t !== void 0 ? t : 40 });
5604
+ }, o = H("event", i, { durationThreshold: (t = e.durationThreshold) !== null && t !== void 0 ? t : 40 });
5612
5605
  s = w(r, n, Ce, e.reportAllChanges), o && (o.observe({ type: "first-input", buffered: !0 }), K((function() {
5613
5606
  i(o.takeRecords()), s(!0);
5614
5607
  })), V((function() {
@@ -5621,7 +5614,7 @@ var Le, C, G, pt, le, Et = -1, V = function(r) {
5621
5614
  e.reportAllChanges || (c = c.slice(-1)), c.forEach((function(d) {
5622
5615
  d.startTime < s.firstHiddenTime && (n.value = Math.max(d.startTime - de(), 0), n.entries = [d], t());
5623
5616
  }));
5624
- }, o = F("largest-contentful-paint", i);
5617
+ }, o = H("largest-contentful-paint", i);
5625
5618
  if (o) {
5626
5619
  t = w(r, n, Re, e.reportAllChanges);
5627
5620
  var l = ue((function() {
@@ -5686,7 +5679,7 @@ var Le, C, G, pt, le, Et = -1, V = function(r) {
5686
5679
  c.startTime < s.firstHiddenTime && (n.value = c.processingStart - c.startTime, n.entries.push(c), t(!0));
5687
5680
  }, o = function(c) {
5688
5681
  c.forEach(i);
5689
- }, l = F("first-input", o);
5682
+ }, l = H("first-input", o);
5690
5683
  t = w(r, n, Oe, e.reportAllChanges), l && (K(ue((function() {
5691
5684
  o(l.takeRecords()), l.disconnect();
5692
5685
  }))), V((function() {