@shware/analytics 7.3.0 → 7.4.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/README.md +12 -0
- package/dist/hooks/use-web-analytics.cjs +4 -3
- package/dist/hooks/use-web-analytics.cjs.map +1 -1
- package/dist/hooks/use-web-analytics.d.cts.map +1 -1
- package/dist/hooks/use-web-analytics.d.mts.map +1 -1
- package/dist/hooks/use-web-analytics.mjs +4 -3
- package/dist/hooks/use-web-analytics.mjs.map +1 -1
- package/dist/next/index.cjs +1 -1
- package/dist/next/index.mjs +1 -1
- package/dist/react-router/index.cjs +1 -1
- package/dist/react-router/index.mjs +1 -1
- package/dist/schema/index.cjs +9 -2
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +6 -2
- package/dist/schema/index.d.cts.map +1 -1
- package/dist/schema/index.d.mts +6 -2
- package/dist/schema/index.d.mts.map +1 -1
- package/dist/schema/index.mjs +9 -2
- package/dist/schema/index.mjs.map +1 -1
- package/dist/setup/session.cjs +8 -9
- package/dist/setup/session.cjs.map +1 -1
- package/dist/setup/session.d.cts +8 -6
- package/dist/setup/session.d.cts.map +1 -1
- package/dist/setup/session.d.mts +8 -6
- package/dist/setup/session.d.mts.map +1 -1
- package/dist/setup/session.mjs +8 -9
- package/dist/setup/session.mjs.map +1 -1
- package/dist/test/setup.cjs +43 -0
- package/dist/test/setup.cjs.map +1 -0
- package/dist/test/setup.d.cts +19 -0
- package/dist/test/setup.d.cts.map +1 -0
- package/dist/test/setup.d.mts +19 -0
- package/dist/test/setup.d.mts.map +1 -0
- package/dist/test/setup.mjs +40 -0
- package/dist/test/setup.mjs.map +1 -0
- package/dist/third-parties/meta-pixel.cjs +1 -1
- package/dist/third-parties/meta-pixel.cjs.map +1 -1
- package/dist/third-parties/meta-pixel.mjs +1 -1
- package/dist/third-parties/meta-pixel.mjs.map +1 -1
- package/dist/track/fbq.cjs +3 -3
- package/dist/track/fbq.cjs.map +1 -1
- package/dist/track/fbq.d.cts.map +1 -1
- package/dist/track/fbq.d.mts.map +1 -1
- package/dist/track/fbq.mjs +3 -3
- package/dist/track/fbq.mjs.map +1 -1
- package/dist/track/index.cjs +3 -2
- package/dist/track/index.cjs.map +1 -1
- package/dist/track/index.d.cts.map +1 -1
- package/dist/track/index.d.mts.map +1 -1
- package/dist/track/index.mjs +3 -2
- package/dist/track/index.mjs.map +1 -1
- package/dist/track/types.d.cts +1 -0
- package/dist/track/types.d.cts.map +1 -1
- package/dist/track/types.d.mts +1 -0
- package/dist/track/types.d.mts.map +1 -1
- package/dist/web/index.cjs +1 -0
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts.map +1 -1
- package/dist/web/index.d.mts.map +1 -1
- package/dist/web/index.mjs +1 -0
- package/dist/web/index.mjs.map +1 -1
- package/package.json +5 -1
package/dist/track/fbq.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fbq.d.mts","names":[],"sources":["../../src/track/fbq.ts"],"mappings":";;;KAIY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;KAOU;;EAEV;;EAGA;;EAGA;;EAGA;;;;;EAMA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;;;;;;;EAUA;;;;;;;;EASA;;;;;;EAOA;;;;;;EAOA;;;;;EAMA;;;;;;EAOA;;;;;EAMA;;;;;;EAOA;;;;;;EAOA;;;;;EAMA;;;;;EAMA;;;;;;EAOA;;;;;;;KAQU;EACV;EACA;EACA;;;;;;;;;EAUA;EACA,WAAW;EACX;EACA;EACA;EACA;EACA;;EAGA;EACA;;;;;KAMU;EACV;IACE;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA;IACA;;EAEF;EACA;EACA;EACA;EACA;IACE;IACA,WAAW;IACX;IACA;IACA;;EAEF;IACE;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;IACA;;EAEF;EACA;IACE;IACA;IACA,WAAW;IACX;IACA;IACA;;EAEF;IACE;IACA;IACA;;EAEF;EACA;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;;;KAIC,+CAKD,MAAM;GACH,gBAAgB;;KAEX;KACA;EAAY;;;;;;;;;;;UAWP;;;;;;;;;;;;;;EAcf,IAAI,cAAc,SAAS,SAAS,aAAa;;EAGjD,IAAI,aAAa,mBAAmB,gBAAgB,SAAS;EAE7D,IAAI,gBAAgB,gBAClB,eACA,OAAO,GACP,aAAa,eAAe,KAAK,kBACjC,UAAU;EAGZ,IACE,qBACA,eACA,aAAa,eAAe,aAAa,kBACzC,UAAU;;EAIZ,IAAI,gBAAgB,gBAClB,qBACA,SAAS,SACT,OAAO,GACP,aAAa,eAAe,KAAK,kBACjC,UAAU;;EAIZ,IACE,2BACA,SAAS,SACT,eACA,aAAa,eAAe,aAAa,kBACzC,UAAU;;;;;;iBAQE,UAAU,YAAY,qBAAqB;
|
|
1
|
+
{"version":3,"file":"fbq.d.mts","names":[],"sources":["../../src/track/fbq.ts"],"mappings":";;;KAIY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;KAOU;;EAEV;;EAGA;;EAGA;;EAGA;;;;;EAMA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;;;;;;;EAUA;;;;;;;;EASA;;;;;;EAOA;;;;;;EAOA;;;;;EAMA;;;;;;EAOA;;;;;EAMA;;;;;;EAOA;;;;;;EAOA;;;;;EAMA;;;;;EAMA;;;;;;EAOA;;;;;;;KAQU;EACV;EACA;EACA;;;;;;;;;EAUA;EACA,WAAW;EACX;EACA;EACA;EACA;EACA;;EAGA;EACA;;;;;KAMU;EACV;IACE;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA;IACA;;EAEF;EACA;EACA;EACA;EACA;IACE;IACA,WAAW;IACX;IACA;IACA;;EAEF;IACE;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;IACA;;EAEF;EACA;IACE;IACA;IACA,WAAW;IACX;IACA;IACA;;EAEF;IACE;IACA;IACA;;EAEF;EACA;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;;;KAIC,+CAKD,MAAM;GACH,gBAAgB;;KAEX;KACA;EAAY;;;;;;;;;;;UAWP;;;;;;;;;;;;;;EAcf,IAAI,cAAc,SAAS,SAAS,aAAa;;EAGjD,IAAI,aAAa,mBAAmB,gBAAgB,SAAS;EAE7D,IAAI,gBAAgB,gBAClB,eACA,OAAO,GACP,aAAa,eAAe,KAAK,kBACjC,UAAU;EAGZ,IACE,qBACA,eACA,aAAa,eAAe,aAAa,kBACzC,UAAU;;EAIZ,IAAI,gBAAgB,gBAClB,qBACA,SAAS,SACT,OAAO,GACP,aAAa,eAAe,KAAK,kBACjC,UAAU;;EAIZ,IACE,2BACA,SAAS,SACT,eACA,aAAa,eAAe,aAAa,kBACzC,UAAU;;;;;;iBAQE,UAAU,YAAY,qBAAqB;iBAc3C,SAAS,QAAQ,SAAS;KAsBrC,OAAO,gBAAgB,4BAA4B,GAAG,eAAe,KAAK;KAC1E,OAAO,oCAAoC,GAAG,eAAe,aAAa;iBAE/D,WAAW,UAAU,WACnC,MAAM,UAAU,IAChB,aAAa,gBAAgB,KAC5B,aAAa,kBAAkB,OAAO,UAAU"}
|
package/dist/track/fbq.mjs
CHANGED
|
@@ -11,9 +11,9 @@ function normalize(parameters) {
|
|
|
11
11
|
zp: parameters.zp?.split("-").at(0)?.trim(),
|
|
12
12
|
fn: parameters.fn?.toLowerCase().trim(),
|
|
13
13
|
ln: parameters.ln?.toLowerCase().trim(),
|
|
14
|
-
ct: parameters.ct?.toLowerCase().replace(/[s/-]/g, "")
|
|
15
|
-
st: parameters.st?.toLowerCase().replace(/[s
|
|
16
|
-
country: parameters.country?.toLowerCase().replace(/[s/-]/g, "")
|
|
14
|
+
ct: parameters.ct?.toLowerCase().replace(/[\s/-]/g, ""),
|
|
15
|
+
st: parameters.st?.toLowerCase().replace(/[\s/,.-]/g, ""),
|
|
16
|
+
country: parameters.country?.toLowerCase().replace(/[\s/-]/g, "")
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
function mapItems(items) {
|
package/dist/track/fbq.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fbq.mjs","names":["p"],"sources":["../../src/track/fbq.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-empty-object-type */\nimport type { Item } from './gtag';\nimport type { EventName, TrackName, TrackProperties } from './types';\n\nexport type Content = {\n id: string;\n quantity: number;\n item_price?: number;\n title?: string;\n description?: string;\n brand?: string;\n category?: string;\n delivery_category?: string;\n};\n\n/**\n * reference: https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nexport type MatchingParameters = {\n /** Email: Unhashed lowercase or hashed SHA-256 */\n em?: string;\n\n /** First Name: Lowercase letters */\n fn?: string;\n\n /** Last Name: Lowercase letters */\n ln?: string;\n\n /** Phone Number: Digits only including country code and area code */\n ph?: string;\n\n /**\n * External ID: Any unique ID from the advertiser, such as loyalty membership ID, user ID, and\n * external cookie ID.\n */\n external_id?: string;\n\n /** Gender: Single lowercase letter, f or m, if unknown, leave blank */\n ge?: 'f' | 'm' | '';\n\n /** Birthdate: Digits only with birth year, month, then day, YYYYMMDD */\n db?: number;\n\n /** City: Lowercase with any spaces removed, e.g. \"menlopark\" */\n ct?: string;\n\n /** State or Province: Lowercase two-letter state or province code, e.g. \"ca\" */\n st?: string;\n\n /** Zip or Postal Code: String */\n zp?: string;\n\n /** Country: Lowercase two-letter country code, e.g. \"us\" */\n country?: string;\n\n /** Client IP Address: Do not hash. */\n client_ip_address?: string;\n\n /** Client User Agent: Do not hash. */\n client_user_agent?: string;\n\n /**\n * Click ID: Do not hash.\n * The Facebook click ID value is stored in the _fbc browser cookie under your domain. See\n * Managing fbc and fbp Parameters for how to get this value or generate this value from a fbclid\n * query parameter.\n *\n * The format is fb.${subdomain_index}.${creation_time}.${fbclid}.\n */\n fbc?: string;\n\n /**\n * Browser ID: Do not hash.\n * The Facebook browser ID value is stored in the _fbp browser cookie under your domain. See\n * Managing fbc and fbp Parameters for how to get this value.\n *\n * The format is fb.${subdomain_index}.${creation_time}.${random_number}.\n */\n fbp?: string;\n\n /**\n * Subscription ID: Do not hash.\n * The subscription ID for the user in this transaction; it is similar to the order ID for an\n * individual product.\n */\n subscription_id?: string;\n\n /**\n * Facebook Login ID: Do not hash.\n * The ID issued by Meta when a person first logs into an instance of an app. This is also known\n * as App-Scoped ID.\n */\n fb_login_id?: number;\n\n /**\n * Lead ID: Do not hash.\n * The ID associated with a lead generated by [Meta's Lead Ads](https://developers.facebook.com/docs/marketing-api/guides/lead-ads).\n */\n lead_id?: number;\n\n /**\n * Install ID: Do not hash.\n * Your install ID. This field represents unique application installation instances.\n * Note: This parameter is for app events only.\n */\n anon_id?: string;\n\n /**\n * Your mobile advertiser ID, the advertising ID from an Android device or the Advertising\n * Identifier (IDFA) from an Apple device.\n */\n madid?: string;\n\n /**\n * Page ID: Do not hash.\n * Your Page ID. Specifies the page ID associated with the event. Use the Facebook page ID of the\n * page associated with the bot.\n */\n page_id?: string;\n\n /**\n * Page Scoped User ID: Do not hash.\n * Specifies the page-scoped user ID associated with the messenger bot that logs the event. Use\n * the page-scoped user ID provided to your webhook.\n */\n page_scoped_user_id?: string;\n\n /**\n * Do not hash.\n * Click ID generated by Meta for ads that click to WhatsApp.\n */\n ctwa_clid?: string;\n\n /**\n * Do not hash.\n * Instagram Account ID that is associated with the business.\n */\n ig_account_id?: string;\n\n /**\n * Do not hash.\n * Users who interact with Instagram are identified by Instagram-Scoped User IDs (IGSID). IGSID\n * can be obtained from this webhook.\n */\n ig_sid?: string;\n};\n\n/**\n * You can include the following predefined object properties with any custom events, and any\n * standard events that support them. Format your parameter object data using JSON. Learn more about\n * event parameters with Blueprint.\n */\nexport type ObjectProperties = {\n content_category?: string;\n content_ids?: string[];\n content_name?: string;\n\n /**\n * Either product or product_group based on the content_ids or contents being passed. If the IDs\n * being passed in content_ids or contents parameter are IDs of products, then the value should be\n * product. If product group IDs are being passed, then the value should be product_group.\n *\n * If no content_type is provided, Meta will match the event to every item that has the same ID,\n * independent of its type.\n */\n content_type?: 'product' | 'product_group' | (string & {});\n contents?: Content[];\n delivery_category?: 'in_store' | 'curbside' | 'home_delivery';\n currency?: string;\n num_items?: number;\n predicted_ltv?: number;\n search_string?: string;\n\n /** Used with the CompleteRegistration event, to show the status of the registration. */\n status?: boolean;\n value?: number;\n};\n\n/**\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/payload-helper\n */\nexport type StandardEvents = {\n AddPaymentInfo: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n value?: number;\n };\n AddToCart: {\n content_ids?: string[];\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents\n currency?: string;\n value?: number;\n };\n AddToWishlist: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n value?: number;\n };\n CompleteRegistration: {\n currency?: string;\n value?: number;\n method?: string;\n };\n Contact: {};\n CustomizeProduct: {};\n Donate: {};\n FindLocation: {};\n InitiateCheckout: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n num_items?: number;\n value?: number;\n };\n Lead: {\n currency?: string;\n value?: number;\n };\n Purchase: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency: string; // required\n num_items?: number;\n value: number; // required\n };\n Schedule: {};\n Search: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency?: string;\n search_string?: string;\n value?: number;\n };\n StartTrial: {\n currency?: string;\n predicted_ltv?: number;\n value?: number;\n };\n SubmitApplication: {};\n Subscribe: {\n currency?: string;\n predicted_ltv?: number;\n value?: number;\n };\n ViewContent: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency?: string;\n value?: number;\n };\n};\n\ntype JSONValue =\n | null\n | string\n | number\n | boolean\n | Array<JSONValue>\n | { [value: string]: JSONValue };\n\nexport type PixelId = `${number}`;\nexport type Options = { eventID?: string };\n\n/**\n * reference: https://developers.facebook.com/docs/meta-pixel/reference#standard-events\n *\n * We determine if events are identical based on their ID and name. So, for an event to be deduplicated:\n * - In corresponding events, a Meta Pixel's eventID must match the Conversion API's event_id.\n * - In corresponding events, a Meta Pixel's event must match the Conversion API's event_name.\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nexport interface FBQ {\n /**\n * reference: https://stackoverflow.com/questions/62304291/sending-user-data-parameters-via-pixel\n *\n * Call init the normal default way first:\n * `fbq('init', 'XXXXX')`\n *\n * And at a later point in time, when you have obtained additional user data, you can call init\n * again basically enriching the already running fbq instance with additional data:\n * `fbq('init', 'XXXXX', { external_id: 1234, em: 'abc@abc.com' } )`\n *\n * Only caveat is that you have to send an event after this additional init call, otherwise the\n * provided data will not be sent to Facebook.\n */\n fbq(type: 'init', pixelId: PixelId, parameters?: MatchingParameters): void;\n\n /** Enable Manual Only mode. (value = false) */\n fbq(type: 'set', key: 'autoConfig', value: boolean, pixelId: PixelId): void;\n\n fbq<T extends keyof StandardEvents>(\n type: 'track',\n event: T,\n properties?: StandardEvents[T] & ObjectProperties,\n options?: Options\n ): void;\n\n fbq(\n type: 'trackCustom',\n event: string,\n properties?: Record<string, JSONValue> & ObjectProperties,\n options?: Options\n ): void;\n\n /** https://developers.facebook.com/docs/meta-pixel/guides/track-multiple-events/ */\n fbq<T extends keyof StandardEvents>(\n type: 'trackSingle',\n pixelId: PixelId,\n event: T,\n properties?: StandardEvents[T] & ObjectProperties,\n options?: Options\n ): void;\n\n /** https://developers.facebook.com/docs/meta-pixel/guides/track-multiple-events/ */\n fbq(\n type: 'trackSingleCustom',\n pixelId: PixelId,\n event: string,\n properties?: Record<string, JSONValue> & ObjectProperties,\n options?: Options\n ): void;\n}\n\n/**\n * Please download this CSV filefor examples of properly normalized and hashed data for the\n * parameters below.\n */\nexport function normalize(parameters: MatchingParameters): MatchingParameters {\n return {\n ...parameters,\n em: parameters.em?.toLowerCase().trim(),\n ph: parameters.ph?.replace(/[-+()\\s]/g, '').replace(/^0+/, ''),\n zp: parameters.zp?.split('-').at(0)?.trim(),\n fn: parameters.fn?.toLowerCase().trim(),\n ln: parameters.ln?.toLowerCase().trim(),\n ct: parameters.ct?.toLowerCase().replace(/[s/-]/g, '').trim(),\n st: parameters.st\n ?.toLowerCase()\n .replace(/[s/-/,.]/g, '')\n .trim(),\n country: parameters.country?.toLowerCase().replace(/[s/-]/g, '').trim(),\n };\n}\n\nexport function mapItems(items?: Item[]): ObjectProperties {\n if (!items) return {};\n const categories = Array.from(new Set(items.map((i) => i.item_category).filter(Boolean)));\n const contents: Content[] = items.map(\n ({ item_id, quantity, price, item_name, item_brand, item_category, ..._others }) => ({\n id: item_id,\n quantity: quantity ?? 1,\n item_price: price,\n title: item_name,\n brand: item_brand,\n category: item_category,\n })\n );\n\n return {\n content_category: categories.length === 1 ? categories.at(0) : undefined,\n contents,\n content_ids: contents.map((c) => c.id),\n num_items: items.reduce((acc, i) => acc + (i.quantity ?? 1), 0),\n };\n}\n\ntype Mapped<F extends keyof StandardEvents> = ['track', F, StandardEvents[F] & ObjectProperties];\ntype Missed<F extends string> = ['trackCustom', F, Record<string, JSONValue> & ObjectProperties];\n\nexport function mapFBEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n): Mapped<keyof StandardEvents> | Missed<TrackName<T>> {\n if (name === 'add_payment_info') {\n const p = properties as TrackProperties<'add_payment_info'> | undefined;\n return [\n 'track',\n 'AddPaymentInfo',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'add_to_cart') {\n const p = properties as TrackProperties<'add_to_cart'> | undefined;\n return [\n 'track',\n 'AddToCart',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'add_to_wishlist') {\n const p = properties as TrackProperties<'add_to_wishlist'> | undefined;\n return [\n 'track',\n 'AddToWishlist',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'login') {\n const p = properties as TrackProperties<'login'> | undefined;\n return ['track', 'CompleteRegistration', { method: p?.method }];\n } else if (name === 'contact') {\n return ['track', 'Contact', {}];\n } else if (name === 'customize_product') {\n return ['track', 'CustomizeProduct', {}];\n } else if (name === 'donate') {\n return ['track', 'Donate', {}];\n } else if (name === 'find_location') {\n return ['track', 'FindLocation', {}];\n } else if (name === 'begin_checkout') {\n const p = properties as TrackProperties<'begin_checkout'> | undefined;\n return [\n 'track',\n 'InitiateCheckout',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'generate_lead') {\n const p = properties as TrackProperties<'generate_lead'> | undefined;\n return ['track', 'Lead', { currency: p?.currency, value: p?.value }];\n } else if (name === 'purchase') {\n const p = properties as TrackProperties<'purchase'> | undefined;\n return [\n 'track',\n 'Purchase',\n { currency: p?.currency ?? 'USD', value: p?.value ?? 0, ...mapItems(p?.items) },\n ];\n } else if (name === 'schedule') {\n return ['track', 'Schedule', {}];\n } else if (name === 'search') {\n const p = properties as TrackProperties<'search'> | undefined;\n return ['track', 'Search', { search_string: p?.search_term }];\n } else if (name === 'trial_begin') {\n const p = properties as TrackProperties<'trial_begin'> | undefined;\n return ['track', 'StartTrial', { currency: p?.currency, value: p?.value }];\n } else if (name === 'submit_application') {\n return ['track', 'SubmitApplication', {}];\n } else if (name === 'subscribe') {\n const p = properties as TrackProperties<'subscribe'> | undefined;\n return ['track', 'Subscribe', { currency: p?.currency, value: p?.value }];\n } else if (name === 'view_item') {\n const p = properties as TrackProperties<'view_item'> | undefined;\n return [\n 'track',\n 'ViewContent',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else {\n return ['trackCustom', name, properties ?? {}];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fbq.mjs","names":["p"],"sources":["../../src/track/fbq.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-empty-object-type */\nimport type { Item } from './gtag';\nimport type { EventName, TrackName, TrackProperties } from './types';\n\nexport type Content = {\n id: string;\n quantity: number;\n item_price?: number;\n title?: string;\n description?: string;\n brand?: string;\n category?: string;\n delivery_category?: string;\n};\n\n/**\n * reference: https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nexport type MatchingParameters = {\n /** Email: Unhashed lowercase or hashed SHA-256 */\n em?: string;\n\n /** First Name: Lowercase letters */\n fn?: string;\n\n /** Last Name: Lowercase letters */\n ln?: string;\n\n /** Phone Number: Digits only including country code and area code */\n ph?: string;\n\n /**\n * External ID: Any unique ID from the advertiser, such as loyalty membership ID, user ID, and\n * external cookie ID.\n */\n external_id?: string;\n\n /** Gender: Single lowercase letter, f or m, if unknown, leave blank */\n ge?: 'f' | 'm' | '';\n\n /** Birthdate: Digits only with birth year, month, then day, YYYYMMDD */\n db?: number;\n\n /** City: Lowercase with any spaces removed, e.g. \"menlopark\" */\n ct?: string;\n\n /** State or Province: Lowercase two-letter state or province code, e.g. \"ca\" */\n st?: string;\n\n /** Zip or Postal Code: String */\n zp?: string;\n\n /** Country: Lowercase two-letter country code, e.g. \"us\" */\n country?: string;\n\n /** Client IP Address: Do not hash. */\n client_ip_address?: string;\n\n /** Client User Agent: Do not hash. */\n client_user_agent?: string;\n\n /**\n * Click ID: Do not hash.\n * The Facebook click ID value is stored in the _fbc browser cookie under your domain. See\n * Managing fbc and fbp Parameters for how to get this value or generate this value from a fbclid\n * query parameter.\n *\n * The format is fb.${subdomain_index}.${creation_time}.${fbclid}.\n */\n fbc?: string;\n\n /**\n * Browser ID: Do not hash.\n * The Facebook browser ID value is stored in the _fbp browser cookie under your domain. See\n * Managing fbc and fbp Parameters for how to get this value.\n *\n * The format is fb.${subdomain_index}.${creation_time}.${random_number}.\n */\n fbp?: string;\n\n /**\n * Subscription ID: Do not hash.\n * The subscription ID for the user in this transaction; it is similar to the order ID for an\n * individual product.\n */\n subscription_id?: string;\n\n /**\n * Facebook Login ID: Do not hash.\n * The ID issued by Meta when a person first logs into an instance of an app. This is also known\n * as App-Scoped ID.\n */\n fb_login_id?: number;\n\n /**\n * Lead ID: Do not hash.\n * The ID associated with a lead generated by [Meta's Lead Ads](https://developers.facebook.com/docs/marketing-api/guides/lead-ads).\n */\n lead_id?: number;\n\n /**\n * Install ID: Do not hash.\n * Your install ID. This field represents unique application installation instances.\n * Note: This parameter is for app events only.\n */\n anon_id?: string;\n\n /**\n * Your mobile advertiser ID, the advertising ID from an Android device or the Advertising\n * Identifier (IDFA) from an Apple device.\n */\n madid?: string;\n\n /**\n * Page ID: Do not hash.\n * Your Page ID. Specifies the page ID associated with the event. Use the Facebook page ID of the\n * page associated with the bot.\n */\n page_id?: string;\n\n /**\n * Page Scoped User ID: Do not hash.\n * Specifies the page-scoped user ID associated with the messenger bot that logs the event. Use\n * the page-scoped user ID provided to your webhook.\n */\n page_scoped_user_id?: string;\n\n /**\n * Do not hash.\n * Click ID generated by Meta for ads that click to WhatsApp.\n */\n ctwa_clid?: string;\n\n /**\n * Do not hash.\n * Instagram Account ID that is associated with the business.\n */\n ig_account_id?: string;\n\n /**\n * Do not hash.\n * Users who interact with Instagram are identified by Instagram-Scoped User IDs (IGSID). IGSID\n * can be obtained from this webhook.\n */\n ig_sid?: string;\n};\n\n/**\n * You can include the following predefined object properties with any custom events, and any\n * standard events that support them. Format your parameter object data using JSON. Learn more about\n * event parameters with Blueprint.\n */\nexport type ObjectProperties = {\n content_category?: string;\n content_ids?: string[];\n content_name?: string;\n\n /**\n * Either product or product_group based on the content_ids or contents being passed. If the IDs\n * being passed in content_ids or contents parameter are IDs of products, then the value should be\n * product. If product group IDs are being passed, then the value should be product_group.\n *\n * If no content_type is provided, Meta will match the event to every item that has the same ID,\n * independent of its type.\n */\n content_type?: 'product' | 'product_group' | (string & {});\n contents?: Content[];\n delivery_category?: 'in_store' | 'curbside' | 'home_delivery';\n currency?: string;\n num_items?: number;\n predicted_ltv?: number;\n search_string?: string;\n\n /** Used with the CompleteRegistration event, to show the status of the registration. */\n status?: boolean;\n value?: number;\n};\n\n/**\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/payload-helper\n */\nexport type StandardEvents = {\n AddPaymentInfo: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n value?: number;\n };\n AddToCart: {\n content_ids?: string[];\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents\n currency?: string;\n value?: number;\n };\n AddToWishlist: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n value?: number;\n };\n CompleteRegistration: {\n currency?: string;\n value?: number;\n method?: string;\n };\n Contact: {};\n CustomizeProduct: {};\n Donate: {};\n FindLocation: {};\n InitiateCheckout: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n num_items?: number;\n value?: number;\n };\n Lead: {\n currency?: string;\n value?: number;\n };\n Purchase: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency: string; // required\n num_items?: number;\n value: number; // required\n };\n Schedule: {};\n Search: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency?: string;\n search_string?: string;\n value?: number;\n };\n StartTrial: {\n currency?: string;\n predicted_ltv?: number;\n value?: number;\n };\n SubmitApplication: {};\n Subscribe: {\n currency?: string;\n predicted_ltv?: number;\n value?: number;\n };\n ViewContent: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency?: string;\n value?: number;\n };\n};\n\ntype JSONValue =\n | null\n | string\n | number\n | boolean\n | Array<JSONValue>\n | { [value: string]: JSONValue };\n\nexport type PixelId = `${number}`;\nexport type Options = { eventID?: string };\n\n/**\n * reference: https://developers.facebook.com/docs/meta-pixel/reference#standard-events\n *\n * We determine if events are identical based on their ID and name. So, for an event to be deduplicated:\n * - In corresponding events, a Meta Pixel's eventID must match the Conversion API's event_id.\n * - In corresponding events, a Meta Pixel's event must match the Conversion API's event_name.\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nexport interface FBQ {\n /**\n * reference: https://stackoverflow.com/questions/62304291/sending-user-data-parameters-via-pixel\n *\n * Call init the normal default way first:\n * `fbq('init', 'XXXXX')`\n *\n * And at a later point in time, when you have obtained additional user data, you can call init\n * again basically enriching the already running fbq instance with additional data:\n * `fbq('init', 'XXXXX', { external_id: 1234, em: 'abc@abc.com' } )`\n *\n * Only caveat is that you have to send an event after this additional init call, otherwise the\n * provided data will not be sent to Facebook.\n */\n fbq(type: 'init', pixelId: PixelId, parameters?: MatchingParameters): void;\n\n /** Enable Manual Only mode. (value = false) */\n fbq(type: 'set', key: 'autoConfig', value: boolean, pixelId: PixelId): void;\n\n fbq<T extends keyof StandardEvents>(\n type: 'track',\n event: T,\n properties?: StandardEvents[T] & ObjectProperties,\n options?: Options\n ): void;\n\n fbq(\n type: 'trackCustom',\n event: string,\n properties?: Record<string, JSONValue> & ObjectProperties,\n options?: Options\n ): void;\n\n /** https://developers.facebook.com/docs/meta-pixel/guides/track-multiple-events/ */\n fbq<T extends keyof StandardEvents>(\n type: 'trackSingle',\n pixelId: PixelId,\n event: T,\n properties?: StandardEvents[T] & ObjectProperties,\n options?: Options\n ): void;\n\n /** https://developers.facebook.com/docs/meta-pixel/guides/track-multiple-events/ */\n fbq(\n type: 'trackSingleCustom',\n pixelId: PixelId,\n event: string,\n properties?: Record<string, JSONValue> & ObjectProperties,\n options?: Options\n ): void;\n}\n\n/**\n * Please download this CSV filefor examples of properly normalized and hashed data for the\n * parameters below.\n */\nexport function normalize(parameters: MatchingParameters): MatchingParameters {\n return {\n ...parameters,\n em: parameters.em?.toLowerCase().trim(),\n ph: parameters.ph?.replace(/[-+()\\s]/g, '').replace(/^0+/, ''),\n zp: parameters.zp?.split('-').at(0)?.trim(),\n fn: parameters.fn?.toLowerCase().trim(),\n ln: parameters.ln?.toLowerCase().trim(),\n ct: parameters.ct?.toLowerCase().replace(/[\\s/-]/g, ''),\n st: parameters.st?.toLowerCase().replace(/[\\s/,.-]/g, ''),\n country: parameters.country?.toLowerCase().replace(/[\\s/-]/g, ''),\n };\n}\n\nexport function mapItems(items?: Item[]): ObjectProperties {\n if (!items) return {};\n const categories = Array.from(new Set(items.map((i) => i.item_category).filter(Boolean)));\n const contents: Content[] = items.map(\n ({ item_id, quantity, price, item_name, item_brand, item_category, ..._others }) => ({\n id: item_id,\n quantity: quantity ?? 1,\n item_price: price,\n title: item_name,\n brand: item_brand,\n category: item_category,\n })\n );\n\n return {\n content_category: categories.length === 1 ? categories.at(0) : undefined,\n contents,\n content_ids: contents.map((c) => c.id),\n num_items: items.reduce((acc, i) => acc + (i.quantity ?? 1), 0),\n };\n}\n\ntype Mapped<F extends keyof StandardEvents> = ['track', F, StandardEvents[F] & ObjectProperties];\ntype Missed<F extends string> = ['trackCustom', F, Record<string, JSONValue> & ObjectProperties];\n\nexport function mapFBEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n): Mapped<keyof StandardEvents> | Missed<TrackName<T>> {\n if (name === 'add_payment_info') {\n const p = properties as TrackProperties<'add_payment_info'> | undefined;\n return [\n 'track',\n 'AddPaymentInfo',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'add_to_cart') {\n const p = properties as TrackProperties<'add_to_cart'> | undefined;\n return [\n 'track',\n 'AddToCart',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'add_to_wishlist') {\n const p = properties as TrackProperties<'add_to_wishlist'> | undefined;\n return [\n 'track',\n 'AddToWishlist',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'login') {\n const p = properties as TrackProperties<'login'> | undefined;\n return ['track', 'CompleteRegistration', { method: p?.method }];\n } else if (name === 'contact') {\n return ['track', 'Contact', {}];\n } else if (name === 'customize_product') {\n return ['track', 'CustomizeProduct', {}];\n } else if (name === 'donate') {\n return ['track', 'Donate', {}];\n } else if (name === 'find_location') {\n return ['track', 'FindLocation', {}];\n } else if (name === 'begin_checkout') {\n const p = properties as TrackProperties<'begin_checkout'> | undefined;\n return [\n 'track',\n 'InitiateCheckout',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'generate_lead') {\n const p = properties as TrackProperties<'generate_lead'> | undefined;\n return ['track', 'Lead', { currency: p?.currency, value: p?.value }];\n } else if (name === 'purchase') {\n const p = properties as TrackProperties<'purchase'> | undefined;\n return [\n 'track',\n 'Purchase',\n { currency: p?.currency ?? 'USD', value: p?.value ?? 0, ...mapItems(p?.items) },\n ];\n } else if (name === 'schedule') {\n return ['track', 'Schedule', {}];\n } else if (name === 'search') {\n const p = properties as TrackProperties<'search'> | undefined;\n return ['track', 'Search', { search_string: p?.search_term }];\n } else if (name === 'trial_begin') {\n const p = properties as TrackProperties<'trial_begin'> | undefined;\n return ['track', 'StartTrial', { currency: p?.currency, value: p?.value }];\n } else if (name === 'submit_application') {\n return ['track', 'SubmitApplication', {}];\n } else if (name === 'subscribe') {\n const p = properties as TrackProperties<'subscribe'> | undefined;\n return ['track', 'Subscribe', { currency: p?.currency, value: p?.value }];\n } else if (name === 'view_item') {\n const p = properties as TrackProperties<'view_item'> | undefined;\n return [\n 'track',\n 'ViewContent',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else {\n return ['trackCustom', name, properties ?? {}];\n }\n}\n"],"mappings":"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"}
|
package/dist/track/index.cjs
CHANGED
|
@@ -6,7 +6,7 @@ const require_third_parties_ignored_events = require("../third-parties/ignored-e
|
|
|
6
6
|
const require_visitor_index = require("../visitor/index.cjs");
|
|
7
7
|
let _shware_utils = require("@shware/utils");
|
|
8
8
|
//#region src/track/index.ts
|
|
9
|
-
const defaultOptions = {
|
|
9
|
+
const defaultOptions = {};
|
|
10
10
|
let tokenBucket;
|
|
11
11
|
/**
|
|
12
12
|
* The rate limiter, built on the first send rather than at module scope: its
|
|
@@ -65,6 +65,7 @@ async function sendEvents(events) {
|
|
|
65
65
|
const response = await (0, _shware_utils.fetch)(`${require_setup_index.config.endpoint}/events`, {
|
|
66
66
|
method: "POST",
|
|
67
67
|
credentials: "include",
|
|
68
|
+
keepalive: true,
|
|
68
69
|
headers: await require_setup_index.config.getHeaders(),
|
|
69
70
|
body: JSON.stringify(dto)
|
|
70
71
|
});
|
|
@@ -81,7 +82,7 @@ async function sendEvents(events) {
|
|
|
81
82
|
const eventId = data.at(index)?.id;
|
|
82
83
|
options.onSucceed?.(eventId ? { id: eventId } : void 0);
|
|
83
84
|
index++;
|
|
84
|
-
if (
|
|
85
|
+
if (options.enableThirdPartyTracking === false || require_third_parties_ignored_events.IGNORED_EVENTS.includes(name)) continue;
|
|
85
86
|
require_setup_index.config.thirdPartyTrackers.forEach((tracker) => {
|
|
86
87
|
try {
|
|
87
88
|
tracker(name, properties, eventId);
|
package/dist/track/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["TokenBucket","config","cache","getSession","getVisitor","IGNORED_EVENTS","keys"],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { keys } from '../constants/storage';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties, TrackTags } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\n\nlet tokenBucket: TokenBucket | undefined;\n\n/**\n * The rate limiter, built on the first send rather than at module scope: its\n * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to\n * set a timer outside a request handler for the same reason they refuse to\n * generate random values there — see `setup/session.ts`.\n */\nfunction getTokenBucket() {\n return (tokenBucket ??= new TokenBucket({ rate: 1, capacity: 20, requested: 2 }));\n}\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n tags: Promise<TrackTags>;\n timestamp: string;\n options: TrackOptions;\n};\n\n/**\n * Tags belong to the moment the event happened, not to the moment its batch goes out: a queued\n * event waits up to `delay` ms, and a single page app can navigate in that window, which would\n * stamp every pending event with the URL of the page the user has already left.\n *\n * The promise then sits in the queue with nothing awaiting it, so a failure has to be absorbed\n * here — an unhandled rejection would surface as a global error long before `sendEvents` could\n * catch it. Falling back to the last built tags keeps the event, minus whatever changed since.\n */\nasync function captureTags(): Promise<TrackTags> {\n try {\n return await config.getTags();\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n return cache.tags ?? {};\n }\n}\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n // One read-modify-write of the stored session for the whole batch: it answers which session\n // these events belong to and whether this batch is the one that started it. Timed by the\n // events themselves rather than by this moment — a tab frozen in the background can hold a\n // batch for far longer than `delay`, and those events belong to the session they happened in.\n const firstTimestamp = events[0].timestamp;\n const { id: session_id, started } = getSession().touch(\n Date.parse(firstTimestamp),\n Date.parse(events[events.length - 1].timestamp)\n );\n if (started) {\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n tags: captureTags(),\n // The session began with the event that opened it, not at this moment: a batch held in a\n // frozen tab would otherwise announce its session later than the events inside it.\n timestamp: firstTimestamp,\n });\n }\n\n await getTokenBucket().removeTokens();\n\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = await Promise.all(\n events.map(async (event) => ({\n name: event.name,\n properties: event.properties,\n tags: await event.tags,\n visitor_id,\n session_id,\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }))\n );\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data.at(index)?.id;\n options.onSucceed?.(eventId ? { id: eventId } : undefined);\n index++;\n if (!options.enableThirdPartyTracking || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => {\n try {\n tracker(name, properties, eventId);\n } catch (e: unknown) {\n // A third-party script does not get to take the rest of the batch with it. This loop is\n // still draining `events` with `shift`, so a throw would escape to the catch below and\n // report failure to whatever is left in the queue — for events the server has already\n // accepted, and after the ones ahead of them were told they succeeded.\n if (e instanceof Error) console.log(e.message);\n }\n });\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Both paths into a send go through here, so a batch that fills up cancels the timer the\n * previous push armed rather than leaving it to wake up on its own with nothing to send.\n */\nfunction flush() {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n if (list.length === 0) return;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n}\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({\n name,\n properties,\n options,\n tags: captureTags(),\n timestamp: new Date().toISOString(),\n });\n if (list.length >= batch) {\n flush();\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([\n { name, properties, options, tags: captureTags(), timestamp: new Date().toISOString() },\n ]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n // The visitor id is persisted, so a returning visitor already has one before `getVisitor` has\n // finished its round trip for this page. Requiring the in-memory copy threw away exactly the\n // events this function exists for: everything a visit accrues before its first batch comes\n // back, which for a short visit is the whole of it.\n const stored = config.storage.getItem(keys.visitor_id);\n const visitor_id = cache.visitor?.id ?? (stored && stored !== 'undefined' ? stored : undefined);\n if (!visitor_id) return;\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n // Tags are worth less than the event carrying them: an empty set still reports the\n // engagement, and every field in `tagsSchema` is optional.\n tags: cache.tags ?? {},\n visitor_id,\n session_id: getSession().extend(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["TokenBucket","config","cache","getSession","getVisitor","IGNORED_EVENTS","keys"],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { keys } from '../constants/storage';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties, TrackTags } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = {};\n\nlet tokenBucket: TokenBucket | undefined;\n\n/**\n * The rate limiter, built on the first send rather than at module scope: its\n * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to\n * set a timer outside a request handler for the same reason they refuse to\n * generate random values there — see `setup/session.ts`.\n */\nfunction getTokenBucket() {\n return (tokenBucket ??= new TokenBucket({ rate: 1, capacity: 20, requested: 2 }));\n}\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n tags: Promise<TrackTags>;\n timestamp: string;\n options: TrackOptions;\n};\n\n/**\n * Tags belong to the moment the event happened, not to the moment its batch goes out: a queued\n * event waits up to `delay` ms, and a single page app can navigate in that window, which would\n * stamp every pending event with the URL of the page the user has already left.\n *\n * The promise then sits in the queue with nothing awaiting it, so a failure has to be absorbed\n * here — an unhandled rejection would surface as a global error long before `sendEvents` could\n * catch it. Falling back to the last built tags keeps the event, minus whatever changed since.\n */\nasync function captureTags(): Promise<TrackTags> {\n try {\n return await config.getTags();\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n return cache.tags ?? {};\n }\n}\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n // One read-modify-write of the stored session for the whole batch: it answers which session\n // these events belong to and whether this batch is the one that started it. Timed by the\n // events themselves rather than by this moment — a tab frozen in the background can hold a\n // batch for far longer than `delay`, and those events belong to the session they happened in.\n const firstTimestamp = events[0].timestamp;\n const { id: session_id, started } = getSession().touch(\n Date.parse(firstTimestamp),\n Date.parse(events[events.length - 1].timestamp)\n );\n if (started) {\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n tags: captureTags(),\n // The session began with the event that opened it, not at this moment: a batch held in a\n // frozen tab would otherwise announce its session later than the events inside it.\n timestamp: firstTimestamp,\n });\n }\n\n await getTokenBucket().removeTokens();\n\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = await Promise.all(\n events.map(async (event) => ({\n name: event.name,\n properties: event.properties,\n tags: await event.tags,\n visitor_id,\n session_id,\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }))\n );\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n // Survive the page being unloaded mid-flight: a batch waits up to `delay` ms, so closing\n // the tab inside that window would otherwise abort the request and lose every event in it.\n // The body stays far under keepalive's 64KB in-flight budget at 100 events per batch.\n keepalive: true,\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data.at(index)?.id;\n options.onSucceed?.(eventId ? { id: eventId } : undefined);\n index++;\n // An explicit false, not falsiness: a caller passing `{ onSucceed }` replaces the options\n // object wholesale, and leaving the flag out must not silently switch forwarding off.\n if (options.enableThirdPartyTracking === false || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => {\n try {\n tracker(name, properties, eventId);\n } catch (e: unknown) {\n // A third-party script does not get to take the rest of the batch with it. This loop is\n // still draining `events` with `shift`, so a throw would escape to the catch below and\n // report failure to whatever is left in the queue — for events the server has already\n // accepted, and after the ones ahead of them were told they succeeded.\n if (e instanceof Error) console.log(e.message);\n }\n });\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Both paths into a send go through here, so a batch that fills up cancels the timer the\n * previous push armed rather than leaving it to wake up on its own with nothing to send.\n */\nfunction flush() {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n if (list.length === 0) return;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n}\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({\n name,\n properties,\n options,\n tags: captureTags(),\n timestamp: new Date().toISOString(),\n });\n if (list.length >= batch) {\n flush();\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([\n { name, properties, options, tags: captureTags(), timestamp: new Date().toISOString() },\n ]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n // The visitor id is persisted, so a returning visitor already has one before `getVisitor` has\n // finished its round trip for this page. Requiring the in-memory copy threw away exactly the\n // events this function exists for: everything a visit accrues before its first batch comes\n // back, which for a short visit is the whole of it.\n const stored = config.storage.getItem(keys.visitor_id);\n const visitor_id = cache.visitor?.id ?? (stored && stored !== 'undefined' ? stored : undefined);\n if (!visitor_id) return;\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n // Tags are worth less than the event carrying them: an empty set still reports the\n // engagement, and every field in `tagsSchema` is optional.\n tags: cache.tags ?? {},\n visitor_id,\n session_id: getSession().extend(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/track/index.ts"],"mappings":";;UASiB;EACf;EACA,aAAa,WAAW;EACxB,WAAW;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/track/index.ts"],"mappings":";;UASiB;EACf;EACA,aAAa,WAAW;EACxB,WAAW;;iBA8JG,MAAM,UAAU,YAAY,WAC1C,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS;iBAiBW,WAAW,UAAU,YAAY,WACrD,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS,eAA6B;iBAOxB,WAAW,UAAU,YAAY,WAC/C,MAAM,UAAU,IAChB,aAAa,gBAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/track/index.ts"],"mappings":";;UASiB;EACf;EACA,aAAa,WAAW;EACxB,WAAW;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/track/index.ts"],"mappings":";;UASiB;EACf;EACA,aAAa,WAAW;EACxB,WAAW;;iBA8JG,MAAM,UAAU,YAAY,WAC1C,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS;iBAiBW,WAAW,UAAU,YAAY,WACrD,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS,eAA6B;iBAOxB,WAAW,UAAU,YAAY,WAC/C,MAAM,UAAU,IAChB,aAAa,gBAAgB"}
|
package/dist/track/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { IGNORED_EVENTS } from "../third-parties/ignored-events.mjs";
|
|
|
5
5
|
import { getVisitor } from "../visitor/index.mjs";
|
|
6
6
|
import { TokenBucket, fetch } from "@shware/utils";
|
|
7
7
|
//#region src/track/index.ts
|
|
8
|
-
const defaultOptions = {
|
|
8
|
+
const defaultOptions = {};
|
|
9
9
|
let tokenBucket;
|
|
10
10
|
/**
|
|
11
11
|
* The rate limiter, built on the first send rather than at module scope: its
|
|
@@ -64,6 +64,7 @@ async function sendEvents(events) {
|
|
|
64
64
|
const response = await fetch(`${config.endpoint}/events`, {
|
|
65
65
|
method: "POST",
|
|
66
66
|
credentials: "include",
|
|
67
|
+
keepalive: true,
|
|
67
68
|
headers: await config.getHeaders(),
|
|
68
69
|
body: JSON.stringify(dto)
|
|
69
70
|
});
|
|
@@ -80,7 +81,7 @@ async function sendEvents(events) {
|
|
|
80
81
|
const eventId = data.at(index)?.id;
|
|
81
82
|
options.onSucceed?.(eventId ? { id: eventId } : void 0);
|
|
82
83
|
index++;
|
|
83
|
-
if (
|
|
84
|
+
if (options.enableThirdPartyTracking === false || IGNORED_EVENTS.includes(name)) continue;
|
|
84
85
|
config.thirdPartyTrackers.forEach((tracker) => {
|
|
85
86
|
try {
|
|
86
87
|
tracker(name, properties, eventId);
|
package/dist/track/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { keys } from '../constants/storage';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties, TrackTags } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\n\nlet tokenBucket: TokenBucket | undefined;\n\n/**\n * The rate limiter, built on the first send rather than at module scope: its\n * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to\n * set a timer outside a request handler for the same reason they refuse to\n * generate random values there — see `setup/session.ts`.\n */\nfunction getTokenBucket() {\n return (tokenBucket ??= new TokenBucket({ rate: 1, capacity: 20, requested: 2 }));\n}\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n tags: Promise<TrackTags>;\n timestamp: string;\n options: TrackOptions;\n};\n\n/**\n * Tags belong to the moment the event happened, not to the moment its batch goes out: a queued\n * event waits up to `delay` ms, and a single page app can navigate in that window, which would\n * stamp every pending event with the URL of the page the user has already left.\n *\n * The promise then sits in the queue with nothing awaiting it, so a failure has to be absorbed\n * here — an unhandled rejection would surface as a global error long before `sendEvents` could\n * catch it. Falling back to the last built tags keeps the event, minus whatever changed since.\n */\nasync function captureTags(): Promise<TrackTags> {\n try {\n return await config.getTags();\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n return cache.tags ?? {};\n }\n}\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n // One read-modify-write of the stored session for the whole batch: it answers which session\n // these events belong to and whether this batch is the one that started it. Timed by the\n // events themselves rather than by this moment — a tab frozen in the background can hold a\n // batch for far longer than `delay`, and those events belong to the session they happened in.\n const firstTimestamp = events[0].timestamp;\n const { id: session_id, started } = getSession().touch(\n Date.parse(firstTimestamp),\n Date.parse(events[events.length - 1].timestamp)\n );\n if (started) {\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n tags: captureTags(),\n // The session began with the event that opened it, not at this moment: a batch held in a\n // frozen tab would otherwise announce its session later than the events inside it.\n timestamp: firstTimestamp,\n });\n }\n\n await getTokenBucket().removeTokens();\n\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = await Promise.all(\n events.map(async (event) => ({\n name: event.name,\n properties: event.properties,\n tags: await event.tags,\n visitor_id,\n session_id,\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }))\n );\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data.at(index)?.id;\n options.onSucceed?.(eventId ? { id: eventId } : undefined);\n index++;\n if (!options.enableThirdPartyTracking || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => {\n try {\n tracker(name, properties, eventId);\n } catch (e: unknown) {\n // A third-party script does not get to take the rest of the batch with it. This loop is\n // still draining `events` with `shift`, so a throw would escape to the catch below and\n // report failure to whatever is left in the queue — for events the server has already\n // accepted, and after the ones ahead of them were told they succeeded.\n if (e instanceof Error) console.log(e.message);\n }\n });\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Both paths into a send go through here, so a batch that fills up cancels the timer the\n * previous push armed rather than leaving it to wake up on its own with nothing to send.\n */\nfunction flush() {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n if (list.length === 0) return;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n}\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({\n name,\n properties,\n options,\n tags: captureTags(),\n timestamp: new Date().toISOString(),\n });\n if (list.length >= batch) {\n flush();\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([\n { name, properties, options, tags: captureTags(), timestamp: new Date().toISOString() },\n ]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n // The visitor id is persisted, so a returning visitor already has one before `getVisitor` has\n // finished its round trip for this page. Requiring the in-memory copy threw away exactly the\n // events this function exists for: everything a visit accrues before its first batch comes\n // back, which for a short visit is the whole of it.\n const stored = config.storage.getItem(keys.visitor_id);\n const visitor_id = cache.visitor?.id ?? (stored && stored !== 'undefined' ? stored : undefined);\n if (!visitor_id) return;\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n // Tags are worth less than the event carrying them: an empty set still reports the\n // engagement, and every field in `tagsSchema` is optional.\n tags: cache.tags ?? {},\n visitor_id,\n session_id: getSession().extend(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { keys } from '../constants/storage';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties, TrackTags } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = {};\n\nlet tokenBucket: TokenBucket | undefined;\n\n/**\n * The rate limiter, built on the first send rather than at module scope: its\n * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to\n * set a timer outside a request handler for the same reason they refuse to\n * generate random values there — see `setup/session.ts`.\n */\nfunction getTokenBucket() {\n return (tokenBucket ??= new TokenBucket({ rate: 1, capacity: 20, requested: 2 }));\n}\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n tags: Promise<TrackTags>;\n timestamp: string;\n options: TrackOptions;\n};\n\n/**\n * Tags belong to the moment the event happened, not to the moment its batch goes out: a queued\n * event waits up to `delay` ms, and a single page app can navigate in that window, which would\n * stamp every pending event with the URL of the page the user has already left.\n *\n * The promise then sits in the queue with nothing awaiting it, so a failure has to be absorbed\n * here — an unhandled rejection would surface as a global error long before `sendEvents` could\n * catch it. Falling back to the last built tags keeps the event, minus whatever changed since.\n */\nasync function captureTags(): Promise<TrackTags> {\n try {\n return await config.getTags();\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n return cache.tags ?? {};\n }\n}\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n // One read-modify-write of the stored session for the whole batch: it answers which session\n // these events belong to and whether this batch is the one that started it. Timed by the\n // events themselves rather than by this moment — a tab frozen in the background can hold a\n // batch for far longer than `delay`, and those events belong to the session they happened in.\n const firstTimestamp = events[0].timestamp;\n const { id: session_id, started } = getSession().touch(\n Date.parse(firstTimestamp),\n Date.parse(events[events.length - 1].timestamp)\n );\n if (started) {\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n tags: captureTags(),\n // The session began with the event that opened it, not at this moment: a batch held in a\n // frozen tab would otherwise announce its session later than the events inside it.\n timestamp: firstTimestamp,\n });\n }\n\n await getTokenBucket().removeTokens();\n\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = await Promise.all(\n events.map(async (event) => ({\n name: event.name,\n properties: event.properties,\n tags: await event.tags,\n visitor_id,\n session_id,\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }))\n );\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n // Survive the page being unloaded mid-flight: a batch waits up to `delay` ms, so closing\n // the tab inside that window would otherwise abort the request and lose every event in it.\n // The body stays far under keepalive's 64KB in-flight budget at 100 events per batch.\n keepalive: true,\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data.at(index)?.id;\n options.onSucceed?.(eventId ? { id: eventId } : undefined);\n index++;\n // An explicit false, not falsiness: a caller passing `{ onSucceed }` replaces the options\n // object wholesale, and leaving the flag out must not silently switch forwarding off.\n if (options.enableThirdPartyTracking === false || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => {\n try {\n tracker(name, properties, eventId);\n } catch (e: unknown) {\n // A third-party script does not get to take the rest of the batch with it. This loop is\n // still draining `events` with `shift`, so a throw would escape to the catch below and\n // report failure to whatever is left in the queue — for events the server has already\n // accepted, and after the ones ahead of them were told they succeeded.\n if (e instanceof Error) console.log(e.message);\n }\n });\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Both paths into a send go through here, so a batch that fills up cancels the timer the\n * previous push armed rather than leaving it to wake up on its own with nothing to send.\n */\nfunction flush() {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n if (list.length === 0) return;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n}\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({\n name,\n properties,\n options,\n tags: captureTags(),\n timestamp: new Date().toISOString(),\n });\n if (list.length >= batch) {\n flush();\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([\n { name, properties, options, tags: captureTags(), timestamp: new Date().toISOString() },\n ]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n // The visitor id is persisted, so a returning visitor already has one before `getVisitor` has\n // finished its round trip for this page. Requiring the in-memory copy threw away exactly the\n // events this function exists for: everything a visit accrues before its first batch comes\n // back, which for a short visit is the whole of it.\n const stored = config.storage.getItem(keys.visitor_id);\n const visitor_id = cache.visitor?.id ?? (stored && stored !== 'undefined' ? stored : undefined);\n if (!visitor_id) return;\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n // Tags are worth less than the event carrying them: an empty set still reports the\n // engagement, and every field in `tagsSchema` is optional.\n tags: cache.tags ?? {},\n visitor_id,\n session_id: getSession().extend(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
|
package/dist/track/types.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.cts","names":[],"sources":["../../src/track/types.ts"],"mappings":";;KAEY;KACA,YAAY;KAEZ,UAAU,UAAU,YAAY,aAAa,gBAAgB,iBACrE,IACA;KACQ,gBAAgB,UAAU,YAAY,aAAa,gBAAgB,iBAC3E,eAAe,KACf,OAAO,mBAAmB;KAElB;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;IAAa;IAAc;IAAe;;EAE1C;EACA;;KAGU,qBAAqB,UAAU,WACzC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;EACA;;UAGe;EACf;EACA;EACA;;;;;;;;;;UAWe;EACf;EACA;EACA;;UAGe;;;;;;;EAOf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;;EAEf;;;;;;;;;;;;EAqCA;;;;;;EAuDA;;EAMA;;EAEA;;EAEA;;EAEA;;EAUA;;EAEA;;UAGe,kBACP,cAAc,YAAY,SAAS,iBAAiB,UAAU,iBAAiB;EACvF;GACC;;UAGc,WAAW,UAAU,YAAY;EAChD;EACA,MAAM,UAAU;EAChB,MAAM;EACN;EACA;EACA,UAAU;EACV,aAAa;EACb,aAAa,gBAAgB;EAC7B;;KAGU;;EAEV"}
|
|
1
|
+
{"version":3,"file":"types.d.cts","names":[],"sources":["../../src/track/types.ts"],"mappings":";;KAEY;KACA,YAAY;KAEZ,UAAU,UAAU,YAAY,aAAa,gBAAgB,iBACrE,IACA;KACQ,gBAAgB,UAAU,YAAY,aAAa,gBAAgB,iBAC3E,eAAe,KACf,OAAO,mBAAmB;KAElB;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;IAAa;IAAc;IAAe;;EAE1C;EACA;;KAGU,qBAAqB,UAAU,WACzC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;EACA;;UAGe;EACf;EACA;EACA;;;;;;;;;;UAWe;EACf;EACA;EACA;;UAGe;;;;;;;EAOf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;;EAEf;;;;;;;;;;;;EAqCA;;;;;;EAuDA;;EAMA;;EAEA;;EAEA;;EAEA;;EAUA;;EAEA;;UAGe,kBACP,cAAc,YAAY,SAAS,iBAAiB,UAAU,iBAAiB;EACvF;GACC;;UAGc,WAAW,UAAU,YAAY;EAChD;EACA,MAAM,UAAU;EAChB,MAAM;EACN;EACA;EACA,UAAU;EACV,aAAa;EACb,aAAa,gBAAgB;EAC7B;;KAGU;;EAEV"}
|
package/dist/track/types.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/track/types.ts"],"mappings":";;KAEY;KACA,YAAY;KAEZ,UAAU,UAAU,YAAY,aAAa,gBAAgB,iBACrE,IACA;KACQ,gBAAgB,UAAU,YAAY,aAAa,gBAAgB,iBAC3E,eAAe,KACf,OAAO,mBAAmB;KAElB;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;IAAa;IAAc;IAAe;;EAE1C;EACA;;KAGU,qBAAqB,UAAU,WACzC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;EACA;;UAGe;EACf;EACA;EACA;;;;;;;;;;UAWe;EACf;EACA;EACA;;UAGe;;;;;;;EAOf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;;EAEf;;;;;;;;;;;;EAqCA;;;;;;EAuDA;;EAMA;;EAEA;;EAEA;;EAEA;;EAUA;;EAEA;;UAGe,kBACP,cAAc,YAAY,SAAS,iBAAiB,UAAU,iBAAiB;EACvF;GACC;;UAGc,WAAW,UAAU,YAAY;EAChD;EACA,MAAM,UAAU;EAChB,MAAM;EACN;EACA;EACA,UAAU;EACV,aAAa;EACb,aAAa,gBAAgB;EAC7B;;KAGU;;EAEV"}
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/track/types.ts"],"mappings":";;KAEY;KACA,YAAY;KAEZ,UAAU,UAAU,YAAY,aAAa,gBAAgB,iBACrE,IACA;KACQ,gBAAgB,UAAU,YAAY,aAAa,gBAAgB,iBAC3E,eAAe,KACf,OAAO,mBAAmB;KAElB;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;IAAa;IAAc;IAAe;;EAE1C;EACA;;KAGU,qBAAqB,UAAU,WACzC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;EACA;;UAGe;EACf;EACA;EACA;;;;;;;;;;UAWe;EACf;EACA;EACA;;UAGe;;;;;;;EAOf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;;EAEf;;;;;;;;;;;;EAqCA;;;;;;EAuDA;;EAMA;;EAEA;;EAEA;;EAEA;;EAUA;;EAEA;;UAGe,kBACP,cAAc,YAAY,SAAS,iBAAiB,UAAU,iBAAiB;EACvF;GACC;;UAGc,WAAW,UAAU,YAAY;EAChD;EACA,MAAM,UAAU;EAChB,MAAM;EACN;EACA;EACA,UAAU;EACV,aAAa;EACb,aAAa,gBAAgB;EAC7B;;KAGU;;EAEV"}
|
package/dist/web/index.cjs
CHANGED
|
@@ -91,6 +91,7 @@ async function getTags() {
|
|
|
91
91
|
ttclid: params.get("ttclid") ?? void 0,
|
|
92
92
|
twclid: params.get("twclid") ?? void 0,
|
|
93
93
|
wbraid: params.get("wbraid") ?? void 0,
|
|
94
|
+
gbraid: params.get("gbraid") ?? void 0,
|
|
94
95
|
yclid: params.get("yclid") ?? void 0,
|
|
95
96
|
utm_source: link?.utm_source ?? params.get("utm_source") ?? void 0,
|
|
96
97
|
utm_medium: link?.utm_medium ?? params.get("utm_medium") ?? void 0,
|
package/dist/web/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["config","keys","getLink","Bowser"],"sources":["../../src/web/index.ts"],"sourcesContent":["import Bowser from 'bowser';\nimport { parseCookie } from 'cookie';\nimport { v4 as uuidv4 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { type Link, getLink } from '../link/index';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\nexport function getDeviceId() {\n const cached = config.storage.getItem(keys.device_id);\n if (cached) return cached;\n // lib.dom types `crypto.randomUUID` as always present, but it is missing in\n // insecure contexts and older browsers, so probe it before use.\n const id = (crypto as Partial<Crypto>).randomUUID ? crypto.randomUUID() : uuidv4();\n config.storage.setItem(keys.device_id, id);\n return id;\n}\n\nconst links = new Map<string, Promise<Link | null>>();\n\n/**\n * `getTags` runs once per event now, and the link a `?s=` id points at cannot change while the\n * page is open, so an uncached lookup would put an identical request on the wire for every\n * event the page sends.\n */\nfunction getCachedLink(id: string) {\n const cached = links.get(id);\n if (cached) return cached;\n const link = getLink(id).then((result) => {\n // A lookup that came back empty must not stick for the lifetime of the page. `getLink`\n // answers null for a network failure as well as for a link that does not exist, and losing\n // the link's utm params for every later event costs more than repeating a rare request.\n if (!result) links.delete(id);\n return result;\n });\n links.set(id, link);\n return link;\n}\n\n/** The user agent cannot change while the page is open, so it is parsed once. */\nlet parser: ReturnType<typeof Bowser.getParser> | undefined;\n\nexport async function getTags() {\n // Read the page before the first await: `getTags` runs when the event happens, and a single\n // page app can navigate while the link lookup below is still in flight.\n const page_location = window.location.href;\n const page_referrer = document.referrer || undefined;\n const page_title = document.title;\n\n parser ??= Bowser.getParser(window.navigator.userAgent);\n const params = new URLSearchParams(window.location.search);\n const os = parser.getOS();\n const browser = parser.getBrowser();\n const platform = parser.getPlatform();\n const parsed = parseCookie(document.cookie);\n\n const linkId = params.get('s');\n const link = linkId ? await getCachedLink(linkId) : null;\n\n const tags: TrackTags = {\n os: `${os.name} ${os.version}`,\n os_name: os.name,\n os_version: os.version,\n browser: `${browser.name} ${browser.version}`,\n browser_name: browser.name,\n browser_version: browser.version,\n device: platform.model,\n device_id: getDeviceId(),\n device_type: platform.type,\n device_vendor: platform.vendor,\n device_pixel_ratio: window.devicePixelRatio,\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n release: config.release,\n language: navigator.language,\n time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n page_location,\n page_referrer,\n page_title,\n // Meta Ads — _fbc is set server-side (see @shware/analytics/server resolveClickIdCookies)\n fbc: parsed._fbc ?? undefined,\n fbp: parsed._fbp,\n fbclid: params.get('fbclid') ?? undefined,\n ad_id: params.get('ad_id') ?? undefined,\n ad_name: params.get('ad_name') ?? undefined,\n adset_id: params.get('adset_id') ?? undefined,\n adset_name: params.get('adset_name') ?? undefined,\n campaign_id: params.get('campaign_id') ?? undefined,\n campaign_name: params.get('campaign_name') ?? undefined,\n placement: params.get('placement') ?? undefined,\n site_source_name: params.get('site_source_name') ?? undefined,\n // Google Ads\n gclid: params.get('gclid') ?? undefined,\n gclsrc: params.get('gclsrc') ?? undefined,\n gad_source: params.get('gad_source') ?? undefined,\n gad_campaignid: params.get('gad_campaignid') ?? undefined,\n // Reddit Ads — _rdt_cid is set server-side (see @shware/analytics/server resolveClickIdCookies)\n rdt_cid: params.get('rdt_cid') ?? parsed._rdt_cid ?? undefined,\n rdt_uuid: parsed._rdt_uuid,\n // LinkedIn Ads: get click id from url params or first-party cookie\n li_fat_id: params.get('li_fat_id') ?? parsed.li_fat_id ?? undefined,\n // click ids\n dclid: params.get('dclid') ?? undefined,\n ko_click_id: params.get('ko_click_id') ?? undefined,\n msclkid: params.get('msclkid') ?? undefined,\n sccid: params.get('sccid') ?? undefined,\n ttclid: params.get('ttclid') ?? undefined,\n twclid: params.get('twclid') ?? undefined,\n wbraid: params.get('wbraid') ?? undefined,\n yclid: params.get('yclid') ?? undefined,\n // utm params\n utm_source: link?.utm_source ?? params.get('utm_source') ?? undefined,\n utm_medium: link?.utm_medium ?? params.get('utm_medium') ?? undefined,\n utm_campaign: link?.utm_campaign ?? params.get('utm_campaign') ?? undefined,\n utm_term: link?.utm_term ?? params.get('utm_term') ?? undefined,\n utm_content: link?.utm_content ?? params.get('utm_content') ?? undefined,\n utm_id: link?.utm_id ?? params.get('utm_id') ?? undefined,\n utm_source_platform:\n link?.utm_source_platform ?? params.get('utm_source_platform') ?? undefined,\n utm_creative_format:\n link?.utm_creative_format ?? params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic:\n link?.utm_marketing_tactic ?? params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["config","keys","getLink","Bowser"],"sources":["../../src/web/index.ts"],"sourcesContent":["import Bowser from 'bowser';\nimport { parseCookie } from 'cookie';\nimport { v4 as uuidv4 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { type Link, getLink } from '../link/index';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\nexport function getDeviceId() {\n const cached = config.storage.getItem(keys.device_id);\n if (cached) return cached;\n // lib.dom types `crypto.randomUUID` as always present, but it is missing in\n // insecure contexts and older browsers, so probe it before use.\n const id = (crypto as Partial<Crypto>).randomUUID ? crypto.randomUUID() : uuidv4();\n config.storage.setItem(keys.device_id, id);\n return id;\n}\n\nconst links = new Map<string, Promise<Link | null>>();\n\n/**\n * `getTags` runs once per event now, and the link a `?s=` id points at cannot change while the\n * page is open, so an uncached lookup would put an identical request on the wire for every\n * event the page sends.\n */\nfunction getCachedLink(id: string) {\n const cached = links.get(id);\n if (cached) return cached;\n const link = getLink(id).then((result) => {\n // A lookup that came back empty must not stick for the lifetime of the page. `getLink`\n // answers null for a network failure as well as for a link that does not exist, and losing\n // the link's utm params for every later event costs more than repeating a rare request.\n if (!result) links.delete(id);\n return result;\n });\n links.set(id, link);\n return link;\n}\n\n/** The user agent cannot change while the page is open, so it is parsed once. */\nlet parser: ReturnType<typeof Bowser.getParser> | undefined;\n\nexport async function getTags() {\n // Read the page before the first await: `getTags` runs when the event happens, and a single\n // page app can navigate while the link lookup below is still in flight.\n const page_location = window.location.href;\n const page_referrer = document.referrer || undefined;\n const page_title = document.title;\n\n parser ??= Bowser.getParser(window.navigator.userAgent);\n const params = new URLSearchParams(window.location.search);\n const os = parser.getOS();\n const browser = parser.getBrowser();\n const platform = parser.getPlatform();\n const parsed = parseCookie(document.cookie);\n\n const linkId = params.get('s');\n const link = linkId ? await getCachedLink(linkId) : null;\n\n const tags: TrackTags = {\n os: `${os.name} ${os.version}`,\n os_name: os.name,\n os_version: os.version,\n browser: `${browser.name} ${browser.version}`,\n browser_name: browser.name,\n browser_version: browser.version,\n device: platform.model,\n device_id: getDeviceId(),\n device_type: platform.type,\n device_vendor: platform.vendor,\n device_pixel_ratio: window.devicePixelRatio,\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n release: config.release,\n language: navigator.language,\n time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n page_location,\n page_referrer,\n page_title,\n // Meta Ads — _fbc is set server-side (see @shware/analytics/server resolveClickIdCookies)\n fbc: parsed._fbc ?? undefined,\n fbp: parsed._fbp,\n fbclid: params.get('fbclid') ?? undefined,\n ad_id: params.get('ad_id') ?? undefined,\n ad_name: params.get('ad_name') ?? undefined,\n adset_id: params.get('adset_id') ?? undefined,\n adset_name: params.get('adset_name') ?? undefined,\n campaign_id: params.get('campaign_id') ?? undefined,\n campaign_name: params.get('campaign_name') ?? undefined,\n placement: params.get('placement') ?? undefined,\n site_source_name: params.get('site_source_name') ?? undefined,\n // Google Ads\n gclid: params.get('gclid') ?? undefined,\n gclsrc: params.get('gclsrc') ?? undefined,\n gad_source: params.get('gad_source') ?? undefined,\n gad_campaignid: params.get('gad_campaignid') ?? undefined,\n // Reddit Ads — _rdt_cid is set server-side (see @shware/analytics/server resolveClickIdCookies)\n rdt_cid: params.get('rdt_cid') ?? parsed._rdt_cid ?? undefined,\n rdt_uuid: parsed._rdt_uuid,\n // LinkedIn Ads: get click id from url params or first-party cookie\n li_fat_id: params.get('li_fat_id') ?? parsed.li_fat_id ?? undefined,\n // click ids\n dclid: params.get('dclid') ?? undefined,\n ko_click_id: params.get('ko_click_id') ?? undefined,\n msclkid: params.get('msclkid') ?? undefined,\n sccid: params.get('sccid') ?? undefined,\n ttclid: params.get('ttclid') ?? undefined,\n twclid: params.get('twclid') ?? undefined,\n wbraid: params.get('wbraid') ?? undefined,\n gbraid: params.get('gbraid') ?? undefined,\n yclid: params.get('yclid') ?? undefined,\n // utm params\n utm_source: link?.utm_source ?? params.get('utm_source') ?? undefined,\n utm_medium: link?.utm_medium ?? params.get('utm_medium') ?? undefined,\n utm_campaign: link?.utm_campaign ?? params.get('utm_campaign') ?? undefined,\n utm_term: link?.utm_term ?? params.get('utm_term') ?? undefined,\n utm_content: link?.utm_content ?? params.get('utm_content') ?? undefined,\n utm_id: link?.utm_id ?? params.get('utm_id') ?? undefined,\n utm_source_platform:\n link?.utm_source_platform ?? params.get('utm_source_platform') ?? undefined,\n utm_creative_format:\n link?.utm_creative_format ?? params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic:\n link?.utm_marketing_tactic ?? params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n"],"mappings":"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"}
|
package/dist/web/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/web/index.ts"],"mappings":";;;iBAQgB;iBAkCM,WAAO,QAAA;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/web/index.ts"],"mappings":";;;iBAQgB;iBAkCM,WAAO,QAAA;cA2FhB,SAAS"}
|
package/dist/web/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/web/index.ts"],"mappings":";;;iBAQgB;iBAkCM,WAAO,QAAA;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/web/index.ts"],"mappings":";;;iBAQgB;iBAkCM,WAAO,QAAA;cA2FhB,SAAS"}
|
package/dist/web/index.mjs
CHANGED
|
@@ -90,6 +90,7 @@ async function getTags() {
|
|
|
90
90
|
ttclid: params.get("ttclid") ?? void 0,
|
|
91
91
|
twclid: params.get("twclid") ?? void 0,
|
|
92
92
|
wbraid: params.get("wbraid") ?? void 0,
|
|
93
|
+
gbraid: params.get("gbraid") ?? void 0,
|
|
93
94
|
yclid: params.get("yclid") ?? void 0,
|
|
94
95
|
utm_source: link?.utm_source ?? params.get("utm_source") ?? void 0,
|
|
95
96
|
utm_medium: link?.utm_medium ?? params.get("utm_medium") ?? void 0,
|
package/dist/web/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["uuidv4","Bowser"],"sources":["../../src/web/index.ts"],"sourcesContent":["import Bowser from 'bowser';\nimport { parseCookie } from 'cookie';\nimport { v4 as uuidv4 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { type Link, getLink } from '../link/index';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\nexport function getDeviceId() {\n const cached = config.storage.getItem(keys.device_id);\n if (cached) return cached;\n // lib.dom types `crypto.randomUUID` as always present, but it is missing in\n // insecure contexts and older browsers, so probe it before use.\n const id = (crypto as Partial<Crypto>).randomUUID ? crypto.randomUUID() : uuidv4();\n config.storage.setItem(keys.device_id, id);\n return id;\n}\n\nconst links = new Map<string, Promise<Link | null>>();\n\n/**\n * `getTags` runs once per event now, and the link a `?s=` id points at cannot change while the\n * page is open, so an uncached lookup would put an identical request on the wire for every\n * event the page sends.\n */\nfunction getCachedLink(id: string) {\n const cached = links.get(id);\n if (cached) return cached;\n const link = getLink(id).then((result) => {\n // A lookup that came back empty must not stick for the lifetime of the page. `getLink`\n // answers null for a network failure as well as for a link that does not exist, and losing\n // the link's utm params for every later event costs more than repeating a rare request.\n if (!result) links.delete(id);\n return result;\n });\n links.set(id, link);\n return link;\n}\n\n/** The user agent cannot change while the page is open, so it is parsed once. */\nlet parser: ReturnType<typeof Bowser.getParser> | undefined;\n\nexport async function getTags() {\n // Read the page before the first await: `getTags` runs when the event happens, and a single\n // page app can navigate while the link lookup below is still in flight.\n const page_location = window.location.href;\n const page_referrer = document.referrer || undefined;\n const page_title = document.title;\n\n parser ??= Bowser.getParser(window.navigator.userAgent);\n const params = new URLSearchParams(window.location.search);\n const os = parser.getOS();\n const browser = parser.getBrowser();\n const platform = parser.getPlatform();\n const parsed = parseCookie(document.cookie);\n\n const linkId = params.get('s');\n const link = linkId ? await getCachedLink(linkId) : null;\n\n const tags: TrackTags = {\n os: `${os.name} ${os.version}`,\n os_name: os.name,\n os_version: os.version,\n browser: `${browser.name} ${browser.version}`,\n browser_name: browser.name,\n browser_version: browser.version,\n device: platform.model,\n device_id: getDeviceId(),\n device_type: platform.type,\n device_vendor: platform.vendor,\n device_pixel_ratio: window.devicePixelRatio,\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n release: config.release,\n language: navigator.language,\n time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n page_location,\n page_referrer,\n page_title,\n // Meta Ads — _fbc is set server-side (see @shware/analytics/server resolveClickIdCookies)\n fbc: parsed._fbc ?? undefined,\n fbp: parsed._fbp,\n fbclid: params.get('fbclid') ?? undefined,\n ad_id: params.get('ad_id') ?? undefined,\n ad_name: params.get('ad_name') ?? undefined,\n adset_id: params.get('adset_id') ?? undefined,\n adset_name: params.get('adset_name') ?? undefined,\n campaign_id: params.get('campaign_id') ?? undefined,\n campaign_name: params.get('campaign_name') ?? undefined,\n placement: params.get('placement') ?? undefined,\n site_source_name: params.get('site_source_name') ?? undefined,\n // Google Ads\n gclid: params.get('gclid') ?? undefined,\n gclsrc: params.get('gclsrc') ?? undefined,\n gad_source: params.get('gad_source') ?? undefined,\n gad_campaignid: params.get('gad_campaignid') ?? undefined,\n // Reddit Ads — _rdt_cid is set server-side (see @shware/analytics/server resolveClickIdCookies)\n rdt_cid: params.get('rdt_cid') ?? parsed._rdt_cid ?? undefined,\n rdt_uuid: parsed._rdt_uuid,\n // LinkedIn Ads: get click id from url params or first-party cookie\n li_fat_id: params.get('li_fat_id') ?? parsed.li_fat_id ?? undefined,\n // click ids\n dclid: params.get('dclid') ?? undefined,\n ko_click_id: params.get('ko_click_id') ?? undefined,\n msclkid: params.get('msclkid') ?? undefined,\n sccid: params.get('sccid') ?? undefined,\n ttclid: params.get('ttclid') ?? undefined,\n twclid: params.get('twclid') ?? undefined,\n wbraid: params.get('wbraid') ?? undefined,\n yclid: params.get('yclid') ?? undefined,\n // utm params\n utm_source: link?.utm_source ?? params.get('utm_source') ?? undefined,\n utm_medium: link?.utm_medium ?? params.get('utm_medium') ?? undefined,\n utm_campaign: link?.utm_campaign ?? params.get('utm_campaign') ?? undefined,\n utm_term: link?.utm_term ?? params.get('utm_term') ?? undefined,\n utm_content: link?.utm_content ?? params.get('utm_content') ?? undefined,\n utm_id: link?.utm_id ?? params.get('utm_id') ?? undefined,\n utm_source_platform:\n link?.utm_source_platform ?? params.get('utm_source_platform') ?? undefined,\n utm_creative_format:\n link?.utm_creative_format ?? params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic:\n link?.utm_marketing_tactic ?? params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["uuidv4","Bowser"],"sources":["../../src/web/index.ts"],"sourcesContent":["import Bowser from 'bowser';\nimport { parseCookie } from 'cookie';\nimport { v4 as uuidv4 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { type Link, getLink } from '../link/index';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\nexport function getDeviceId() {\n const cached = config.storage.getItem(keys.device_id);\n if (cached) return cached;\n // lib.dom types `crypto.randomUUID` as always present, but it is missing in\n // insecure contexts and older browsers, so probe it before use.\n const id = (crypto as Partial<Crypto>).randomUUID ? crypto.randomUUID() : uuidv4();\n config.storage.setItem(keys.device_id, id);\n return id;\n}\n\nconst links = new Map<string, Promise<Link | null>>();\n\n/**\n * `getTags` runs once per event now, and the link a `?s=` id points at cannot change while the\n * page is open, so an uncached lookup would put an identical request on the wire for every\n * event the page sends.\n */\nfunction getCachedLink(id: string) {\n const cached = links.get(id);\n if (cached) return cached;\n const link = getLink(id).then((result) => {\n // A lookup that came back empty must not stick for the lifetime of the page. `getLink`\n // answers null for a network failure as well as for a link that does not exist, and losing\n // the link's utm params for every later event costs more than repeating a rare request.\n if (!result) links.delete(id);\n return result;\n });\n links.set(id, link);\n return link;\n}\n\n/** The user agent cannot change while the page is open, so it is parsed once. */\nlet parser: ReturnType<typeof Bowser.getParser> | undefined;\n\nexport async function getTags() {\n // Read the page before the first await: `getTags` runs when the event happens, and a single\n // page app can navigate while the link lookup below is still in flight.\n const page_location = window.location.href;\n const page_referrer = document.referrer || undefined;\n const page_title = document.title;\n\n parser ??= Bowser.getParser(window.navigator.userAgent);\n const params = new URLSearchParams(window.location.search);\n const os = parser.getOS();\n const browser = parser.getBrowser();\n const platform = parser.getPlatform();\n const parsed = parseCookie(document.cookie);\n\n const linkId = params.get('s');\n const link = linkId ? await getCachedLink(linkId) : null;\n\n const tags: TrackTags = {\n os: `${os.name} ${os.version}`,\n os_name: os.name,\n os_version: os.version,\n browser: `${browser.name} ${browser.version}`,\n browser_name: browser.name,\n browser_version: browser.version,\n device: platform.model,\n device_id: getDeviceId(),\n device_type: platform.type,\n device_vendor: platform.vendor,\n device_pixel_ratio: window.devicePixelRatio,\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n release: config.release,\n language: navigator.language,\n time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n page_location,\n page_referrer,\n page_title,\n // Meta Ads — _fbc is set server-side (see @shware/analytics/server resolveClickIdCookies)\n fbc: parsed._fbc ?? undefined,\n fbp: parsed._fbp,\n fbclid: params.get('fbclid') ?? undefined,\n ad_id: params.get('ad_id') ?? undefined,\n ad_name: params.get('ad_name') ?? undefined,\n adset_id: params.get('adset_id') ?? undefined,\n adset_name: params.get('adset_name') ?? undefined,\n campaign_id: params.get('campaign_id') ?? undefined,\n campaign_name: params.get('campaign_name') ?? undefined,\n placement: params.get('placement') ?? undefined,\n site_source_name: params.get('site_source_name') ?? undefined,\n // Google Ads\n gclid: params.get('gclid') ?? undefined,\n gclsrc: params.get('gclsrc') ?? undefined,\n gad_source: params.get('gad_source') ?? undefined,\n gad_campaignid: params.get('gad_campaignid') ?? undefined,\n // Reddit Ads — _rdt_cid is set server-side (see @shware/analytics/server resolveClickIdCookies)\n rdt_cid: params.get('rdt_cid') ?? parsed._rdt_cid ?? undefined,\n rdt_uuid: parsed._rdt_uuid,\n // LinkedIn Ads: get click id from url params or first-party cookie\n li_fat_id: params.get('li_fat_id') ?? parsed.li_fat_id ?? undefined,\n // click ids\n dclid: params.get('dclid') ?? undefined,\n ko_click_id: params.get('ko_click_id') ?? undefined,\n msclkid: params.get('msclkid') ?? undefined,\n sccid: params.get('sccid') ?? undefined,\n ttclid: params.get('ttclid') ?? undefined,\n twclid: params.get('twclid') ?? undefined,\n wbraid: params.get('wbraid') ?? undefined,\n gbraid: params.get('gbraid') ?? undefined,\n yclid: params.get('yclid') ?? undefined,\n // utm params\n utm_source: link?.utm_source ?? params.get('utm_source') ?? undefined,\n utm_medium: link?.utm_medium ?? params.get('utm_medium') ?? undefined,\n utm_campaign: link?.utm_campaign ?? params.get('utm_campaign') ?? undefined,\n utm_term: link?.utm_term ?? params.get('utm_term') ?? undefined,\n utm_content: link?.utm_content ?? params.get('utm_content') ?? undefined,\n utm_id: link?.utm_id ?? params.get('utm_id') ?? undefined,\n utm_source_platform:\n link?.utm_source_platform ?? params.get('utm_source_platform') ?? undefined,\n utm_creative_format:\n link?.utm_creative_format ?? params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic:\n link?.utm_marketing_tactic ?? params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shware/analytics",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -134,10 +134,14 @@
|
|
|
134
134
|
},
|
|
135
135
|
"devDependencies": {
|
|
136
136
|
"@repo/typescript-config": "0.0.0",
|
|
137
|
+
"@testing-library/react": "^16.3.2",
|
|
137
138
|
"@types/facebook-nodejs-business-sdk": "^24.0.0",
|
|
138
139
|
"@types/node": "^26.3.0",
|
|
139
140
|
"@types/react": "^19.2.18",
|
|
141
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
140
142
|
"bowser": "^2.14.1",
|
|
143
|
+
"jsdom": "^30.0.1",
|
|
144
|
+
"react-dom": "^19.2.8",
|
|
141
145
|
"typescript": "^7.0.2",
|
|
142
146
|
"vitest": "^4.1.11"
|
|
143
147
|
},
|