mytart 0.5.2 → 0.6.1

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,12 @@
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)
14
15
  - ✅ **Node.js â‰Ĩ 18**
15
16
 
16
17
  ## Installation
@@ -253,6 +254,64 @@ await provider.updatePixelConsent(true); // fbq('consent', 'grant')
253
254
  await provider.updatePixelConsent(false); // fbq('consent', 'revoke')
254
255
  ```
255
256
 
257
+ ### Microsoft Clarity
258
+
259
+ [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.
260
+
261
+ ```typescript
262
+ {
263
+ provider: 'clarity',
264
+ projectId: 'YOUR_PROJECT_ID',
265
+ enabled: true,
266
+ }
267
+ ```
268
+
269
+ When enabled:
270
+
271
+ - The official `https://www.clarity.ms/tag/{projectId}` script is loaded once on the first `track()`, `identify()`, or `page()` call
272
+ - `track()` fires `clarity('event', eventName)` and sets each property as a custom tag via `clarity('set', key, value)`
273
+ - `identify()` calls `clarity('identify', userId)` with an optional friendly name from `traits.name`, and sets remaining traits as custom tags
274
+ - `page()` fires a `PageView` event and sets `pageUrl`, `pageName`, and `referrer` as custom tags
275
+ - SSR-safe: silently succeeds when `window` is undefined
276
+
277
+ #### Cookie consent
278
+
279
+ By default Clarity operates in cookieless mode. To enable cookie-based tracking, set `cookie: true`:
280
+
281
+ ```typescript
282
+ {
283
+ provider: 'clarity',
284
+ projectId: 'YOUR_PROJECT_ID',
285
+ cookie: true,
286
+ enabled: true,
287
+ }
288
+ ```
289
+
290
+ This calls `clarity('consent')` during initialisation so Clarity can set cookies for more accurate session tracking.
291
+
292
+ ## Bot Filtering
293
+
294
+ 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.
295
+
296
+ ```typescript
297
+ const analytics = new Mytart({
298
+ ignoreBots: true,
299
+ providers: [
300
+ { provider: 'google-analytics', measurementId: 'G-XXXXXXXXXX', enabled: true },
301
+ { provider: 'segment', writeKey: 'YOUR_KEY', enabled: true },
302
+ ],
303
+ });
304
+ ```
305
+
306
+ When a bot is detected, all methods return an empty `TrackResult[]` array — no events are dispatched to any provider.
307
+
308
+ The User-Agent is read from:
309
+
310
+ 1. `context.userAgent` if supplied in the `track()` call
311
+ 2. `navigator.userAgent` in browser environments
312
+
313
+ If no User-Agent is available (e.g. server-side without `context.userAgent`), the call proceeds normally.
314
+
256
315
  ## API Reference
257
316
 
258
317
  ### `new Mytart(config: MytartConfig)`
@@ -263,6 +322,7 @@ interface MytartConfig {
263
322
  defaultUserId?: string; // applied to every track/page call if no userId given
264
323
  defaultAnonymousId?: string; // applied to every track/page call if no anonymousId given
265
324
  debug?: boolean;
325
+ ignoreBots?: boolean; // when true, silently drops all tracking calls from known bots/crawlers
266
326
  }
267
327
  ```
268
328
 
@@ -373,7 +433,7 @@ import type {
373
433
  TrackResult, MytartError, EventContext, ProviderName, GoogleAnalyticsAppType,
374
434
  GoogleAnalyticsConfig, ConsentSettings, ConsentState, MixpanelConfig, SegmentConfig,
375
435
  AmplitudeConfig, PlausibleConfig, PostHogConfig, MetaPixelConfig, MetaPixelAppType,
376
- MetaPixelAdvancedMatching,
436
+ MetaPixelAdvancedMatching, ClarityConfig,
377
437
  } from 'mytart';
378
438
  ```
379
439
 
package/dist/index.d.mts CHANGED
@@ -35,7 +35,7 @@ interface ConsentSettings {
35
35
  /** Controls storage for security purposes (e.g. authentication). */
36
36
  security_storage?: ConsentState;
37
37
  }
38
- type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog' | 'meta-pixel';
38
+ type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog' | 'meta-pixel' | 'clarity';
39
39
  type GoogleAnalyticsAppType = 'browser' | 'server';
40
40
  interface GoogleAnalyticsConfig extends BaseProviderConfig {
41
41
  provider: 'google-analytics';
@@ -180,12 +180,34 @@ interface MetaPixelConfig extends BaseProviderConfig {
180
180
  /** Enable Meta Pixel debug mode (`fbq('set', 'debug', true)`). */
181
181
  debug?: boolean;
182
182
  }
183
- type ProviderConfig = GoogleAnalyticsConfig | MixpanelConfig | SegmentConfig | AmplitudeConfig | PlausibleConfig | PostHogConfig | MetaPixelConfig;
183
+ interface ClarityConfig extends BaseProviderConfig {
184
+ provider: 'clarity';
185
+ /** The Clarity project ID (from the Clarity dashboard). */
186
+ projectId: string;
187
+ /**
188
+ * Enable Clarity cookie consent mode. When `true`, calls
189
+ * `clarity('consent')` after initialisation so Clarity sets cookies.
190
+ * When omitted or `false`, Clarity operates in cookieless mode.
191
+ */
192
+ cookie?: boolean;
193
+ }
194
+ type ProviderConfig = GoogleAnalyticsConfig | MixpanelConfig | SegmentConfig | AmplitudeConfig | PlausibleConfig | PostHogConfig | MetaPixelConfig | ClarityConfig;
184
195
  interface MytartConfig {
185
196
  providers: ProviderConfig[];
186
197
  defaultUserId?: string;
187
198
  defaultAnonymousId?: string;
188
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;
189
211
  }
190
212
  interface EventContext {
191
213
  ip?: string;
@@ -238,6 +260,11 @@ declare class Mytart {
238
260
  private readonly providers;
239
261
  private readonly config;
240
262
  constructor(config: MytartConfig);
263
+ /**
264
+ * Returns `true` when `ignoreBots` is enabled and the given (or detected)
265
+ * User-Agent belongs to a known bot or crawler.
266
+ */
267
+ private isBotRequest;
241
268
  track(options: TrackOptions): Promise<TrackResult[]>;
242
269
  identify(options: IdentifyOptions): Promise<TrackResult[]>;
243
270
  page(options: PageOptions): Promise<TrackResult[]>;
@@ -470,4 +497,33 @@ declare class MetaPixelProvider extends BaseProvider {
470
497
  page(options: PageOptions): Promise<TrackResult>;
471
498
  }
472
499
 
473
- export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, 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 };
500
+ declare global {
501
+ interface Window {
502
+ clarity: ClarityFn & {
503
+ q?: unknown[][];
504
+ };
505
+ }
506
+ }
507
+ type ClarityFn = (...args: unknown[]) => void;
508
+ declare class ClarityProvider extends BaseProvider {
509
+ readonly name = "clarity";
510
+ private readonly config;
511
+ private clarityReady;
512
+ constructor(config: ClarityConfig);
513
+ /**
514
+ * Injects the official Clarity tracking snippet. Mirrors:
515
+ *
516
+ * (function(c,l,a,r,i,t,y){
517
+ * c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
518
+ * t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
519
+ * y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
520
+ * })(window, document, "clarity", "script", "PROJECT_ID");
521
+ */
522
+ private initClarity;
523
+ private ensureClarity;
524
+ track(options: TrackOptions): Promise<TrackResult>;
525
+ identify(options: IdentifyOptions): Promise<TrackResult>;
526
+ page(options: PageOptions): Promise<TrackResult>;
527
+ }
528
+
529
+ 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 };
package/dist/index.d.ts CHANGED
@@ -35,7 +35,7 @@ interface ConsentSettings {
35
35
  /** Controls storage for security purposes (e.g. authentication). */
36
36
  security_storage?: ConsentState;
37
37
  }
38
- type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog' | 'meta-pixel';
38
+ type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog' | 'meta-pixel' | 'clarity';
39
39
  type GoogleAnalyticsAppType = 'browser' | 'server';
40
40
  interface GoogleAnalyticsConfig extends BaseProviderConfig {
41
41
  provider: 'google-analytics';
@@ -180,12 +180,34 @@ interface MetaPixelConfig extends BaseProviderConfig {
180
180
  /** Enable Meta Pixel debug mode (`fbq('set', 'debug', true)`). */
181
181
  debug?: boolean;
182
182
  }
183
- type ProviderConfig = GoogleAnalyticsConfig | MixpanelConfig | SegmentConfig | AmplitudeConfig | PlausibleConfig | PostHogConfig | MetaPixelConfig;
183
+ interface ClarityConfig extends BaseProviderConfig {
184
+ provider: 'clarity';
185
+ /** The Clarity project ID (from the Clarity dashboard). */
186
+ projectId: string;
187
+ /**
188
+ * Enable Clarity cookie consent mode. When `true`, calls
189
+ * `clarity('consent')` after initialisation so Clarity sets cookies.
190
+ * When omitted or `false`, Clarity operates in cookieless mode.
191
+ */
192
+ cookie?: boolean;
193
+ }
194
+ type ProviderConfig = GoogleAnalyticsConfig | MixpanelConfig | SegmentConfig | AmplitudeConfig | PlausibleConfig | PostHogConfig | MetaPixelConfig | ClarityConfig;
184
195
  interface MytartConfig {
185
196
  providers: ProviderConfig[];
186
197
  defaultUserId?: string;
187
198
  defaultAnonymousId?: string;
188
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;
189
211
  }
190
212
  interface EventContext {
191
213
  ip?: string;
@@ -238,6 +260,11 @@ declare class Mytart {
238
260
  private readonly providers;
239
261
  private readonly config;
240
262
  constructor(config: MytartConfig);
263
+ /**
264
+ * Returns `true` when `ignoreBots` is enabled and the given (or detected)
265
+ * User-Agent belongs to a known bot or crawler.
266
+ */
267
+ private isBotRequest;
241
268
  track(options: TrackOptions): Promise<TrackResult[]>;
242
269
  identify(options: IdentifyOptions): Promise<TrackResult[]>;
243
270
  page(options: PageOptions): Promise<TrackResult[]>;
@@ -470,4 +497,33 @@ declare class MetaPixelProvider extends BaseProvider {
470
497
  page(options: PageOptions): Promise<TrackResult>;
471
498
  }
472
499
 
473
- export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, 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 };
500
+ declare global {
501
+ interface Window {
502
+ clarity: ClarityFn & {
503
+ q?: unknown[][];
504
+ };
505
+ }
506
+ }
507
+ type ClarityFn = (...args: unknown[]) => void;
508
+ declare class ClarityProvider extends BaseProvider {
509
+ readonly name = "clarity";
510
+ private readonly config;
511
+ private clarityReady;
512
+ constructor(config: ClarityConfig);
513
+ /**
514
+ * Injects the official Clarity tracking snippet. Mirrors:
515
+ *
516
+ * (function(c,l,a,r,i,t,y){
517
+ * c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
518
+ * t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
519
+ * y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
520
+ * })(window, document, "clarity", "script", "PROJECT_ID");
521
+ */
522
+ private initClarity;
523
+ private ensureClarity;
524
+ track(options: TrackOptions): Promise<TrackResult>;
525
+ identify(options: IdentifyOptions): Promise<TrackResult>;
526
+ page(options: PageOptions): Promise<TrackResult>;
527
+ }
528
+
529
+ 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 };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AmplitudeProvider: () => AmplitudeProvider,
34
34
  BaseProvider: () => BaseProvider,
35
+ ClarityProvider: () => ClarityProvider,
35
36
  GoogleAnalyticsProvider: () => GoogleAnalyticsProvider,
36
37
  MetaPixelProvider: () => MetaPixelProvider,
37
38
  MixpanelProvider: () => MixpanelProvider,
@@ -42,6 +43,9 @@ __export(index_exports, {
42
43
  });
43
44
  module.exports = __toCommonJS(index_exports);
44
45
 
46
+ // src/mytart.ts
47
+ var import_bot_detection = require("ua-parser-js/bot-detection");
48
+
45
49
  // src/providers/base.ts
46
50
  var BaseProvider = class {
47
51
  /**
@@ -1092,6 +1096,107 @@ var MetaPixelProvider = class extends BaseProvider {
1092
1096
  }
1093
1097
  };
1094
1098
 
1099
+ // src/providers/clarity.ts
1100
+ var CLARITY_SCRIPT_BASE = "https://www.clarity.ms/tag/";
1101
+ var ClarityProvider = class extends BaseProvider {
1102
+ constructor(config) {
1103
+ super();
1104
+ this.name = "clarity";
1105
+ this.clarityReady = null;
1106
+ this.config = config;
1107
+ }
1108
+ /**
1109
+ * Injects the official Clarity tracking snippet. Mirrors:
1110
+ *
1111
+ * (function(c,l,a,r,i,t,y){
1112
+ * c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
1113
+ * t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
1114
+ * y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
1115
+ * })(window, document, "clarity", "script", "PROJECT_ID");
1116
+ */
1117
+ initClarity() {
1118
+ if (typeof window === "undefined" || typeof document === "undefined") {
1119
+ return Promise.resolve();
1120
+ }
1121
+ if (!window.clarity) {
1122
+ const q = [];
1123
+ const fn = function clarity(...args) {
1124
+ q.push(args);
1125
+ };
1126
+ fn.q = q;
1127
+ window.clarity = fn;
1128
+ }
1129
+ if (this.config.cookie) {
1130
+ window.clarity("consent");
1131
+ }
1132
+ return new Promise((resolve) => {
1133
+ const script = document.createElement("script");
1134
+ script.async = true;
1135
+ script.src = CLARITY_SCRIPT_BASE + this.config.projectId;
1136
+ script.onload = () => resolve();
1137
+ script.onerror = () => resolve();
1138
+ document.head.appendChild(script);
1139
+ });
1140
+ }
1141
+ ensureClarity() {
1142
+ if (!this.clarityReady) {
1143
+ this.clarityReady = this.initClarity();
1144
+ }
1145
+ return this.clarityReady;
1146
+ }
1147
+ async track(options) {
1148
+ if (typeof window === "undefined") {
1149
+ return this.buildSuccess(200);
1150
+ }
1151
+ await this.ensureClarity();
1152
+ window.clarity("event", options.event);
1153
+ if (options.properties) {
1154
+ for (const [key, value] of Object.entries(options.properties)) {
1155
+ window.clarity("set", key, String(value));
1156
+ }
1157
+ }
1158
+ return this.buildSuccess(200);
1159
+ }
1160
+ async identify(options) {
1161
+ if (typeof window === "undefined") {
1162
+ return this.buildSuccess(200);
1163
+ }
1164
+ await this.ensureClarity();
1165
+ const friendlyName = options.traits?.name ?? void 0;
1166
+ window.clarity("identify", options.userId, void 0, void 0, friendlyName);
1167
+ if (options.traits) {
1168
+ for (const [key, value] of Object.entries(options.traits)) {
1169
+ if (key !== "name") {
1170
+ window.clarity("set", key, String(value));
1171
+ }
1172
+ }
1173
+ }
1174
+ return this.buildSuccess(200);
1175
+ }
1176
+ async page(options) {
1177
+ if (typeof window === "undefined") {
1178
+ return this.buildSuccess(200);
1179
+ }
1180
+ await this.ensureClarity();
1181
+ window.clarity("event", "PageView");
1182
+ if (options.url) {
1183
+ window.clarity("set", "pageUrl", options.url);
1184
+ }
1185
+ if (options.name) {
1186
+ window.clarity("set", "pageName", options.name);
1187
+ }
1188
+ if (options.referrer) {
1189
+ window.clarity("set", "referrer", options.referrer);
1190
+ }
1191
+ if (options.properties) {
1192
+ for (const [key, value] of Object.entries(options.properties)) {
1193
+ window.clarity("set", key, String(value));
1194
+ }
1195
+ }
1196
+ return this.buildSuccess(200);
1197
+ }
1198
+ };
1199
+
1095
1200
  // src/mytart.ts
1096
1201
  function createProvider(config) {
1097
1202
  switch (config.provider) {
@@ -1109,6 +1214,8 @@ function createProvider(config) {
1109
1214
  return new PostHogProvider(config);
1110
1215
  case "meta-pixel":
1111
1216
  return new MetaPixelProvider(config);
1217
+ case "clarity":
1218
+ return new ClarityProvider(config);
1112
1219
  default: {
1113
1220
  const exhaustive = config;
1114
1221
  throw new Error(`Unknown provider: ${exhaustive.provider}`);
@@ -1120,7 +1227,17 @@ var Mytart = class {
1120
1227
  this.config = config;
1121
1228
  this.providers = config.providers.filter((c) => c.enabled === true).map(createProvider);
1122
1229
  }
1230
+ /**
1231
+ * Returns `true` when `ignoreBots` is enabled and the given (or detected)
1232
+ * User-Agent belongs to a known bot or crawler.
1233
+ */
1234
+ isBotRequest(userAgent) {
1235
+ if (!this.config.ignoreBots) return false;
1236
+ const ua = userAgent ?? (typeof navigator !== "undefined" ? navigator.userAgent : void 0);
1237
+ return ua ? (0, import_bot_detection.isBot)(ua) : false;
1238
+ }
1123
1239
  async track(options) {
1240
+ if (this.isBotRequest(options.context?.userAgent)) return [];
1124
1241
  const enriched = {
1125
1242
  userId: this.config.defaultUserId,
1126
1243
  anonymousId: this.config.defaultAnonymousId,
@@ -1129,9 +1246,11 @@ var Mytart = class {
1129
1246
  return Promise.all(this.providers.map((p) => p.track(enriched)));
1130
1247
  }
1131
1248
  async identify(options) {
1249
+ if (this.isBotRequest()) return [];
1132
1250
  return Promise.all(this.providers.map((p) => p.identify(options)));
1133
1251
  }
1134
1252
  async page(options) {
1253
+ if (this.isBotRequest()) return [];
1135
1254
  const enriched = {
1136
1255
  userId: this.config.defaultUserId,
1137
1256
  anonymousId: this.config.defaultAnonymousId,
@@ -1169,6 +1288,7 @@ var Mytart = class {
1169
1288
  0 && (module.exports = {
1170
1289
  AmplitudeProvider,
1171
1290
  BaseProvider,
1291
+ ClarityProvider,
1172
1292
  GoogleAnalyticsProvider,
1173
1293
  MetaPixelProvider,
1174
1294
  MixpanelProvider,
package/dist/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ // src/mytart.ts
2
+ import { isBot } from "ua-parser-js/bot-detection";
3
+
1
4
  // src/providers/base.ts
2
5
  var BaseProvider = class {
3
6
  /**
@@ -1048,6 +1051,107 @@ var MetaPixelProvider = class extends BaseProvider {
1048
1051
  }
1049
1052
  };
1050
1053
 
1054
+ // src/providers/clarity.ts
1055
+ var CLARITY_SCRIPT_BASE = "https://www.clarity.ms/tag/";
1056
+ var ClarityProvider = class extends BaseProvider {
1057
+ constructor(config) {
1058
+ super();
1059
+ this.name = "clarity";
1060
+ this.clarityReady = null;
1061
+ this.config = config;
1062
+ }
1063
+ /**
1064
+ * Injects the official Clarity tracking snippet. Mirrors:
1065
+ *
1066
+ * (function(c,l,a,r,i,t,y){
1067
+ * c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
1068
+ * t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
1069
+ * y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
1070
+ * })(window, document, "clarity", "script", "PROJECT_ID");
1071
+ */
1072
+ initClarity() {
1073
+ if (typeof window === "undefined" || typeof document === "undefined") {
1074
+ return Promise.resolve();
1075
+ }
1076
+ if (!window.clarity) {
1077
+ const q = [];
1078
+ const fn = function clarity(...args) {
1079
+ q.push(args);
1080
+ };
1081
+ fn.q = q;
1082
+ window.clarity = fn;
1083
+ }
1084
+ if (this.config.cookie) {
1085
+ window.clarity("consent");
1086
+ }
1087
+ return new Promise((resolve) => {
1088
+ const script = document.createElement("script");
1089
+ script.async = true;
1090
+ script.src = CLARITY_SCRIPT_BASE + this.config.projectId;
1091
+ script.onload = () => resolve();
1092
+ script.onerror = () => resolve();
1093
+ document.head.appendChild(script);
1094
+ });
1095
+ }
1096
+ ensureClarity() {
1097
+ if (!this.clarityReady) {
1098
+ this.clarityReady = this.initClarity();
1099
+ }
1100
+ return this.clarityReady;
1101
+ }
1102
+ async track(options) {
1103
+ if (typeof window === "undefined") {
1104
+ return this.buildSuccess(200);
1105
+ }
1106
+ await this.ensureClarity();
1107
+ window.clarity("event", options.event);
1108
+ if (options.properties) {
1109
+ for (const [key, value] of Object.entries(options.properties)) {
1110
+ window.clarity("set", key, String(value));
1111
+ }
1112
+ }
1113
+ return this.buildSuccess(200);
1114
+ }
1115
+ async identify(options) {
1116
+ if (typeof window === "undefined") {
1117
+ return this.buildSuccess(200);
1118
+ }
1119
+ await this.ensureClarity();
1120
+ const friendlyName = options.traits?.name ?? void 0;
1121
+ window.clarity("identify", options.userId, void 0, void 0, friendlyName);
1122
+ if (options.traits) {
1123
+ for (const [key, value] of Object.entries(options.traits)) {
1124
+ if (key !== "name") {
1125
+ window.clarity("set", key, String(value));
1126
+ }
1127
+ }
1128
+ }
1129
+ return this.buildSuccess(200);
1130
+ }
1131
+ async page(options) {
1132
+ if (typeof window === "undefined") {
1133
+ return this.buildSuccess(200);
1134
+ }
1135
+ await this.ensureClarity();
1136
+ window.clarity("event", "PageView");
1137
+ if (options.url) {
1138
+ window.clarity("set", "pageUrl", options.url);
1139
+ }
1140
+ if (options.name) {
1141
+ window.clarity("set", "pageName", options.name);
1142
+ }
1143
+ if (options.referrer) {
1144
+ window.clarity("set", "referrer", options.referrer);
1145
+ }
1146
+ if (options.properties) {
1147
+ for (const [key, value] of Object.entries(options.properties)) {
1148
+ window.clarity("set", key, String(value));
1149
+ }
1150
+ }
1151
+ return this.buildSuccess(200);
1152
+ }
1153
+ };
1154
+
1051
1155
  // src/mytart.ts
1052
1156
  function createProvider(config) {
1053
1157
  switch (config.provider) {
@@ -1065,6 +1169,8 @@ function createProvider(config) {
1065
1169
  return new PostHogProvider(config);
1066
1170
  case "meta-pixel":
1067
1171
  return new MetaPixelProvider(config);
1172
+ case "clarity":
1173
+ return new ClarityProvider(config);
1068
1174
  default: {
1069
1175
  const exhaustive = config;
1070
1176
  throw new Error(`Unknown provider: ${exhaustive.provider}`);
@@ -1076,7 +1182,17 @@ var Mytart = class {
1076
1182
  this.config = config;
1077
1183
  this.providers = config.providers.filter((c) => c.enabled === true).map(createProvider);
1078
1184
  }
1185
+ /**
1186
+ * Returns `true` when `ignoreBots` is enabled and the given (or detected)
1187
+ * User-Agent belongs to a known bot or crawler.
1188
+ */
1189
+ isBotRequest(userAgent) {
1190
+ if (!this.config.ignoreBots) return false;
1191
+ const ua = userAgent ?? (typeof navigator !== "undefined" ? navigator.userAgent : void 0);
1192
+ return ua ? isBot(ua) : false;
1193
+ }
1079
1194
  async track(options) {
1195
+ if (this.isBotRequest(options.context?.userAgent)) return [];
1080
1196
  const enriched = {
1081
1197
  userId: this.config.defaultUserId,
1082
1198
  anonymousId: this.config.defaultAnonymousId,
@@ -1085,9 +1201,11 @@ var Mytart = class {
1085
1201
  return Promise.all(this.providers.map((p) => p.track(enriched)));
1086
1202
  }
1087
1203
  async identify(options) {
1204
+ if (this.isBotRequest()) return [];
1088
1205
  return Promise.all(this.providers.map((p) => p.identify(options)));
1089
1206
  }
1090
1207
  async page(options) {
1208
+ if (this.isBotRequest()) return [];
1091
1209
  const enriched = {
1092
1210
  userId: this.config.defaultUserId,
1093
1211
  anonymousId: this.config.defaultAnonymousId,
@@ -1124,6 +1242,7 @@ var Mytart = class {
1124
1242
  export {
1125
1243
  AmplitudeProvider,
1126
1244
  BaseProvider,
1245
+ ClarityProvider,
1127
1246
  GoogleAnalyticsProvider,
1128
1247
  MetaPixelProvider,
1129
1248
  MixpanelProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mytart",
3
- "version": "0.5.2",
3
+ "version": "0.6.1",
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",