aurea-tracking-sdk 1.1.2 → 1.3.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/dist/index.mjs CHANGED
@@ -17,6 +17,15 @@ var AureaSDK = class {
17
17
  fcp: false,
18
18
  ttfb: false
19
19
  };
20
+ // NEW: Funnel tracking
21
+ this.currentStage = "awareness";
22
+ this.stageHistory = [];
23
+ this.microConversions = [];
24
+ this.isInCheckout = false;
25
+ // NEW: Event categorization tracking
26
+ this.eventCategoryStats = /* @__PURE__ */ new Map();
27
+ // NEW: Track which events have been fired (for trackOnce behavior)
28
+ this.trackedOnceEvents = /* @__PURE__ */ new Set();
20
29
  this.config = {
21
30
  apiUrl: "http://localhost:3000/api",
22
31
  debug: false,
@@ -70,11 +79,13 @@ var AureaSDK = class {
70
79
  this.startPurchasePolling();
71
80
  this.trackWebVitals();
72
81
  this.trackSessionTiming();
82
+ this.initializeFunnelTracking();
73
83
  if (this.config.debug) {
74
84
  console.log("[Aurea SDK] Initialized", {
75
85
  sessionId: this.sessionId,
76
86
  anonymousId: this.anonymousId,
77
- userId: this.userId
87
+ userId: this.userId,
88
+ currentStage: this.currentStage
78
89
  });
79
90
  }
80
91
  }
@@ -152,6 +163,372 @@ var AureaSDK = class {
152
163
  console.log("[Aurea SDK] Conversion tracked:", data);
153
164
  }
154
165
  }
166
+ /**
167
+ * Register custom event categories
168
+ * Allows users to define their own event categorization and auto-stage progression
169
+ *
170
+ * @example
171
+ * aurea.registerEventCategories({
172
+ * 'video_started': {
173
+ * category: 'engagement',
174
+ * advanceTo: 'interest',
175
+ * value: 30,
176
+ * description: 'User started watching sales video'
177
+ * },
178
+ * 'pricing_viewed': {
179
+ * category: 'intent',
180
+ * advanceTo: 'desire',
181
+ * value: 60
182
+ * },
183
+ * 'buy_button_clicked': {
184
+ * category: 'conversion',
185
+ * value: 90
186
+ * }
187
+ * })
188
+ */
189
+ registerEventCategories(categories) {
190
+ if (!this.config.eventCategories) {
191
+ this.config.eventCategories = {};
192
+ }
193
+ this.config.eventCategories = {
194
+ ...this.config.eventCategories,
195
+ ...categories
196
+ };
197
+ if (this.config.debug) {
198
+ console.log("[Aurea SDK] Event categories registered:", categories);
199
+ }
200
+ }
201
+ /**
202
+ * Track a categorized event with automatic stage progression
203
+ * This is the recommended way to track events - user defines their own event names
204
+ *
205
+ * @param eventName - User-defined event name (e.g., 'video_started', 'pricing_clicked')
206
+ * @param properties - Additional event properties
207
+ * @param options - Override category/value/stage for this specific event
208
+ *
209
+ * @example
210
+ * // Using pre-registered category
211
+ * aurea.trackEvent('video_started', { duration: 120 })
212
+ *
213
+ * // One-off event with inline category
214
+ * aurea.trackEvent('custom_action', { foo: 'bar' }, {
215
+ * category: 'engagement',
216
+ * value: 40,
217
+ * advanceTo: 'interest'
218
+ * })
219
+ */
220
+ trackEvent(eventName, properties, options) {
221
+ const categoryConfig = this.config.eventCategories?.[eventName];
222
+ const category = options?.category || categoryConfig?.category || "custom";
223
+ const value = options?.value ?? categoryConfig?.value ?? 50;
224
+ const advanceTo = options?.advanceTo || categoryConfig?.advanceTo;
225
+ const description = options?.description || categoryConfig?.description;
226
+ const trackOnce = categoryConfig?.trackOnce ?? false;
227
+ if (trackOnce) {
228
+ if (this.trackedOnceEvents.has(eventName)) {
229
+ if (this.config.debug) {
230
+ console.log(`[Aurea SDK] Event skipped (already tracked once): ${eventName}`);
231
+ }
232
+ return;
233
+ }
234
+ this.trackedOnceEvents.add(eventName);
235
+ }
236
+ this.eventCategoryStats.set(
237
+ category,
238
+ (this.eventCategoryStats.get(category) || 0) + 1
239
+ );
240
+ const autoAdvance = this.config.autoAdvanceStages !== false;
241
+ if (autoAdvance && advanceTo && advanceTo !== this.currentStage) {
242
+ this.enterStage(advanceTo);
243
+ }
244
+ this.track(eventName, {
245
+ ...properties,
246
+ // Add categorization metadata
247
+ _category: category,
248
+ _value: value,
249
+ _description: description,
250
+ _currentStage: this.currentStage,
251
+ _categoryStats: Object.fromEntries(this.eventCategoryStats),
252
+ _trackedOnce: trackOnce
253
+ // Include for backend reference
254
+ });
255
+ if (this.config.debug) {
256
+ console.log(`[Aurea SDK] Event tracked: ${eventName} [${category}] (value: ${value}, stage: ${this.currentStage}${trackOnce ? ", once-only" : ""})`);
257
+ }
258
+ }
259
+ /**
260
+ * Get current event category statistics
261
+ */
262
+ getCategoryStats() {
263
+ return Object.fromEntries(this.eventCategoryStats);
264
+ }
265
+ /**
266
+ * Get current funnel stage
267
+ */
268
+ getCurrentStage() {
269
+ return this.currentStage;
270
+ }
271
+ /**
272
+ * Get stage history
273
+ */
274
+ getStageHistory() {
275
+ return this.stageHistory.map((entry, index) => {
276
+ const nextEntry = this.stageHistory[index + 1];
277
+ const durationMs = nextEntry ? nextEntry.enteredAt - entry.enteredAt : Date.now() - entry.enteredAt;
278
+ return {
279
+ stage: entry.stage,
280
+ enteredAt: entry.enteredAt,
281
+ durationMs
282
+ };
283
+ });
284
+ }
285
+ /**
286
+ * Enter a new funnel stage
287
+ */
288
+ enterStage(stage) {
289
+ const previousStage = this.currentStage;
290
+ this.currentStage = stage;
291
+ this.stageHistory.push({
292
+ stage,
293
+ enteredAt: Date.now()
294
+ });
295
+ this.track("funnel_stage_entered", {
296
+ stage,
297
+ previousStage,
298
+ stageHistory: this.stageHistory,
299
+ timeInPreviousStage: this.getTimeInCurrentStage(previousStage)
300
+ });
301
+ if (this.config.debug) {
302
+ console.log(`[Aurea SDK] Entered funnel stage: ${previousStage} \u2192 ${stage}`);
303
+ }
304
+ }
305
+ /**
306
+ * Track micro-conversion (engagement signals)
307
+ *
308
+ * @deprecated Use trackEvent() instead for more flexibility
309
+ * This method is kept for backward compatibility
310
+ *
311
+ * @param type - Type of micro-conversion (e.g., 'video_played', 'faq_opened')
312
+ * @param value - Impact score 0-100 (how strong this signal is)
313
+ * @param properties - Additional metadata
314
+ * @param autoAdvanceStage - Whether to automatically advance funnel stage (default: true)
315
+ */
316
+ trackMicroConversion(type, value = 50, properties, autoAdvanceStage = true) {
317
+ const microConversion = {
318
+ type,
319
+ value,
320
+ properties
321
+ };
322
+ this.microConversions.push(microConversion);
323
+ const categoryConfig = this.config.eventCategories?.[type];
324
+ if (autoAdvanceStage) {
325
+ if (categoryConfig?.advanceTo && categoryConfig.advanceTo !== this.currentStage) {
326
+ this.enterStage(categoryConfig.advanceTo);
327
+ } else {
328
+ const suggestedStage = this.getSuggestedStageFromMicroConversion(type, value);
329
+ if (suggestedStage && suggestedStage !== this.currentStage) {
330
+ this.enterStage(suggestedStage);
331
+ }
332
+ }
333
+ }
334
+ this.track("micro_conversion", {
335
+ microConversionType: type,
336
+ value,
337
+ category: categoryConfig?.category || "custom",
338
+ currentStage: this.currentStage,
339
+ totalMicroConversions: this.microConversions.length,
340
+ ...properties
341
+ });
342
+ if (this.config.debug) {
343
+ console.log(`[Aurea SDK] Micro-conversion: ${type} (value: ${value}, stage: ${this.currentStage})`);
344
+ }
345
+ }
346
+ /**
347
+ * Determine suggested funnel stage based on micro-conversion type
348
+ * This uses common patterns to automatically progress users through the funnel
349
+ */
350
+ getSuggestedStageFromMicroConversion(type, value) {
351
+ const currentStageIndex = this.getFunnelStageOrder().indexOf(this.currentStage);
352
+ const interestSignals = [
353
+ "video_played",
354
+ "video_started",
355
+ "video_25_percent",
356
+ "video_50_percent",
357
+ "scroll_depth_25",
358
+ "scroll_depth_50",
359
+ "page_section_viewed",
360
+ "testimonial_viewed",
361
+ "feature_section_viewed"
362
+ ];
363
+ const desireSignals = [
364
+ "video_75_percent",
365
+ "video_completed",
366
+ "faq_opened",
367
+ "pricing_viewed",
368
+ "cta_section_viewed",
369
+ "scroll_depth_75",
370
+ "comparison_table_viewed",
371
+ "guarantee_section_viewed",
372
+ "bonus_section_viewed"
373
+ ];
374
+ const checkoutIntentSignals = [
375
+ "buy_button_hovered",
376
+ "checkout_button_clicked",
377
+ "add_to_cart",
378
+ "pricing_tier_selected"
379
+ ];
380
+ if (interestSignals.includes(type) && currentStageIndex < 1) {
381
+ return "interest";
382
+ }
383
+ if (desireSignals.includes(type) && currentStageIndex < 2) {
384
+ return "desire";
385
+ }
386
+ if (checkoutIntentSignals.includes(type) && currentStageIndex < 3) {
387
+ if (this.config.debug) {
388
+ console.log(`[Aurea SDK] High checkout intent detected: ${type}`);
389
+ }
390
+ }
391
+ if (value >= 80 && currentStageIndex < 2) {
392
+ return "desire";
393
+ }
394
+ return null;
395
+ }
396
+ /**
397
+ * Get funnel stage order for comparison
398
+ */
399
+ getFunnelStageOrder() {
400
+ return ["awareness", "interest", "desire", "checkout", "purchase", "abandoned"];
401
+ }
402
+ /**
403
+ * Track checkout started (user clicked buy button)
404
+ * This DOES NOT end the session - preserves context for when user returns
405
+ */
406
+ checkoutStarted(product) {
407
+ this.isInCheckout = true;
408
+ this.checkoutStartedAt = Date.now();
409
+ this.enterStage("checkout");
410
+ const checkoutContext = {
411
+ originalSessionId: this.sessionId,
412
+ anonymousId: this.anonymousId,
413
+ checkoutStartedAt: this.checkoutStartedAt,
414
+ product,
415
+ currentStage: this.currentStage,
416
+ priorStages: this.stageHistory.map((h) => h.stage),
417
+ utmSource: this.getUTMParam("utm_source"),
418
+ utmCampaign: this.getUTMParam("utm_campaign"),
419
+ utmMedium: this.getUTMParam("utm_medium")
420
+ };
421
+ if (typeof localStorage !== "undefined") {
422
+ localStorage.setItem("aurea_checkout_context", JSON.stringify(checkoutContext));
423
+ }
424
+ this.track("checkout_started", {
425
+ productId: product.productId,
426
+ productName: product.productName,
427
+ price: product.price,
428
+ currency: product.currency || "USD",
429
+ quantity: product.quantity || 1,
430
+ variant: product.variant,
431
+ sessionDurationSoFar: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
432
+ microConversionsCount: this.microConversions.length,
433
+ priorStages: this.stageHistory.map((h) => h.stage)
434
+ });
435
+ if (this.config.debug) {
436
+ console.log("[Aurea SDK] Checkout started:", product);
437
+ console.log("[Aurea SDK] Session context preserved for return journey");
438
+ }
439
+ }
440
+ /**
441
+ * Track checkout completed (purchase successful)
442
+ * Links back to original session if user returned from external checkout
443
+ */
444
+ checkoutCompleted(data) {
445
+ let checkoutContext = null;
446
+ let checkoutDuration;
447
+ if (typeof localStorage !== "undefined") {
448
+ const stored = localStorage.getItem("aurea_checkout_context");
449
+ if (stored) {
450
+ try {
451
+ checkoutContext = JSON.parse(stored);
452
+ checkoutDuration = Math.floor((Date.now() - checkoutContext.checkoutStartedAt) / 1e3);
453
+ localStorage.removeItem("aurea_checkout_context");
454
+ } catch (e) {
455
+ console.error("[Aurea SDK] Failed to parse checkout context:", e);
456
+ }
457
+ }
458
+ }
459
+ this.enterStage("purchase");
460
+ this.track("checkout_completed", {
461
+ orderId: data.orderId,
462
+ revenue: data.revenue,
463
+ currency: data.currency || "USD",
464
+ paymentMethod: data.paymentMethod,
465
+ products: data.products,
466
+ // Link to original session if available
467
+ originalSessionId: checkoutContext?.originalSessionId,
468
+ checkoutDuration,
469
+ totalSessionDuration: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
470
+ // Attribution data from original session
471
+ originalUtmSource: checkoutContext?.utmSource,
472
+ originalUtmCampaign: checkoutContext?.utmCampaign,
473
+ originalUtmMedium: checkoutContext?.utmMedium,
474
+ priorStages: checkoutContext?.priorStages || this.stageHistory.map((h) => h.stage),
475
+ // Engagement data
476
+ microConversionsCount: this.microConversions.length,
477
+ microConversions: this.microConversions
478
+ });
479
+ this.conversion({
480
+ type: "purchase",
481
+ revenue: data.revenue,
482
+ currency: data.currency,
483
+ orderId: data.orderId
484
+ });
485
+ if (this.config.debug) {
486
+ console.log("[Aurea SDK] Checkout completed:", data);
487
+ if (checkoutContext) {
488
+ console.log("[Aurea SDK] Session bridged - linked to original session:", checkoutContext.originalSessionId);
489
+ }
490
+ }
491
+ }
492
+ /**
493
+ * Track checkout abandoned (user left without completing)
494
+ */
495
+ checkoutAbandoned(reason) {
496
+ if (!this.isInCheckout) {
497
+ return;
498
+ }
499
+ const checkoutDuration = this.checkoutStartedAt ? Math.floor((Date.now() - this.checkoutStartedAt) / 1e3) : void 0;
500
+ this.enterStage("abandoned");
501
+ this.track("checkout_abandoned", {
502
+ reason: reason || "unknown",
503
+ checkoutDuration,
504
+ sessionDuration: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
505
+ microConversionsCount: this.microConversions.length,
506
+ priorStages: this.stageHistory.map((h) => h.stage)
507
+ });
508
+ this.isInCheckout = false;
509
+ if (this.config.debug) {
510
+ console.log("[Aurea SDK] Checkout abandoned:", reason);
511
+ }
512
+ }
513
+ /**
514
+ * Get time spent in a specific stage (in seconds)
515
+ */
516
+ getTimeInCurrentStage(stage) {
517
+ const stageEntries = this.stageHistory.filter((h) => h.stage === stage);
518
+ if (stageEntries.length === 0) return 0;
519
+ const lastEntry = stageEntries[stageEntries.length - 1];
520
+ const nextEntry = this.stageHistory.find((h) => h.enteredAt > lastEntry.enteredAt);
521
+ const endTime = nextEntry ? nextEntry.enteredAt : Date.now();
522
+ return Math.floor((endTime - lastEntry.enteredAt) / 1e3);
523
+ }
524
+ /**
525
+ * Get UTM parameter from current URL
526
+ */
527
+ getUTMParam(param) {
528
+ if (typeof window === "undefined") return void 0;
529
+ const urlParams = new URLSearchParams(window.location.search);
530
+ return urlParams.get(param) || void 0;
531
+ }
155
532
  /**
156
533
  * Get or create session ID
157
534
  */
@@ -553,6 +930,9 @@ var AureaSDK = class {
553
930
  window.addEventListener("touchstart", resetInactivityTimer, { passive: true });
554
931
  window.addEventListener("beforeunload", () => {
555
932
  const now = Date.now();
933
+ if (this.isInCheckout && this.currentStage === "checkout") {
934
+ this.checkoutAbandoned("page_close");
935
+ }
556
936
  if (this.isPageVisible) {
557
937
  this.activeTime += now - this.lastActiveTimestamp;
558
938
  }
@@ -599,6 +979,53 @@ var AureaSDK = class {
599
979
  console.log("[Aurea SDK] Session timing tracking enabled");
600
980
  }
601
981
  }
982
+ /**
983
+ * Initialize funnel tracking
984
+ * Checks for returning checkout users and sets initial stage
985
+ */
986
+ initializeFunnelTracking() {
987
+ if (typeof window === "undefined") return;
988
+ if (typeof localStorage !== "undefined") {
989
+ const checkoutContext = localStorage.getItem("aurea_checkout_context");
990
+ if (checkoutContext) {
991
+ try {
992
+ const context = JSON.parse(checkoutContext);
993
+ const urlParams = new URLSearchParams(window.location.search);
994
+ const purchased = urlParams.get("purchased") === "true";
995
+ if (purchased) {
996
+ if (this.config.debug) {
997
+ console.log(
998
+ "[Aurea SDK] Returning from successful checkout - awaiting checkoutCompleted() call"
999
+ );
1000
+ }
1001
+ } else {
1002
+ const timeSinceCheckout = Date.now() - context.checkoutStartedAt;
1003
+ const fifteenMinutes = 15 * 60 * 1e3;
1004
+ if (timeSinceCheckout > fifteenMinutes) {
1005
+ if (this.config.debug) {
1006
+ console.log(
1007
+ "[Aurea SDK] Checkout abandoned (15+ min elapsed)"
1008
+ );
1009
+ }
1010
+ }
1011
+ }
1012
+ } catch (err) {
1013
+ console.error(
1014
+ "[Aurea SDK] Failed to parse checkout context:",
1015
+ err
1016
+ );
1017
+ localStorage.removeItem("aurea_checkout_context");
1018
+ }
1019
+ }
1020
+ }
1021
+ this.stageHistory.push({
1022
+ stage: "awareness",
1023
+ enteredAt: Date.now()
1024
+ });
1025
+ if (this.config.debug) {
1026
+ console.log("[Aurea SDK] Funnel tracking initialized - Stage: awareness");
1027
+ }
1028
+ }
602
1029
  /**
603
1030
  * Generate unique ID
604
1031
  */
@@ -661,6 +1088,7 @@ function trackPage(name, properties) {
661
1088
  }
662
1089
  }
663
1090
  export {
1091
+ AureaSDK,
664
1092
  getAurea,
665
1093
  identifyUser,
666
1094
  initAurea,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aurea-tracking-sdk",
3
- "version": "1.1.2",
3
+ "version": "1.3.0",
4
4
  "description": "Standalone tracking SDK for Aurea CRM external funnels",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",