mytart 0.6.0 → 0.6.2

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 CHANGED
@@ -6,11 +6,13 @@
6
6
 
7
7
  ## Features
8
8
 
9
- - 🔌 **7 providers out of the box**: Google Analytics 4, Mixpanel, Segment, Amplitude, Plausible, PostHog, Meta Pixel
9
+ - 🔌 **8 providers out of the box**: Google Analytics 4, Mixpanel, Segment, Amplitude, Plausible, PostHog, Meta Pixel, Microsoft Clarity
10
10
  - 🌐 **Universal**: works in Node.js, browsers, and any JS framework (Next.js, Remix, Astro, SvelteKit, etc.)
11
11
  - 🔷 **TypeScript-first**: precise typings, object-parameter style for great DX
12
12
  - ðŸ“Ķ **Dual ESM/CJS output**: works with `import` and `require`
13
13
  - ðŸŠķ **Lightweight**: direct HTTP via axios, no SDK overhead
14
+ - ðŸĪ– **Bot filtering**: optionally ignore bots and crawlers via [ua-parser-js](https://github.com/nicolevanderhoeven/ua-parser-js)
15
+ - 🔗 **Cross-provider linking**: auto-captures click IDs (`gclid`, `fbclid`, `msclkid`, etc.) and cookies, then injects them into every provider call for unified attribution
14
16
  - ✅ **Node.js â‰Ĩ 18**
15
17
 
16
18
  ## Installation
@@ -253,6 +255,123 @@ await provider.updatePixelConsent(true); // fbq('consent', 'grant')
253
255
  await provider.updatePixelConsent(false); // fbq('consent', 'revoke')
254
256
  ```
255
257
 
258
+ ### Microsoft Clarity
259
+
260
+ [Microsoft Clarity](https://clarity.microsoft.com/) is a free behavioral analytics tool that provides session recordings, heatmaps, and insights. Clarity is **browser-only** — there is no server mode.
261
+
262
+ ```typescript
263
+ {
264
+ provider: 'clarity',
265
+ projectId: 'YOUR_PROJECT_ID',
266
+ enabled: true,
267
+ }
268
+ ```
269
+
270
+ When enabled:
271
+
272
+ - The official `https://www.clarity.ms/tag/{projectId}` script is loaded once on the first `track()`, `identify()`, or `page()` call
273
+ - `track()` fires `clarity('event', eventName)` and sets each property as a custom tag via `clarity('set', key, value)`
274
+ - `identify()` calls `clarity('identify', userId)` with an optional friendly name from `traits.name`, and sets remaining traits as custom tags
275
+ - `page()` fires a `PageView` event and sets `pageUrl`, `pageName`, and `referrer` as custom tags
276
+ - SSR-safe: silently succeeds when `window` is undefined
277
+
278
+ #### Cookie consent
279
+
280
+ By default Clarity operates in cookieless mode. To enable cookie-based tracking, set `cookie: true`:
281
+
282
+ ```typescript
283
+ {
284
+ provider: 'clarity',
285
+ projectId: 'YOUR_PROJECT_ID',
286
+ cookie: true,
287
+ enabled: true,
288
+ }
289
+ ```
290
+
291
+ This calls `clarity('consent')` during initialisation so Clarity can set cookies for more accurate session tracking.
292
+
293
+ ## Bot Filtering
294
+
295
+ Set `ignoreBots: true` at the top level to silently drop all `track()`, `identify()`, and `page()` calls when the visitor is a known bot or crawler. Detection is powered by [`ua-parser-js`](https://github.com/nicolevanderhoeven/ua-parser-js)'s `isBot()` function.
296
+
297
+ ```typescript
298
+ const analytics = new Mytart({
299
+ ignoreBots: true,
300
+ providers: [
301
+ { provider: 'google-analytics', measurementId: 'G-XXXXXXXXXX', enabled: true },
302
+ { provider: 'segment', writeKey: 'YOUR_KEY', enabled: true },
303
+ ],
304
+ });
305
+ ```
306
+
307
+ When a bot is detected, all methods return an empty `TrackResult[]` array — no events are dispatched to any provider.
308
+
309
+ The User-Agent is read from:
310
+
311
+ 1. `context.userAgent` if supplied in the `track()` call
312
+ 2. `navigator.userAgent` in browser environments
313
+
314
+ If no User-Agent is available (e.g. server-side without `context.userAgent`), the call proceeds normally.
315
+
316
+ ## Cross-Provider Linking
317
+
318
+ Set `crossProviderLinking: true` to automatically capture click IDs and analytics cookies, then inject them as `xpl_`-prefixed properties into every `track()`, `page()`, and `identify()` call. This lets you correlate events across providers — e.g. trace a Meta ad click through to a Clarity session recording or a Mixpanel funnel.
319
+
320
+ ```typescript
321
+ const analytics = new Mytart({
322
+ crossProviderLinking: true,
323
+ providers: [
324
+ { provider: 'google-analytics', measurementId: 'G-XXXXXXXXXX', appType: 'browser', enabled: true },
325
+ { provider: 'meta-pixel', pixelId: '123456789', appType: 'browser', enabled: true },
326
+ { provider: 'clarity', projectId: 'YOUR_PROJECT_ID', enabled: true },
327
+ { provider: 'mixpanel', token: 'YOUR_TOKEN', enabled: true },
328
+ ],
329
+ });
330
+ ```
331
+
332
+ ### What gets captured
333
+
334
+ | Source | Captured ID | Property key |
335
+ |---|---|---|
336
+ | `?gclid=` URL param | Google click ID | `xpl_gclid` |
337
+ | `?fbclid=` URL param | Meta click ID | `xpl_fbclid` |
338
+ | `?ttclid=` URL param | TikTok click ID | `xpl_ttclid` |
339
+ | `?msclkid=` URL param | Microsoft Ads click ID | `xpl_msclkid` |
340
+ | `?li_fat_id=` URL param | LinkedIn click ID | `xpl_li_fat_id` |
341
+ | `_fbp` cookie | Meta browser ID | `xpl_fbp` |
342
+ | `_fbc` cookie | Meta click ID cookie | `xpl_fbc` |
343
+ | `_ga` cookie | GA client ID | `xpl_ga_client_id` |
344
+
345
+ If `fbclid` is present in the URL but the `_fbc` cookie has not yet been set, mytart synthesises an `fbc` value in Meta's standard format (`fb.1.{timestamp}.{fbclid}`).
346
+
347
+ ### How it flows to each provider
348
+
349
+ All captured IDs are injected as event properties (`track`/`page`) or user traits (`identify`). No provider code changes are needed — each provider already forwards properties to its API:
350
+
351
+ - **GA4** — `xpl_*` fields appear as event parameters and user properties (queryable in BigQuery exports)
352
+ - **Meta Pixel** — `xpl_*` fields become custom data parameters (`fbclid`/`_fbp`/`_fbc` are also handled natively by Meta)
353
+ - **Clarity** — `xpl_*` fields are set as custom tags, letting you filter session recordings by ad click source
354
+ - **Mixpanel / Amplitude / PostHog** — `xpl_*` fields become event and user properties, fully queryable
355
+ - **Segment** — `xpl_*` fields flow through to all downstream destinations in your Segment pipeline
356
+ - **Plausible** — `xpl_*` fields are sent as custom props (must be [registered in your Plausible dashboard](https://plausible.io/docs/custom-props/introduction) to appear in reports)
357
+
358
+ ### Inspecting captured IDs
359
+
360
+ Use `getCapturedIds()` to see what was captured at construction time:
361
+
362
+ ```typescript
363
+ const ids = analytics.getCapturedIds();
364
+ // { fbclid: 'abc123', fbc: 'fb.1.1234567890.abc123', gaClientId: '1234567890.1234567890' }
365
+ ```
366
+
367
+ Returns `null` when `crossProviderLinking` is disabled.
368
+
369
+ ### Notes
370
+
371
+ - **Browser-only.** SSR-safe — returns empty results when `window` is undefined.
372
+ - **User properties take precedence.** If you pass a property with the same `xpl_*` key, your value wins.
373
+ - **Consent.** This feature reads existing URL parameters and cookies — it does not set new cookies or tracking identifiers. Each provider's own consent mechanism remains authoritative.
374
+
256
375
  ## API Reference
257
376
 
258
377
  ### `new Mytart(config: MytartConfig)`
@@ -263,6 +382,8 @@ interface MytartConfig {
263
382
  defaultUserId?: string; // applied to every track/page call if no userId given
264
383
  defaultAnonymousId?: string; // applied to every track/page call if no anonymousId given
265
384
  debug?: boolean;
385
+ ignoreBots?: boolean; // when true, silently drops all tracking calls from known bots/crawlers
386
+ crossProviderLinking?: boolean; // when true, auto-captures click IDs & cookies and injects them into every call
266
387
  }
267
388
  ```
268
389
 
@@ -373,7 +494,7 @@ import type {
373
494
  TrackResult, MytartError, EventContext, ProviderName, GoogleAnalyticsAppType,
374
495
  GoogleAnalyticsConfig, ConsentSettings, ConsentState, MixpanelConfig, SegmentConfig,
375
496
  AmplitudeConfig, PlausibleConfig, PostHogConfig, MetaPixelConfig, MetaPixelAppType,
376
- MetaPixelAdvancedMatching,
497
+ MetaPixelAdvancedMatching, ClarityConfig,
377
498
  } from 'mytart';
378
499
  ```
379
500
 
package/dist/index.d.mts CHANGED
@@ -197,6 +197,32 @@ interface MytartConfig {
197
197
  defaultUserId?: string;
198
198
  defaultAnonymousId?: string;
199
199
  debug?: boolean;
200
+ /**
201
+ * When `true`, silently drops all `track`, `identify`, and `page` calls
202
+ * if the visitor's User-Agent belongs to a known bot or crawler.
203
+ *
204
+ * Detection uses `isBot()` from `ua-parser-js`. The User-Agent is read
205
+ * from `context.userAgent` (if supplied) or `navigator.userAgent` in
206
+ * browser environments.
207
+ *
208
+ * Defaults to `false` (bots are tracked like any other visitor).
209
+ */
210
+ ignoreBots?: boolean;
211
+ /**
212
+ * When `true`, automatically captures click IDs from URL parameters
213
+ * (`gclid`, `fbclid`, `ttclid`, `msclkid`, `li_fat_id`) and analytics
214
+ * cookies (`_fbp`, `_fbc`, `_ga`) at construction time, then injects
215
+ * them as `xpl_`-prefixed properties into every `track()`, `page()`,
216
+ * and `identify()` call.
217
+ *
218
+ * This enables cross-provider attribution — e.g. linking a Meta ad
219
+ * click to a Clarity session recording or a Mixpanel event — without
220
+ * any manual wiring.
221
+ *
222
+ * Browser-only. SSR-safe (no-op when `window` is undefined).
223
+ * Defaults to `false`.
224
+ */
225
+ crossProviderLinking?: boolean;
200
226
  }
201
227
  interface EventContext {
202
228
  ip?: string;
@@ -245,10 +271,46 @@ interface MytartError {
245
271
  originalError?: unknown;
246
272
  }
247
273
 
274
+ /**
275
+ * Cross-provider identity linking.
276
+ *
277
+ * Captures click IDs from URL parameters and analytics cookies in the
278
+ * browser, then converts them into a flat property map that can be
279
+ * injected into every provider call.
280
+ *
281
+ * Browser-only — returns empty results when `window` is undefined.
282
+ */
283
+ /** Identifiers captured from URL parameters and cookies. */
284
+ interface CapturedIds {
285
+ /** Google click ID (`gclid` URL param) */
286
+ gclid?: string;
287
+ /** Meta/Facebook click ID (`fbclid` URL param) */
288
+ fbclid?: string;
289
+ /** TikTok click ID (`ttclid` URL param) */
290
+ ttclid?: string;
291
+ /** Microsoft Ads click ID (`msclkid` URL param) */
292
+ msclkid?: string;
293
+ /** LinkedIn click ID (`li_fat_id` URL param) */
294
+ li_fat_id?: string;
295
+ /** Meta browser ID (from `_fbp` cookie) */
296
+ fbp?: string;
297
+ /** Meta click ID cookie (from `_fbc` cookie, or synthesised from `fbclid`) */
298
+ fbc?: string;
299
+ /** Google Analytics client ID (extracted from `_ga` cookie) */
300
+ gaClientId?: string;
301
+ }
302
+
248
303
  declare class Mytart {
249
304
  private readonly providers;
250
305
  private readonly config;
306
+ private readonly capturedIds;
307
+ private readonly linkingProperties;
251
308
  constructor(config: MytartConfig);
309
+ /**
310
+ * Returns `true` when `ignoreBots` is enabled and the given (or detected)
311
+ * User-Agent belongs to a known bot or crawler.
312
+ */
313
+ private isBotRequest;
252
314
  track(options: TrackOptions): Promise<TrackResult[]>;
253
315
  identify(options: IdentifyOptions): Promise<TrackResult[]>;
254
316
  page(options: PageOptions): Promise<TrackResult[]>;
@@ -265,6 +327,11 @@ declare class Mytart {
265
327
  addProvider(config: ProviderConfig): void;
266
328
  removeProvider(name: string): void;
267
329
  getProviders(): string[];
330
+ /**
331
+ * Returns the click IDs and cookie values captured at construction
332
+ * time, or `null` when `crossProviderLinking` is disabled.
333
+ */
334
+ getCapturedIds(): CapturedIds | null;
268
335
  }
269
336
 
270
337
  declare abstract class BaseProvider {
@@ -510,4 +577,4 @@ declare class ClarityProvider extends BaseProvider {
510
577
  page(options: PageOptions): Promise<TrackResult>;
511
578
  }
512
579
 
513
- export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type ClarityConfig, ClarityProvider, type ConsentSettings, type ConsentState, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MetaPixelAdvancedMatching, type MetaPixelAppType, type MetaPixelConfig, MetaPixelProvider, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
580
+ export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type CapturedIds, type ClarityConfig, ClarityProvider, type ConsentSettings, type ConsentState, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MetaPixelAdvancedMatching, type MetaPixelAppType, type MetaPixelConfig, MetaPixelProvider, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
package/dist/index.d.ts CHANGED
@@ -197,6 +197,32 @@ interface MytartConfig {
197
197
  defaultUserId?: string;
198
198
  defaultAnonymousId?: string;
199
199
  debug?: boolean;
200
+ /**
201
+ * When `true`, silently drops all `track`, `identify`, and `page` calls
202
+ * if the visitor's User-Agent belongs to a known bot or crawler.
203
+ *
204
+ * Detection uses `isBot()` from `ua-parser-js`. The User-Agent is read
205
+ * from `context.userAgent` (if supplied) or `navigator.userAgent` in
206
+ * browser environments.
207
+ *
208
+ * Defaults to `false` (bots are tracked like any other visitor).
209
+ */
210
+ ignoreBots?: boolean;
211
+ /**
212
+ * When `true`, automatically captures click IDs from URL parameters
213
+ * (`gclid`, `fbclid`, `ttclid`, `msclkid`, `li_fat_id`) and analytics
214
+ * cookies (`_fbp`, `_fbc`, `_ga`) at construction time, then injects
215
+ * them as `xpl_`-prefixed properties into every `track()`, `page()`,
216
+ * and `identify()` call.
217
+ *
218
+ * This enables cross-provider attribution — e.g. linking a Meta ad
219
+ * click to a Clarity session recording or a Mixpanel event — without
220
+ * any manual wiring.
221
+ *
222
+ * Browser-only. SSR-safe (no-op when `window` is undefined).
223
+ * Defaults to `false`.
224
+ */
225
+ crossProviderLinking?: boolean;
200
226
  }
201
227
  interface EventContext {
202
228
  ip?: string;
@@ -245,10 +271,46 @@ interface MytartError {
245
271
  originalError?: unknown;
246
272
  }
247
273
 
274
+ /**
275
+ * Cross-provider identity linking.
276
+ *
277
+ * Captures click IDs from URL parameters and analytics cookies in the
278
+ * browser, then converts them into a flat property map that can be
279
+ * injected into every provider call.
280
+ *
281
+ * Browser-only — returns empty results when `window` is undefined.
282
+ */
283
+ /** Identifiers captured from URL parameters and cookies. */
284
+ interface CapturedIds {
285
+ /** Google click ID (`gclid` URL param) */
286
+ gclid?: string;
287
+ /** Meta/Facebook click ID (`fbclid` URL param) */
288
+ fbclid?: string;
289
+ /** TikTok click ID (`ttclid` URL param) */
290
+ ttclid?: string;
291
+ /** Microsoft Ads click ID (`msclkid` URL param) */
292
+ msclkid?: string;
293
+ /** LinkedIn click ID (`li_fat_id` URL param) */
294
+ li_fat_id?: string;
295
+ /** Meta browser ID (from `_fbp` cookie) */
296
+ fbp?: string;
297
+ /** Meta click ID cookie (from `_fbc` cookie, or synthesised from `fbclid`) */
298
+ fbc?: string;
299
+ /** Google Analytics client ID (extracted from `_ga` cookie) */
300
+ gaClientId?: string;
301
+ }
302
+
248
303
  declare class Mytart {
249
304
  private readonly providers;
250
305
  private readonly config;
306
+ private readonly capturedIds;
307
+ private readonly linkingProperties;
251
308
  constructor(config: MytartConfig);
309
+ /**
310
+ * Returns `true` when `ignoreBots` is enabled and the given (or detected)
311
+ * User-Agent belongs to a known bot or crawler.
312
+ */
313
+ private isBotRequest;
252
314
  track(options: TrackOptions): Promise<TrackResult[]>;
253
315
  identify(options: IdentifyOptions): Promise<TrackResult[]>;
254
316
  page(options: PageOptions): Promise<TrackResult[]>;
@@ -265,6 +327,11 @@ declare class Mytart {
265
327
  addProvider(config: ProviderConfig): void;
266
328
  removeProvider(name: string): void;
267
329
  getProviders(): string[];
330
+ /**
331
+ * Returns the click IDs and cookie values captured at construction
332
+ * time, or `null` when `crossProviderLinking` is disabled.
333
+ */
334
+ getCapturedIds(): CapturedIds | null;
268
335
  }
269
336
 
270
337
  declare abstract class BaseProvider {
@@ -510,4 +577,4 @@ declare class ClarityProvider extends BaseProvider {
510
577
  page(options: PageOptions): Promise<TrackResult>;
511
578
  }
512
579
 
513
- export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type ClarityConfig, ClarityProvider, type ConsentSettings, type ConsentState, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MetaPixelAdvancedMatching, type MetaPixelAppType, type MetaPixelConfig, MetaPixelProvider, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
580
+ export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type CapturedIds, type ClarityConfig, ClarityProvider, type ConsentSettings, type ConsentState, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MetaPixelAdvancedMatching, type MetaPixelAppType, type MetaPixelConfig, MetaPixelProvider, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
package/dist/index.js CHANGED
@@ -43,6 +43,68 @@ __export(index_exports, {
43
43
  });
44
44
  module.exports = __toCommonJS(index_exports);
45
45
 
46
+ // src/mytart.ts
47
+ var import_bot_detection = require("ua-parser-js/bot-detection");
48
+
49
+ // src/utils/cross-provider-ids.ts
50
+ var CLICK_ID_PARAMS = ["gclid", "fbclid", "ttclid", "msclkid", "li_fat_id"];
51
+ function getCookie(name) {
52
+ if (typeof document === "undefined") return void 0;
53
+ const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
54
+ return match ? decodeURIComponent(match[1]) : void 0;
55
+ }
56
+ function parseGaClientId(gaCookie) {
57
+ const parts = gaCookie.split(".");
58
+ if (parts.length < 4) return void 0;
59
+ return parts.slice(-2).join(".");
60
+ }
61
+ function captureClickIds() {
62
+ if (typeof window === "undefined") return {};
63
+ const ids = {};
64
+ const params = new URLSearchParams(window.location.search);
65
+ for (const key of CLICK_ID_PARAMS) {
66
+ const value = params.get(key);
67
+ if (value) {
68
+ ids[key] = value;
69
+ }
70
+ }
71
+ const fbp = getCookie("_fbp");
72
+ if (fbp) ids.fbp = fbp;
73
+ const fbc = getCookie("_fbc");
74
+ if (fbc) {
75
+ ids.fbc = fbc;
76
+ } else if (ids.fbclid) {
77
+ ids.fbc = `fb.1.${Date.now()}.${ids.fbclid}`;
78
+ }
79
+ const ga = getCookie("_ga");
80
+ if (ga) {
81
+ const clientId = parseGaClientId(ga);
82
+ if (clientId) ids.gaClientId = clientId;
83
+ }
84
+ return ids;
85
+ }
86
+ var PREFIX = "xpl_";
87
+ var KEY_MAP = {
88
+ gclid: "gclid",
89
+ fbclid: "fbclid",
90
+ ttclid: "ttclid",
91
+ msclkid: "msclkid",
92
+ li_fat_id: "li_fat_id",
93
+ fbp: "fbp",
94
+ fbc: "fbc",
95
+ gaClientId: "ga_client_id"
96
+ };
97
+ function capturedIdsToProperties(ids) {
98
+ const props = {};
99
+ for (const [field, suffix] of Object.entries(KEY_MAP)) {
100
+ const value = ids[field];
101
+ if (value) {
102
+ props[`${PREFIX}${suffix}`] = value;
103
+ }
104
+ }
105
+ return props;
106
+ }
107
+
46
108
  // src/providers/base.ts
47
109
  var BaseProvider = class {
48
110
  /**
@@ -1223,23 +1285,58 @@ var Mytart = class {
1223
1285
  constructor(config) {
1224
1286
  this.config = config;
1225
1287
  this.providers = config.providers.filter((c) => c.enabled === true).map(createProvider);
1288
+ if (config.crossProviderLinking) {
1289
+ this.capturedIds = captureClickIds();
1290
+ const props = capturedIdsToProperties(this.capturedIds);
1291
+ this.linkingProperties = Object.keys(props).length > 0 ? props : null;
1292
+ } else {
1293
+ this.capturedIds = null;
1294
+ this.linkingProperties = null;
1295
+ }
1296
+ }
1297
+ /**
1298
+ * Returns `true` when `ignoreBots` is enabled and the given (or detected)
1299
+ * User-Agent belongs to a known bot or crawler.
1300
+ */
1301
+ isBotRequest(userAgent) {
1302
+ if (!this.config.ignoreBots) return false;
1303
+ const ua = userAgent ?? (typeof navigator !== "undefined" ? navigator.userAgent : void 0);
1304
+ return ua ? (0, import_bot_detection.isBot)(ua) : false;
1226
1305
  }
1227
1306
  async track(options) {
1307
+ if (this.isBotRequest(options.context?.userAgent)) return [];
1228
1308
  const enriched = {
1229
1309
  userId: this.config.defaultUserId,
1230
1310
  anonymousId: this.config.defaultAnonymousId,
1231
- ...options
1311
+ ...options,
1312
+ properties: {
1313
+ ...this.linkingProperties,
1314
+ ...options.properties
1315
+ }
1232
1316
  };
1233
1317
  return Promise.all(this.providers.map((p) => p.track(enriched)));
1234
1318
  }
1235
1319
  async identify(options) {
1236
- return Promise.all(this.providers.map((p) => p.identify(options)));
1320
+ if (this.isBotRequest()) return [];
1321
+ const enriched = {
1322
+ ...options,
1323
+ traits: {
1324
+ ...this.linkingProperties,
1325
+ ...options.traits
1326
+ }
1327
+ };
1328
+ return Promise.all(this.providers.map((p) => p.identify(enriched)));
1237
1329
  }
1238
1330
  async page(options) {
1331
+ if (this.isBotRequest()) return [];
1239
1332
  const enriched = {
1240
1333
  userId: this.config.defaultUserId,
1241
1334
  anonymousId: this.config.defaultAnonymousId,
1242
- ...options
1335
+ ...options,
1336
+ properties: {
1337
+ ...this.linkingProperties,
1338
+ ...options.properties
1339
+ }
1243
1340
  };
1244
1341
  return Promise.all(this.providers.map((p) => p.page(enriched)));
1245
1342
  }
@@ -1268,6 +1365,13 @@ var Mytart = class {
1268
1365
  getProviders() {
1269
1366
  return this.providers.map((p) => p.name);
1270
1367
  }
1368
+ /**
1369
+ * Returns the click IDs and cookie values captured at construction
1370
+ * time, or `null` when `crossProviderLinking` is disabled.
1371
+ */
1372
+ getCapturedIds() {
1373
+ return this.capturedIds;
1374
+ }
1271
1375
  };
1272
1376
  // Annotate the CommonJS export names for ESM import in node:
1273
1377
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -1,3 +1,65 @@
1
+ // src/mytart.ts
2
+ import { isBot } from "ua-parser-js/bot-detection";
3
+
4
+ // src/utils/cross-provider-ids.ts
5
+ var CLICK_ID_PARAMS = ["gclid", "fbclid", "ttclid", "msclkid", "li_fat_id"];
6
+ function getCookie(name) {
7
+ if (typeof document === "undefined") return void 0;
8
+ const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
9
+ return match ? decodeURIComponent(match[1]) : void 0;
10
+ }
11
+ function parseGaClientId(gaCookie) {
12
+ const parts = gaCookie.split(".");
13
+ if (parts.length < 4) return void 0;
14
+ return parts.slice(-2).join(".");
15
+ }
16
+ function captureClickIds() {
17
+ if (typeof window === "undefined") return {};
18
+ const ids = {};
19
+ const params = new URLSearchParams(window.location.search);
20
+ for (const key of CLICK_ID_PARAMS) {
21
+ const value = params.get(key);
22
+ if (value) {
23
+ ids[key] = value;
24
+ }
25
+ }
26
+ const fbp = getCookie("_fbp");
27
+ if (fbp) ids.fbp = fbp;
28
+ const fbc = getCookie("_fbc");
29
+ if (fbc) {
30
+ ids.fbc = fbc;
31
+ } else if (ids.fbclid) {
32
+ ids.fbc = `fb.1.${Date.now()}.${ids.fbclid}`;
33
+ }
34
+ const ga = getCookie("_ga");
35
+ if (ga) {
36
+ const clientId = parseGaClientId(ga);
37
+ if (clientId) ids.gaClientId = clientId;
38
+ }
39
+ return ids;
40
+ }
41
+ var PREFIX = "xpl_";
42
+ var KEY_MAP = {
43
+ gclid: "gclid",
44
+ fbclid: "fbclid",
45
+ ttclid: "ttclid",
46
+ msclkid: "msclkid",
47
+ li_fat_id: "li_fat_id",
48
+ fbp: "fbp",
49
+ fbc: "fbc",
50
+ gaClientId: "ga_client_id"
51
+ };
52
+ function capturedIdsToProperties(ids) {
53
+ const props = {};
54
+ for (const [field, suffix] of Object.entries(KEY_MAP)) {
55
+ const value = ids[field];
56
+ if (value) {
57
+ props[`${PREFIX}${suffix}`] = value;
58
+ }
59
+ }
60
+ return props;
61
+ }
62
+
1
63
  // src/providers/base.ts
2
64
  var BaseProvider = class {
3
65
  /**
@@ -1178,23 +1240,58 @@ var Mytart = class {
1178
1240
  constructor(config) {
1179
1241
  this.config = config;
1180
1242
  this.providers = config.providers.filter((c) => c.enabled === true).map(createProvider);
1243
+ if (config.crossProviderLinking) {
1244
+ this.capturedIds = captureClickIds();
1245
+ const props = capturedIdsToProperties(this.capturedIds);
1246
+ this.linkingProperties = Object.keys(props).length > 0 ? props : null;
1247
+ } else {
1248
+ this.capturedIds = null;
1249
+ this.linkingProperties = null;
1250
+ }
1251
+ }
1252
+ /**
1253
+ * Returns `true` when `ignoreBots` is enabled and the given (or detected)
1254
+ * User-Agent belongs to a known bot or crawler.
1255
+ */
1256
+ isBotRequest(userAgent) {
1257
+ if (!this.config.ignoreBots) return false;
1258
+ const ua = userAgent ?? (typeof navigator !== "undefined" ? navigator.userAgent : void 0);
1259
+ return ua ? isBot(ua) : false;
1181
1260
  }
1182
1261
  async track(options) {
1262
+ if (this.isBotRequest(options.context?.userAgent)) return [];
1183
1263
  const enriched = {
1184
1264
  userId: this.config.defaultUserId,
1185
1265
  anonymousId: this.config.defaultAnonymousId,
1186
- ...options
1266
+ ...options,
1267
+ properties: {
1268
+ ...this.linkingProperties,
1269
+ ...options.properties
1270
+ }
1187
1271
  };
1188
1272
  return Promise.all(this.providers.map((p) => p.track(enriched)));
1189
1273
  }
1190
1274
  async identify(options) {
1191
- return Promise.all(this.providers.map((p) => p.identify(options)));
1275
+ if (this.isBotRequest()) return [];
1276
+ const enriched = {
1277
+ ...options,
1278
+ traits: {
1279
+ ...this.linkingProperties,
1280
+ ...options.traits
1281
+ }
1282
+ };
1283
+ return Promise.all(this.providers.map((p) => p.identify(enriched)));
1192
1284
  }
1193
1285
  async page(options) {
1286
+ if (this.isBotRequest()) return [];
1194
1287
  const enriched = {
1195
1288
  userId: this.config.defaultUserId,
1196
1289
  anonymousId: this.config.defaultAnonymousId,
1197
- ...options
1290
+ ...options,
1291
+ properties: {
1292
+ ...this.linkingProperties,
1293
+ ...options.properties
1294
+ }
1198
1295
  };
1199
1296
  return Promise.all(this.providers.map((p) => p.page(enriched)));
1200
1297
  }
@@ -1223,6 +1320,13 @@ var Mytart = class {
1223
1320
  getProviders() {
1224
1321
  return this.providers.map((p) => p.name);
1225
1322
  }
1323
+ /**
1324
+ * Returns the click IDs and cookie values captured at construction
1325
+ * time, or `null` when `crossProviderLinking` is disabled.
1326
+ */
1327
+ getCapturedIds() {
1328
+ return this.capturedIds;
1329
+ }
1226
1330
  };
1227
1331
  export {
1228
1332
  AmplitudeProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mytart",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Multi-Yield Tracking & Analytics Relay Tool — framework-agnostic analytics for any project",
5
5
  "keywords": [
6
6
  "analytics",
@@ -35,7 +35,8 @@
35
35
  "node": ">=18"
36
36
  },
37
37
  "dependencies": {
38
- "axios": "^1.7.0"
38
+ "axios": "^1.7.0",
39
+ "ua-parser-js": "^2.0.9"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@types/jest": "^29.5.0",