@thisbefine/analytics 0.2.1 → 0.3.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.
Files changed (49) hide show
  1. package/README.md +161 -3
  2. package/dist/core/analytics.d.ts +44 -2
  3. package/dist/core/analytics.d.ts.map +1 -1
  4. package/dist/core/analytics.js +154 -17
  5. package/dist/core/analytics.js.map +1 -1
  6. package/dist/core/errors.d.ts.map +1 -1
  7. package/dist/core/errors.js +3 -2
  8. package/dist/core/errors.js.map +1 -1
  9. package/dist/core/logger.d.ts +2 -1
  10. package/dist/core/logger.d.ts.map +1 -1
  11. package/dist/core/logger.js +53 -1
  12. package/dist/core/logger.js.map +1 -1
  13. package/dist/core/privacy.d.ts +50 -2
  14. package/dist/core/privacy.d.ts.map +1 -1
  15. package/dist/core/privacy.js +118 -1
  16. package/dist/core/privacy.js.map +1 -1
  17. package/dist/core/queue.d.ts +76 -3
  18. package/dist/core/queue.d.ts.map +1 -1
  19. package/dist/core/queue.js +353 -40
  20. package/dist/core/queue.js.map +1 -1
  21. package/dist/core/session.d.ts +5 -3
  22. package/dist/core/session.d.ts.map +1 -1
  23. package/dist/core/session.js +20 -5
  24. package/dist/core/session.js.map +1 -1
  25. package/dist/core/types.d.ts +150 -1
  26. package/dist/core/types.d.ts.map +1 -1
  27. package/dist/core/types.js +10 -0
  28. package/dist/core/types.js.map +1 -1
  29. package/dist/core/version.d.ts +1 -1
  30. package/dist/core/version.js +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/next.d.ts +2 -0
  35. package/dist/next.d.ts.map +1 -1
  36. package/dist/next.js +2 -3
  37. package/dist/next.js.map +1 -1
  38. package/dist/react/context.d.ts +95 -0
  39. package/dist/react/context.d.ts.map +1 -0
  40. package/dist/react/context.js +120 -0
  41. package/dist/react/context.js.map +1 -0
  42. package/dist/react/hooks.d.ts.map +1 -1
  43. package/dist/react/hooks.js +38 -34
  44. package/dist/react/hooks.js.map +1 -1
  45. package/dist/react.d.ts +2 -0
  46. package/dist/react.d.ts.map +1 -1
  47. package/dist/react.js +1 -0
  48. package/dist/react.js.map +1 -1
  49. package/package.json +1 -1
package/README.md CHANGED
@@ -169,6 +169,38 @@ analytics.log('Cache miss for user preferences', 'debug', {
169
169
  });
170
170
  ```
171
171
 
172
+ ### Lifecycle Events
173
+
174
+ Track key SaaS milestones with built-in methods. These use reserved event names (prefixed with `$`) and typed properties for consistent analytics.
175
+
176
+ ```typescript
177
+ // User lifecycle
178
+ analytics.signup({ method: 'google', plan: 'free' });
179
+ analytics.login({ method: 'passkey', isNewDevice: true });
180
+ analytics.logout({ reason: 'manual' });
181
+ analytics.accountDeleted({ reason: 'not_using', tenure: 45 });
182
+
183
+ // Subscription events
184
+ analytics.subscriptionStarted({ plan: 'pro', interval: 'monthly', mrr: 29 });
185
+ analytics.subscriptionCancelled({ plan: 'pro', reason: 'too_expensive', mrr: 29 });
186
+ analytics.subscriptionRenewed({ plan: 'pro', renewalCount: 12 });
187
+
188
+ // Plan changes
189
+ analytics.planUpgraded({ fromPlan: 'starter', toPlan: 'pro', mrrChange: 20 });
190
+ analytics.planDowngraded({ fromPlan: 'pro', toPlan: 'starter', reason: 'budget' });
191
+
192
+ // Trials
193
+ analytics.trialStarted({ plan: 'pro', trialDays: 14 });
194
+ analytics.trialEnded({ plan: 'pro', converted: true });
195
+
196
+ // Team/collaboration
197
+ analytics.inviteSent({ inviteEmail: 'teammate@company.com', role: 'editor' });
198
+ analytics.inviteAccepted({ invitedBy: 'user_123', role: 'editor' });
199
+
200
+ // Feature adoption
201
+ analytics.featureActivated({ feature: 'dark_mode', isFirstTime: true });
202
+ ```
203
+
172
204
  ### Privacy Controls
173
205
 
174
206
  Because being creepy is bad for business.
@@ -187,6 +219,36 @@ if (analytics.isOptedOut()) {
187
219
  analytics.optIn();
188
220
  ```
189
221
 
222
+ ### Consent Management
223
+
224
+ Granular control over tracking categories for GDPR/CCPA compliance.
225
+
226
+ ```typescript
227
+ // Configure consent categories on init
228
+ const analytics = createAnalytics({
229
+ apiKey: 'tbf_xxx',
230
+ consent: {
231
+ categories: ['analytics', 'marketing', 'functional'],
232
+ defaultConsent: false, // Don't track until explicit consent
233
+ },
234
+ });
235
+
236
+ // Check if a category is enabled
237
+ if (analytics.hasConsent('marketing')) {
238
+ // Show personalized content
239
+ }
240
+
241
+ // Get all consented categories
242
+ const categories = analytics.getConsentedCategories();
243
+
244
+ // Set consent (replaces current consent)
245
+ analytics.setConsent(['analytics', 'functional']);
246
+
247
+ // Grant/revoke individual categories
248
+ analytics.grantConsent('marketing');
249
+ analytics.revokeConsent('marketing');
250
+ ```
251
+
190
252
  ## React Hooks
191
253
 
192
254
  All hooks are available from both `@thisbefine/analytics/react` and `@thisbefine/analytics/next`.
@@ -289,9 +351,10 @@ const analytics = createAnalytics({
289
351
  // Required
290
352
  apiKey: 'tbf_xxx',
291
353
 
292
- // Optional
354
+ // Optional - Basic
293
355
  host: 'https://thisbefine.com', // API endpoint
294
356
  debug: false, // Console logging
357
+ structuredLogging: false, // Output debug logs as JSON
295
358
  flushAt: 20, // Batch size before sending
296
359
  flushInterval: 10000, // Ms between flushes
297
360
  sessionTimeout: 1800000, // 30 min session timeout
@@ -299,12 +362,46 @@ const analytics = createAnalytics({
299
362
  respectDNT: true, // Honor Do Not Track
300
363
  maxRetries: 3, // Retry failed requests
301
364
 
302
- // Error tracking config
365
+ // Error tracking
303
366
  errors: {
304
367
  enabled: true,
305
368
  captureUnhandled: true,
306
369
  captureConsoleErrors: true,
307
370
  },
371
+
372
+ // Lifecycle hooks
373
+ onFlushError: (error, failedEvents) => {
374
+ console.error('Failed to send events:', error);
375
+ // Log to your error tracking service
376
+ },
377
+ beforeSend: (event) => {
378
+ // Scrub PII from event properties
379
+ if (event.type === 'track' && event.properties?.email) {
380
+ const { email, ...rest } = event.properties;
381
+ return { ...event, properties: rest };
382
+ }
383
+ // Return null to discard the event
384
+ return event;
385
+ },
386
+
387
+ // Queue persistence (crash recovery)
388
+ persistQueue: true, // Save queue to localStorage
389
+ maxPersistedEvents: 1000, // Max events to persist
390
+
391
+ // Privacy & consent
392
+ anonymousIdMaxAge: 2592000000, // Rotate anonymous ID every 30 days
393
+ consent: {
394
+ categories: ['analytics', 'marketing', 'functional'],
395
+ defaultConsent: true, // Track by default (respects DNT)
396
+ },
397
+
398
+ // Rate limiting & sampling
399
+ maxEventsPerSecond: 100, // Drop events exceeding this rate
400
+ sampleRate: 1, // 1 = 100%, 0.5 = 50% of events
401
+
402
+ // Circuit breaker (stops hammering failed servers)
403
+ circuitBreakerThreshold: 5, // Open after N consecutive failures
404
+ circuitBreakerResetTimeout: 30000, // Try again after 30s
308
405
  });
309
406
  ```
310
407
 
@@ -334,12 +431,44 @@ const analytics = createAnalytics({
334
431
  | `page(name?, properties?)` | Track a page view |
335
432
  | `group(accountId, traits?)` | Associate user with account |
336
433
  | `reset()` | Clear user data (call on logout) |
337
- | `flush()` | Force send queued events |
434
+ | `flush()` | Force send queued events (returns `FlushResult`) |
435
+ | `destroy()` | Clean up resources, flush remaining events |
338
436
  | `optOut()` | Disable tracking |
339
437
  | `optIn()` | Re-enable tracking |
340
438
  | `isOptedOut()` | Check opt-out status |
341
439
  | `getUser()` | Get current user state |
342
440
 
441
+ ### Consent Methods
442
+
443
+ | Method | Description |
444
+ |--------|-------------|
445
+ | `hasConsent(category)` | Check if a category is enabled |
446
+ | `getConsentedCategories()` | Get all consented categories |
447
+ | `setConsent(categories)` | Set consent (replaces current) |
448
+ | `grantConsent(category)` | Enable a category |
449
+ | `revokeConsent(category)` | Disable a category |
450
+
451
+ Categories: `analytics` | `marketing` | `functional`
452
+
453
+ ### Lifecycle Methods
454
+
455
+ | Method | Description |
456
+ |--------|-------------|
457
+ | `signup(props?)` | Track user signup |
458
+ | `login(props?)` | Track user login |
459
+ | `logout(props?)` | Track user logout |
460
+ | `accountDeleted(props?)` | Track account deletion |
461
+ | `subscriptionStarted(props)` | Track subscription start |
462
+ | `subscriptionCancelled(props)` | Track subscription cancellation |
463
+ | `subscriptionRenewed(props)` | Track subscription renewal |
464
+ | `planUpgraded(props)` | Track plan upgrade |
465
+ | `planDowngraded(props)` | Track plan downgrade |
466
+ | `trialStarted(props)` | Track trial start |
467
+ | `trialEnded(props)` | Track trial end |
468
+ | `inviteSent(props?)` | Track invite sent |
469
+ | `inviteAccepted(props?)` | Track invite accepted |
470
+ | `featureActivated(props)` | Track feature activation |
471
+
343
472
  ### Error Tracking Methods
344
473
 
345
474
  | Method | Description |
@@ -376,11 +505,40 @@ Full type definitions included:
376
505
 
377
506
  ```typescript
378
507
  import type {
508
+ // Core types
379
509
  Analytics,
380
510
  AnalyticsConfig,
381
511
  UserState,
382
512
  UserTraits,
383
513
  AccountTraits,
514
+ FlushResult,
515
+
516
+ // Event types
517
+ AnalyticsEvent,
518
+ TrackEvent,
519
+ IdentifyEvent,
520
+ PageEvent,
521
+ GroupEvent,
522
+
523
+ // Hooks
524
+ BeforeSend,
525
+ OnFlushError,
526
+
527
+ // Lifecycle event props
528
+ SignupProps,
529
+ LoginProps,
530
+ LogoutProps,
531
+ AccountDeletedProps,
532
+ SubscriptionStartedProps,
533
+ SubscriptionCancelledProps,
534
+ SubscriptionRenewedProps,
535
+ PlanUpgradedProps,
536
+ PlanDowngradedProps,
537
+ TrialStartedProps,
538
+ TrialEndedProps,
539
+ InviteSentProps,
540
+ InviteAcceptedProps,
541
+ FeatureActivatedProps,
384
542
  } from '@thisbefine/analytics';
385
543
  ```
386
544
 
@@ -1,6 +1,6 @@
1
1
  import { type AccountDeletedProps, type FeatureActivatedProps, type InviteAcceptedProps, type InviteSentProps, type LoginProps, type LogoutProps, type PlanDowngradedProps, type PlanUpgradedProps, type SignupProps, type SubscriptionCancelledProps, type SubscriptionRenewedProps, type SubscriptionStartedProps, type TrialEndedProps, type TrialStartedProps } from "./lifecycle";
2
2
  import type { LogLevel } from "./logging";
3
- import type { AccountTraits, Analytics, AnalyticsConfig, UserState, UserTraits } from "./types";
3
+ import type { AccountTraits, Analytics, AnalyticsConfig, ConsentCategory, FlushResult, UserState, UserTraits } from "./types";
4
4
  /**
5
5
  * Main Analytics class - the primary entry point for the SDK
6
6
  *
@@ -22,11 +22,26 @@ export declare class AnalyticsImpl implements Analytics {
22
22
  private errorCapture;
23
23
  private initialized;
24
24
  private logger;
25
+ private eventTimestamps;
26
+ private rateLimitWarned;
25
27
  constructor(config: AnalyticsConfig);
26
28
  /**
27
29
  * Resolve configuration with defaults
28
30
  */
29
31
  private resolveConfig;
32
+ /**
33
+ * Queue an event, applying sampling, rate limiting and beforeSend hook if configured
34
+ */
35
+ private queueEvent;
36
+ /**
37
+ * Check if event should be sampled based on sampleRate
38
+ */
39
+ private shouldSample;
40
+ /**
41
+ * Check if event should be allowed based on rate limit
42
+ * Uses a sliding window algorithm
43
+ */
44
+ private checkRateLimit;
30
45
  /**
31
46
  * Track a custom event
32
47
  */
@@ -69,8 +84,15 @@ export declare class AnalyticsImpl implements Analytics {
69
84
  reset(): void;
70
85
  /**
71
86
  * Manually flush the event queue
87
+ * @returns FlushResult with success status, event count, and any errors
88
+ */
89
+ flush(): Promise<FlushResult>;
90
+ /**
91
+ * Destroy the analytics instance, cleaning up all resources.
92
+ * Flushes remaining events before cleanup.
93
+ * After calling this, the instance should not be used.
72
94
  */
73
- flush(): Promise<void>;
95
+ destroy(): Promise<void>;
74
96
  /**
75
97
  * Opt out of tracking
76
98
  */
@@ -83,6 +105,26 @@ export declare class AnalyticsImpl implements Analytics {
83
105
  * Check if user has opted out
84
106
  */
85
107
  isOptedOut(): boolean;
108
+ /**
109
+ * Check if a specific consent category is enabled
110
+ */
111
+ hasConsent(category: ConsentCategory): boolean;
112
+ /**
113
+ * Get all currently consented categories
114
+ */
115
+ getConsentedCategories(): ConsentCategory[];
116
+ /**
117
+ * Set consent for specific categories
118
+ */
119
+ setConsent(categories: ConsentCategory[]): void;
120
+ /**
121
+ * Grant consent for a specific category
122
+ */
123
+ grantConsent(category: ConsentCategory): void;
124
+ /**
125
+ * Revoke consent for a specific category
126
+ */
127
+ revokeConsent(category: ConsentCategory): void;
86
128
  /**
87
129
  * Get current user state
88
130
  */
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/core/analytics.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAEpB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAM1C,OAAO,KAAK,EACX,aAAa,EACb,SAAS,EACT,eAAe,EAOf,SAAS,EACT,UAAU,EACV,MAAM,SAAS,CAAC;AASjB;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,YAAW,SAAS;IAC9C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,eAAe;IAkCnC;;OAEG;IACH,OAAO,CAAC,aAAa;IAerB;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IA+BhE;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAmCnD;;OAEG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IA+B/D;;OAEG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI;IAmCtD;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAKvE;;OAEG;IACH,cAAc,CACb,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,EACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI;IAKP;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE;QACzB,IAAI,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;QAChE,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC/B,GAAG,IAAI;IAIR;;OAEG;IACH,GAAG,CACF,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,QAAQ,EACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAKP;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,UAAU,IAAI,OAAO;IAIrB;;OAEG;IACH,OAAO,IAAI,SAAS;IAIpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAanB;;OAEG;IACH,OAAO,CAAC,UAAU;IA+BlB;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI;IAIjC;;OAEG;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI;IAI/B;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI;IAIjC;;OAEG;IACH,cAAc,CAAC,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI;IAIjD;;OAEG;IACH,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,IAAI;IAO1D;;OAEG;IACH,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,IAAI;IAO9D;;OAEG;IACH,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,IAAI;IAO1D;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAO5C;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAOhD;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAO5C;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAOxC;;OAEG;IACH,UAAU,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI;IAIzC;;OAEG;IACH,cAAc,CAAC,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI;IAIjD;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI;CAMpD;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,eAAe,KAAG,SAEzD,CAAC;AAIF;;GAEG;AACH,eAAO,MAAM,YAAY,QAAO,SAAS,GAAG,IAE3C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,eAAe,KAAG,SAQvD,CAAC"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/core/analytics.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,7 @@ import { log as sendLog } from "./logging";
5
5
  import { Privacy } from "./privacy";
6
6
  import { Queue } from "./queue";
7
7
  import { Session } from "./session";
8
- import { Storage } from "./storage";
8
+ import { generateId, Storage } from "./storage";
9
9
  import { DEFAULT_CONFIG, LIBRARY_INFO } from "./types";
10
10
  import { validateAccountId, validateEventName, validateProperties, validateUserId, } from "./validation";
11
11
  /**
@@ -24,11 +24,13 @@ export class AnalyticsImpl {
24
24
  constructor(config) {
25
25
  this.errorCapture = null;
26
26
  this.initialized = false;
27
+ this.eventTimestamps = [];
28
+ this.rateLimitWarned = false;
27
29
  this.config = this.resolveConfig(config);
28
- this.logger = createLogger("Analytics", this.config.debug);
30
+ this.logger = createLogger("Analytics", this.config.debug, this.config.structuredLogging);
29
31
  this.storage = new Storage(this.config.cookieDomain);
30
- this.session = new Session(this.storage, this.config.sessionTimeout, this.config.debug);
31
- this.privacy = new Privacy(this.storage, this.config.respectDNT, this.config.debug);
32
+ this.session = new Session(this.storage, this.config.sessionTimeout, this.config.debug, this.config.anonymousIdMaxAge);
33
+ this.privacy = new Privacy(this.storage, this.config.respectDNT, this.config.debug, this.config.consent);
32
34
  this.queue = new Queue(this.config);
33
35
  if (this.config.errors?.enabled !== false) {
34
36
  this.errorCapture = new ErrorCapture(this.config, this.session, this.config.errors);
@@ -57,8 +59,84 @@ export class AnalyticsImpl {
57
59
  respectDNT: config.respectDNT ?? DEFAULT_CONFIG.respectDNT,
58
60
  maxRetries: config.maxRetries ?? DEFAULT_CONFIG.maxRetries,
59
61
  errors: config.errors,
62
+ onFlushError: config.onFlushError,
63
+ persistQueue: config.persistQueue ?? DEFAULT_CONFIG.persistQueue,
64
+ maxPersistedEvents: config.maxPersistedEvents ?? DEFAULT_CONFIG.maxPersistedEvents,
65
+ beforeSend: config.beforeSend,
66
+ anonymousIdMaxAge: config.anonymousIdMaxAge ?? DEFAULT_CONFIG.anonymousIdMaxAge,
67
+ circuitBreakerThreshold: config.circuitBreakerThreshold ??
68
+ DEFAULT_CONFIG.circuitBreakerThreshold,
69
+ circuitBreakerResetTimeout: config.circuitBreakerResetTimeout ??
70
+ DEFAULT_CONFIG.circuitBreakerResetTimeout,
71
+ consent: config.consent,
72
+ maxEventsPerSecond: config.maxEventsPerSecond ?? DEFAULT_CONFIG.maxEventsPerSecond,
73
+ sampleRate: Math.max(0, Math.min(1, config.sampleRate ?? DEFAULT_CONFIG.sampleRate)),
74
+ structuredLogging: config.structuredLogging ?? DEFAULT_CONFIG.structuredLogging,
60
75
  };
61
76
  }
77
+ /**
78
+ * Queue an event, applying sampling, rate limiting and beforeSend hook if configured
79
+ */
80
+ queueEvent(event) {
81
+ if (!this.shouldSample()) {
82
+ this.logger.log("Event dropped by sampling");
83
+ return;
84
+ }
85
+ if (!this.checkRateLimit()) {
86
+ return;
87
+ }
88
+ let finalEvent = event;
89
+ if (this.config.beforeSend) {
90
+ try {
91
+ finalEvent = this.config.beforeSend(event);
92
+ if (finalEvent === null) {
93
+ this.logger.log("Event discarded by beforeSend hook:", event.type);
94
+ return;
95
+ }
96
+ }
97
+ catch (error) {
98
+ this.logger.warn("beforeSend hook threw an error:", error);
99
+ finalEvent = event;
100
+ }
101
+ }
102
+ this.queue.push(finalEvent);
103
+ }
104
+ /**
105
+ * Check if event should be sampled based on sampleRate
106
+ */
107
+ shouldSample() {
108
+ const sampleRate = this.config.sampleRate;
109
+ if (sampleRate >= 1)
110
+ return true;
111
+ if (sampleRate <= 0)
112
+ return false;
113
+ return Math.random() < sampleRate;
114
+ }
115
+ /**
116
+ * Check if event should be allowed based on rate limit
117
+ * Uses a sliding window algorithm
118
+ */
119
+ checkRateLimit() {
120
+ const maxEvents = this.config.maxEventsPerSecond;
121
+ if (maxEvents <= 0) {
122
+ return true;
123
+ }
124
+ const now = Date.now();
125
+ const windowStart = now - 1000;
126
+ this.eventTimestamps = this.eventTimestamps.filter((t) => t > windowStart);
127
+ if (this.eventTimestamps.length >= maxEvents) {
128
+ if (!this.rateLimitWarned) {
129
+ this.logger.warn(`Rate limit exceeded (${maxEvents} events/second). Events are being dropped.`);
130
+ this.rateLimitWarned = true;
131
+ setTimeout(() => {
132
+ this.rateLimitWarned = false;
133
+ }, 5000);
134
+ }
135
+ return false;
136
+ }
137
+ this.eventTimestamps.push(now);
138
+ return true;
139
+ }
62
140
  /**
63
141
  * Track a custom event
64
142
  */
@@ -77,6 +155,7 @@ export class AnalyticsImpl {
77
155
  }
78
156
  const trackEvent = {
79
157
  type: "track",
158
+ messageId: generateId(),
80
159
  event,
81
160
  properties,
82
161
  timestamp: new Date().toISOString(),
@@ -86,7 +165,7 @@ export class AnalyticsImpl {
86
165
  accountId: this.session.getAccountId(),
87
166
  context: this.getContext(),
88
167
  };
89
- this.queue.push(trackEvent);
168
+ this.queueEvent(trackEvent);
90
169
  this.session.updateLastActivity();
91
170
  }
92
171
  /**
@@ -111,6 +190,7 @@ export class AnalyticsImpl {
111
190
  }
112
191
  const identifyEvent = {
113
192
  type: "identify",
193
+ messageId: generateId(),
114
194
  userId,
115
195
  traits,
116
196
  timestamp: new Date().toISOString(),
@@ -119,7 +199,7 @@ export class AnalyticsImpl {
119
199
  accountId: this.session.getAccountId(),
120
200
  context: this.getContext(),
121
201
  };
122
- this.queue.push(identifyEvent);
202
+ this.queueEvent(identifyEvent);
123
203
  this.session.updateLastActivity();
124
204
  }
125
205
  /**
@@ -137,6 +217,7 @@ export class AnalyticsImpl {
137
217
  const referrer = typeof document !== "undefined" ? document.referrer : undefined;
138
218
  const pageEvent = {
139
219
  type: "page",
220
+ messageId: generateId(),
140
221
  name,
141
222
  properties,
142
223
  url,
@@ -148,7 +229,7 @@ export class AnalyticsImpl {
148
229
  accountId: this.session.getAccountId(),
149
230
  context: this.getContext(),
150
231
  };
151
- this.queue.push(pageEvent);
232
+ this.queueEvent(pageEvent);
152
233
  this.session.updateLastActivity();
153
234
  }
154
235
  /**
@@ -173,6 +254,7 @@ export class AnalyticsImpl {
173
254
  }
174
255
  const groupEvent = {
175
256
  type: "group",
257
+ messageId: generateId(),
176
258
  accountId,
177
259
  traits,
178
260
  timestamp: new Date().toISOString(),
@@ -181,7 +263,7 @@ export class AnalyticsImpl {
181
263
  sessionId: this.session.getSessionId(),
182
264
  context: this.getContext(),
183
265
  };
184
- this.queue.push(groupEvent);
266
+ this.queueEvent(groupEvent);
185
267
  this.session.updateLastActivity();
186
268
  }
187
269
  /**
@@ -225,9 +307,26 @@ export class AnalyticsImpl {
225
307
  }
226
308
  /**
227
309
  * Manually flush the event queue
310
+ * @returns FlushResult with success status, event count, and any errors
228
311
  */
229
312
  async flush() {
230
- await this.queue.flush();
313
+ return this.queue.flush();
314
+ }
315
+ /**
316
+ * Destroy the analytics instance, cleaning up all resources.
317
+ * Flushes remaining events before cleanup.
318
+ * After calling this, the instance should not be used.
319
+ */
320
+ async destroy() {
321
+ this.logger.log("Destroying analytics instance...");
322
+ await this.queue.destroy();
323
+ this.session.destroy();
324
+ this.errorCapture?.uninstall();
325
+ this.initialized = false;
326
+ if (globalInstance === this) {
327
+ globalInstance = null;
328
+ }
329
+ this.logger.log("Analytics instance destroyed");
231
330
  }
232
331
  /**
233
332
  * Opt out of tracking
@@ -247,6 +346,36 @@ export class AnalyticsImpl {
247
346
  isOptedOut() {
248
347
  return this.privacy.isOptedOut();
249
348
  }
349
+ /**
350
+ * Check if a specific consent category is enabled
351
+ */
352
+ hasConsent(category) {
353
+ return this.privacy.hasConsent(category);
354
+ }
355
+ /**
356
+ * Get all currently consented categories
357
+ */
358
+ getConsentedCategories() {
359
+ return this.privacy.getConsentedCategories();
360
+ }
361
+ /**
362
+ * Set consent for specific categories
363
+ */
364
+ setConsent(categories) {
365
+ this.privacy.setConsent(categories);
366
+ }
367
+ /**
368
+ * Grant consent for a specific category
369
+ */
370
+ grantConsent(category) {
371
+ this.privacy.grantConsent(category);
372
+ }
373
+ /**
374
+ * Revoke consent for a specific category
375
+ */
376
+ revokeConsent(category) {
377
+ this.privacy.revokeConsent(category);
378
+ }
250
379
  /**
251
380
  * Get current user state
252
381
  */
@@ -323,7 +452,8 @@ export class AnalyticsImpl {
323
452
  */
324
453
  subscriptionStarted(props) {
325
454
  if (!props?.plan) {
326
- this.logger.warn("subscriptionStarted: plan is required");
455
+ this.logger.warn("subscriptionStarted: plan is required, event not sent");
456
+ return;
327
457
  }
328
458
  this.track(LIFECYCLE_EVENTS.SUBSCRIPTION_STARTED, props);
329
459
  }
@@ -332,7 +462,8 @@ export class AnalyticsImpl {
332
462
  */
333
463
  subscriptionCancelled(props) {
334
464
  if (!props?.plan) {
335
- this.logger.warn("subscriptionCancelled: plan is required");
465
+ this.logger.warn("subscriptionCancelled: plan is required, event not sent");
466
+ return;
336
467
  }
337
468
  this.track(LIFECYCLE_EVENTS.SUBSCRIPTION_CANCELLED, props);
338
469
  }
@@ -341,7 +472,8 @@ export class AnalyticsImpl {
341
472
  */
342
473
  subscriptionRenewed(props) {
343
474
  if (!props?.plan) {
344
- this.logger.warn("subscriptionRenewed: plan is required");
475
+ this.logger.warn("subscriptionRenewed: plan is required, event not sent");
476
+ return;
345
477
  }
346
478
  this.track(LIFECYCLE_EVENTS.SUBSCRIPTION_RENEWED, props);
347
479
  }
@@ -350,7 +482,8 @@ export class AnalyticsImpl {
350
482
  */
351
483
  planUpgraded(props) {
352
484
  if (!props?.fromPlan || !props?.toPlan) {
353
- this.logger.warn("planUpgraded: fromPlan and toPlan are required");
485
+ this.logger.warn("planUpgraded: fromPlan and toPlan are required, event not sent");
486
+ return;
354
487
  }
355
488
  this.track(LIFECYCLE_EVENTS.PLAN_UPGRADED, props);
356
489
  }
@@ -359,7 +492,8 @@ export class AnalyticsImpl {
359
492
  */
360
493
  planDowngraded(props) {
361
494
  if (!props?.fromPlan || !props?.toPlan) {
362
- this.logger.warn("planDowngraded: fromPlan and toPlan are required");
495
+ this.logger.warn("planDowngraded: fromPlan and toPlan are required, event not sent");
496
+ return;
363
497
  }
364
498
  this.track(LIFECYCLE_EVENTS.PLAN_DOWNGRADED, props);
365
499
  }
@@ -368,7 +502,8 @@ export class AnalyticsImpl {
368
502
  */
369
503
  trialStarted(props) {
370
504
  if (!props?.plan) {
371
- this.logger.warn("trialStarted: plan is required");
505
+ this.logger.warn("trialStarted: plan is required, event not sent");
506
+ return;
372
507
  }
373
508
  this.track(LIFECYCLE_EVENTS.TRIAL_STARTED, props);
374
509
  }
@@ -377,7 +512,8 @@ export class AnalyticsImpl {
377
512
  */
378
513
  trialEnded(props) {
379
514
  if (!props?.plan || props?.converted === undefined) {
380
- this.logger.warn("trialEnded: plan and converted are required");
515
+ this.logger.warn("trialEnded: plan and converted are required, event not sent");
516
+ return;
381
517
  }
382
518
  this.track(LIFECYCLE_EVENTS.TRIAL_ENDED, props);
383
519
  }
@@ -398,7 +534,8 @@ export class AnalyticsImpl {
398
534
  */
399
535
  featureActivated(props) {
400
536
  if (!props?.feature) {
401
- this.logger.warn("featureActivated: feature is required");
537
+ this.logger.warn("featureActivated: feature is required, event not sent");
538
+ return;
402
539
  }
403
540
  this.track(LIFECYCLE_EVENTS.FEATURE_ACTIVATED, props);
404
541
  }