@grainql/analytics-web 2.2.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attribution.d.ts +47 -0
- package/dist/attribution.d.ts.map +1 -0
- package/dist/attribution.js +228 -0
- package/dist/cjs/attribution.d.ts +47 -0
- package/dist/cjs/attribution.d.ts.map +1 -0
- package/dist/cjs/attribution.js +228 -0
- package/dist/cjs/attribution.js.map +1 -0
- package/dist/cjs/countries.d.ts +13 -0
- package/dist/cjs/countries.d.ts.map +1 -0
- package/dist/cjs/countries.js +2907 -0
- package/dist/cjs/countries.js.map +1 -0
- package/dist/cjs/heartbeat.d.ts +1 -0
- package/dist/cjs/heartbeat.d.ts.map +1 -1
- package/dist/cjs/heartbeat.js +1 -1
- package/dist/cjs/heartbeat.js.map +1 -1
- package/dist/cjs/index.d.ts +26 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/page-tracking.d.ts +25 -0
- package/dist/cjs/page-tracking.d.ts.map +1 -1
- package/dist/cjs/page-tracking.js +162 -9
- package/dist/cjs/page-tracking.js.map +1 -1
- package/dist/countries.d.ts +13 -0
- package/dist/countries.d.ts.map +1 -0
- package/dist/countries.js +2907 -0
- package/dist/esm/attribution.d.ts +47 -0
- package/dist/esm/attribution.d.ts.map +1 -0
- package/dist/esm/attribution.js +218 -0
- package/dist/esm/attribution.js.map +1 -0
- package/dist/esm/countries.d.ts +13 -0
- package/dist/esm/countries.d.ts.map +1 -0
- package/dist/esm/countries.js +2902 -0
- package/dist/esm/countries.js.map +1 -0
- package/dist/esm/heartbeat.d.ts +1 -0
- package/dist/esm/heartbeat.d.ts.map +1 -1
- package/dist/esm/heartbeat.js +1 -1
- package/dist/esm/heartbeat.js.map +1 -1
- package/dist/esm/index.d.ts +26 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/page-tracking.d.ts +25 -0
- package/dist/esm/page-tracking.d.ts.map +1 -1
- package/dist/esm/page-tracking.js +162 -9
- package/dist/esm/page-tracking.js.map +1 -1
- package/dist/heartbeat.d.ts +1 -0
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +1 -1
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.dev.js +3361 -11
- package/dist/index.global.dev.js.map +3 -3
- package/dist/index.global.js +2 -2
- package/dist/index.global.js.map +4 -4
- package/dist/index.js +162 -1
- package/dist/index.mjs +157 -0
- package/dist/page-tracking.d.ts +25 -0
- package/dist/page-tracking.d.ts.map +1 -1
- package/dist/page-tracking.js +162 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,13 +4,21 @@
|
|
|
4
4
|
* A lightweight, dependency-free TypeScript SDK for sending analytics events to Grain's REST API
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.GrainAnalytics = void 0;
|
|
7
|
+
exports.GrainAnalytics = exports.getState = exports.getCountryCodeFromTimezone = exports.getCountry = exports.parseUTMParameters = exports.categorizeReferrer = void 0;
|
|
8
8
|
exports.createGrainAnalytics = createGrainAnalytics;
|
|
9
9
|
const consent_1 = require("./consent");
|
|
10
10
|
const cookies_1 = require("./cookies");
|
|
11
11
|
const activity_1 = require("./activity");
|
|
12
12
|
const heartbeat_1 = require("./heartbeat");
|
|
13
13
|
const page_tracking_1 = require("./page-tracking");
|
|
14
|
+
const attribution_1 = require("./attribution");
|
|
15
|
+
Object.defineProperty(exports, "categorizeReferrer", { enumerable: true, get: function () { return attribution_1.categorizeReferrer; } });
|
|
16
|
+
Object.defineProperty(exports, "parseUTMParameters", { enumerable: true, get: function () { return attribution_1.parseUTMParameters; } });
|
|
17
|
+
// Re-export timezone-country utilities
|
|
18
|
+
var countries_1 = require("./countries");
|
|
19
|
+
Object.defineProperty(exports, "getCountry", { enumerable: true, get: function () { return countries_1.getCountry; } });
|
|
20
|
+
Object.defineProperty(exports, "getCountryCodeFromTimezone", { enumerable: true, get: function () { return countries_1.getCountryCodeFromTimezone; } });
|
|
21
|
+
Object.defineProperty(exports, "getState", { enumerable: true, get: function () { return countries_1.getState; } });
|
|
14
22
|
class GrainAnalytics {
|
|
15
23
|
constructor(config) {
|
|
16
24
|
this.eventQueue = [];
|
|
@@ -31,6 +39,9 @@ class GrainAnalytics {
|
|
|
31
39
|
this.pageTrackingManager = null;
|
|
32
40
|
this.ephemeralSessionId = null;
|
|
33
41
|
this.eventCountSinceLastHeartbeat = 0;
|
|
42
|
+
// Session tracking
|
|
43
|
+
this.sessionStartTime = Date.now();
|
|
44
|
+
this.sessionEventCount = 0;
|
|
34
45
|
this.config = {
|
|
35
46
|
apiUrl: 'https://api.grainql.com',
|
|
36
47
|
authStrategy: 'NONE',
|
|
@@ -83,6 +94,8 @@ class GrainAnalytics {
|
|
|
83
94
|
// Initialize automatic tracking (browser only)
|
|
84
95
|
if (typeof window !== 'undefined') {
|
|
85
96
|
this.initializeAutomaticTracking();
|
|
97
|
+
// Track session start
|
|
98
|
+
this.trackSessionStart();
|
|
86
99
|
}
|
|
87
100
|
// Set up consent change listener to flush waiting events and handle consent upgrade
|
|
88
101
|
this.consentManager.addListener((state) => {
|
|
@@ -521,6 +534,8 @@ class GrainAnalytics {
|
|
|
521
534
|
if (typeof window === 'undefined')
|
|
522
535
|
return;
|
|
523
536
|
const handleBeforeUnload = () => {
|
|
537
|
+
// Track session end
|
|
538
|
+
this.trackSessionEnd();
|
|
524
539
|
if (this.eventQueue.length > 0) {
|
|
525
540
|
// Use beacon API for reliable delivery during page unload
|
|
526
541
|
const eventsToSend = [...this.eventQueue];
|
|
@@ -575,6 +590,7 @@ class GrainAnalytics {
|
|
|
575
590
|
this.pageTrackingManager = new page_tracking_1.PageTrackingManager(this, {
|
|
576
591
|
stripQueryParams: this.config.stripQueryParams,
|
|
577
592
|
debug: this.config.debug,
|
|
593
|
+
tenantId: this.config.tenantId,
|
|
578
594
|
});
|
|
579
595
|
this.log('Auto page view tracking initialized');
|
|
580
596
|
}
|
|
@@ -583,6 +599,150 @@ class GrainAnalytics {
|
|
|
583
599
|
}
|
|
584
600
|
}
|
|
585
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Track session start event
|
|
604
|
+
*/
|
|
605
|
+
trackSessionStart() {
|
|
606
|
+
if (typeof window === 'undefined')
|
|
607
|
+
return;
|
|
608
|
+
const hasConsent = this.consentManager.hasConsent('analytics');
|
|
609
|
+
const properties = {
|
|
610
|
+
session_id: this.getSessionId(),
|
|
611
|
+
timestamp: this.sessionStartTime,
|
|
612
|
+
};
|
|
613
|
+
if (hasConsent) {
|
|
614
|
+
const referrer = document.referrer || '';
|
|
615
|
+
const currentUrl = window.location.href;
|
|
616
|
+
// Parse UTM parameters
|
|
617
|
+
const utmParams = (0, attribution_1.parseUTMParameters)(currentUrl);
|
|
618
|
+
const sessionUTMs = (0, attribution_1.getSessionUTMParameters)() || utmParams;
|
|
619
|
+
// Get first-touch attribution
|
|
620
|
+
const firstTouch = (0, attribution_1.getOrCreateFirstTouchAttribution)(this.config.tenantId, referrer, currentUrl, sessionUTMs);
|
|
621
|
+
// Landing page
|
|
622
|
+
properties.landing_page = window.location.pathname;
|
|
623
|
+
// Referrer info
|
|
624
|
+
if (referrer) {
|
|
625
|
+
properties.referrer = referrer;
|
|
626
|
+
properties.referrer_domain = new URL(referrer).hostname;
|
|
627
|
+
properties.referrer_category = (0, attribution_1.categorizeReferrer)(referrer, currentUrl);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
properties.referrer_category = 'direct';
|
|
631
|
+
}
|
|
632
|
+
// UTM parameters
|
|
633
|
+
if (sessionUTMs.utm_source)
|
|
634
|
+
properties.utm_source = sessionUTMs.utm_source;
|
|
635
|
+
if (sessionUTMs.utm_medium)
|
|
636
|
+
properties.utm_medium = sessionUTMs.utm_medium;
|
|
637
|
+
if (sessionUTMs.utm_campaign)
|
|
638
|
+
properties.utm_campaign = sessionUTMs.utm_campaign;
|
|
639
|
+
if (sessionUTMs.utm_term)
|
|
640
|
+
properties.utm_term = sessionUTMs.utm_term;
|
|
641
|
+
if (sessionUTMs.utm_content)
|
|
642
|
+
properties.utm_content = sessionUTMs.utm_content;
|
|
643
|
+
// First-touch attribution
|
|
644
|
+
properties.first_touch_source = firstTouch.source;
|
|
645
|
+
properties.first_touch_medium = firstTouch.medium;
|
|
646
|
+
properties.first_touch_campaign = firstTouch.campaign;
|
|
647
|
+
properties.first_touch_referrer_category = firstTouch.referrer_category;
|
|
648
|
+
// Device and browser info
|
|
649
|
+
properties.device = this.getDeviceType();
|
|
650
|
+
properties.screen_resolution = `${screen.width}x${screen.height}`;
|
|
651
|
+
properties.viewport = `${window.innerWidth}x${window.innerHeight}`;
|
|
652
|
+
properties.browser = this.getBrowser();
|
|
653
|
+
properties.os = this.getOS();
|
|
654
|
+
properties.language = navigator.language || '';
|
|
655
|
+
properties.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
656
|
+
}
|
|
657
|
+
this.trackSystemEvent('_grain_session_start', properties);
|
|
658
|
+
this.log('Session started:', properties);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Track session end event
|
|
662
|
+
*/
|
|
663
|
+
trackSessionEnd() {
|
|
664
|
+
if (typeof window === 'undefined')
|
|
665
|
+
return;
|
|
666
|
+
const hasConsent = this.consentManager.hasConsent('analytics');
|
|
667
|
+
const sessionDuration = Date.now() - this.sessionStartTime;
|
|
668
|
+
const properties = {
|
|
669
|
+
session_id: this.getSessionId(),
|
|
670
|
+
session_duration: Math.floor(sessionDuration / 1000), // In seconds for easier querying
|
|
671
|
+
duration: sessionDuration, // Keep for backward compatibility
|
|
672
|
+
event_count: this.sessionEventCount,
|
|
673
|
+
timestamp: Date.now(),
|
|
674
|
+
};
|
|
675
|
+
if (hasConsent && this.pageTrackingManager) {
|
|
676
|
+
const pageCount = this.pageTrackingManager.getPageViewCount();
|
|
677
|
+
properties.pages_per_session = pageCount;
|
|
678
|
+
properties.page_count = pageCount; // Keep for backward compatibility
|
|
679
|
+
}
|
|
680
|
+
this.trackSystemEvent('_grain_session_end', properties);
|
|
681
|
+
this.log('Session ended:', properties);
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Detect browser name
|
|
685
|
+
*/
|
|
686
|
+
getBrowser() {
|
|
687
|
+
if (typeof navigator === 'undefined')
|
|
688
|
+
return 'Unknown';
|
|
689
|
+
const ua = navigator.userAgent;
|
|
690
|
+
if (ua.includes('Firefox/'))
|
|
691
|
+
return 'Firefox';
|
|
692
|
+
if (ua.includes('Edg/'))
|
|
693
|
+
return 'Edge';
|
|
694
|
+
if (ua.includes('Chrome/'))
|
|
695
|
+
return 'Chrome';
|
|
696
|
+
if (ua.includes('Safari/') && !ua.includes('Chrome/'))
|
|
697
|
+
return 'Safari';
|
|
698
|
+
if (ua.includes('Opera/') || ua.includes('OPR/'))
|
|
699
|
+
return 'Opera';
|
|
700
|
+
return 'Unknown';
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Detect operating system
|
|
704
|
+
*/
|
|
705
|
+
getOS() {
|
|
706
|
+
if (typeof navigator === 'undefined')
|
|
707
|
+
return 'Unknown';
|
|
708
|
+
const ua = navigator.userAgent;
|
|
709
|
+
if (ua.includes('Win'))
|
|
710
|
+
return 'Windows';
|
|
711
|
+
if (ua.includes('Mac'))
|
|
712
|
+
return 'macOS';
|
|
713
|
+
if (ua.includes('Linux'))
|
|
714
|
+
return 'Linux';
|
|
715
|
+
if (ua.includes('Android'))
|
|
716
|
+
return 'Android';
|
|
717
|
+
if (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad'))
|
|
718
|
+
return 'iOS';
|
|
719
|
+
return 'Unknown';
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Detect device type (Mobile, Tablet, Desktop)
|
|
723
|
+
*/
|
|
724
|
+
getDeviceType() {
|
|
725
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined')
|
|
726
|
+
return 'Unknown';
|
|
727
|
+
const ua = navigator.userAgent;
|
|
728
|
+
const width = window.innerWidth;
|
|
729
|
+
// Check for tablet-specific indicators
|
|
730
|
+
if (ua.includes('iPad') || (ua.includes('Android') && !ua.includes('Mobile'))) {
|
|
731
|
+
return 'Tablet';
|
|
732
|
+
}
|
|
733
|
+
// Check for mobile indicators
|
|
734
|
+
if (ua.includes('Mobile') || ua.includes('iPhone') || ua.includes('Android')) {
|
|
735
|
+
return 'Mobile';
|
|
736
|
+
}
|
|
737
|
+
// Fallback to screen width detection
|
|
738
|
+
if (width < 768) {
|
|
739
|
+
return 'Mobile';
|
|
740
|
+
}
|
|
741
|
+
else if (width >= 768 && width < 1024) {
|
|
742
|
+
return 'Tablet';
|
|
743
|
+
}
|
|
744
|
+
return 'Desktop';
|
|
745
|
+
}
|
|
586
746
|
/**
|
|
587
747
|
* Handle consent granted - upgrade ephemeral session to persistent user
|
|
588
748
|
*/
|
|
@@ -716,6 +876,7 @@ class GrainAnalytics {
|
|
|
716
876
|
}
|
|
717
877
|
this.eventQueue.push(formattedEvent);
|
|
718
878
|
this.eventCountSinceLastHeartbeat++;
|
|
879
|
+
this.sessionEventCount++;
|
|
719
880
|
this.log(`Queued event: ${event.eventName}`, event.properties);
|
|
720
881
|
// Check if we should flush immediately
|
|
721
882
|
if (opts.flush || this.eventQueue.length >= this.config.batchSize) {
|
package/dist/index.mjs
CHANGED
|
@@ -7,6 +7,10 @@ import { setCookie, getCookie, areCookiesEnabled } from './cookies.js';
|
|
|
7
7
|
import { ActivityDetector } from './activity.js';
|
|
8
8
|
import { HeartbeatManager } from './heartbeat.js';
|
|
9
9
|
import { PageTrackingManager } from './page-tracking.js';
|
|
10
|
+
import { categorizeReferrer, parseUTMParameters, getOrCreateFirstTouchAttribution, getSessionUTMParameters, } from './attribution.js';
|
|
11
|
+
export { categorizeReferrer, parseUTMParameters };
|
|
12
|
+
// Re-export timezone-country utilities
|
|
13
|
+
export { getCountry, getCountryCodeFromTimezone, getState } from './countries.js';
|
|
10
14
|
export class GrainAnalytics {
|
|
11
15
|
constructor(config) {
|
|
12
16
|
this.eventQueue = [];
|
|
@@ -27,6 +31,9 @@ export class GrainAnalytics {
|
|
|
27
31
|
this.pageTrackingManager = null;
|
|
28
32
|
this.ephemeralSessionId = null;
|
|
29
33
|
this.eventCountSinceLastHeartbeat = 0;
|
|
34
|
+
// Session tracking
|
|
35
|
+
this.sessionStartTime = Date.now();
|
|
36
|
+
this.sessionEventCount = 0;
|
|
30
37
|
this.config = {
|
|
31
38
|
apiUrl: 'https://api.grainql.com',
|
|
32
39
|
authStrategy: 'NONE',
|
|
@@ -79,6 +86,8 @@ export class GrainAnalytics {
|
|
|
79
86
|
// Initialize automatic tracking (browser only)
|
|
80
87
|
if (typeof window !== 'undefined') {
|
|
81
88
|
this.initializeAutomaticTracking();
|
|
89
|
+
// Track session start
|
|
90
|
+
this.trackSessionStart();
|
|
82
91
|
}
|
|
83
92
|
// Set up consent change listener to flush waiting events and handle consent upgrade
|
|
84
93
|
this.consentManager.addListener((state) => {
|
|
@@ -517,6 +526,8 @@ export class GrainAnalytics {
|
|
|
517
526
|
if (typeof window === 'undefined')
|
|
518
527
|
return;
|
|
519
528
|
const handleBeforeUnload = () => {
|
|
529
|
+
// Track session end
|
|
530
|
+
this.trackSessionEnd();
|
|
520
531
|
if (this.eventQueue.length > 0) {
|
|
521
532
|
// Use beacon API for reliable delivery during page unload
|
|
522
533
|
const eventsToSend = [...this.eventQueue];
|
|
@@ -571,6 +582,7 @@ export class GrainAnalytics {
|
|
|
571
582
|
this.pageTrackingManager = new PageTrackingManager(this, {
|
|
572
583
|
stripQueryParams: this.config.stripQueryParams,
|
|
573
584
|
debug: this.config.debug,
|
|
585
|
+
tenantId: this.config.tenantId,
|
|
574
586
|
});
|
|
575
587
|
this.log('Auto page view tracking initialized');
|
|
576
588
|
}
|
|
@@ -579,6 +591,150 @@ export class GrainAnalytics {
|
|
|
579
591
|
}
|
|
580
592
|
}
|
|
581
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Track session start event
|
|
596
|
+
*/
|
|
597
|
+
trackSessionStart() {
|
|
598
|
+
if (typeof window === 'undefined')
|
|
599
|
+
return;
|
|
600
|
+
const hasConsent = this.consentManager.hasConsent('analytics');
|
|
601
|
+
const properties = {
|
|
602
|
+
session_id: this.getSessionId(),
|
|
603
|
+
timestamp: this.sessionStartTime,
|
|
604
|
+
};
|
|
605
|
+
if (hasConsent) {
|
|
606
|
+
const referrer = document.referrer || '';
|
|
607
|
+
const currentUrl = window.location.href;
|
|
608
|
+
// Parse UTM parameters
|
|
609
|
+
const utmParams = parseUTMParameters(currentUrl);
|
|
610
|
+
const sessionUTMs = getSessionUTMParameters() || utmParams;
|
|
611
|
+
// Get first-touch attribution
|
|
612
|
+
const firstTouch = getOrCreateFirstTouchAttribution(this.config.tenantId, referrer, currentUrl, sessionUTMs);
|
|
613
|
+
// Landing page
|
|
614
|
+
properties.landing_page = window.location.pathname;
|
|
615
|
+
// Referrer info
|
|
616
|
+
if (referrer) {
|
|
617
|
+
properties.referrer = referrer;
|
|
618
|
+
properties.referrer_domain = new URL(referrer).hostname;
|
|
619
|
+
properties.referrer_category = categorizeReferrer(referrer, currentUrl);
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
properties.referrer_category = 'direct';
|
|
623
|
+
}
|
|
624
|
+
// UTM parameters
|
|
625
|
+
if (sessionUTMs.utm_source)
|
|
626
|
+
properties.utm_source = sessionUTMs.utm_source;
|
|
627
|
+
if (sessionUTMs.utm_medium)
|
|
628
|
+
properties.utm_medium = sessionUTMs.utm_medium;
|
|
629
|
+
if (sessionUTMs.utm_campaign)
|
|
630
|
+
properties.utm_campaign = sessionUTMs.utm_campaign;
|
|
631
|
+
if (sessionUTMs.utm_term)
|
|
632
|
+
properties.utm_term = sessionUTMs.utm_term;
|
|
633
|
+
if (sessionUTMs.utm_content)
|
|
634
|
+
properties.utm_content = sessionUTMs.utm_content;
|
|
635
|
+
// First-touch attribution
|
|
636
|
+
properties.first_touch_source = firstTouch.source;
|
|
637
|
+
properties.first_touch_medium = firstTouch.medium;
|
|
638
|
+
properties.first_touch_campaign = firstTouch.campaign;
|
|
639
|
+
properties.first_touch_referrer_category = firstTouch.referrer_category;
|
|
640
|
+
// Device and browser info
|
|
641
|
+
properties.device = this.getDeviceType();
|
|
642
|
+
properties.screen_resolution = `${screen.width}x${screen.height}`;
|
|
643
|
+
properties.viewport = `${window.innerWidth}x${window.innerHeight}`;
|
|
644
|
+
properties.browser = this.getBrowser();
|
|
645
|
+
properties.os = this.getOS();
|
|
646
|
+
properties.language = navigator.language || '';
|
|
647
|
+
properties.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
648
|
+
}
|
|
649
|
+
this.trackSystemEvent('_grain_session_start', properties);
|
|
650
|
+
this.log('Session started:', properties);
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Track session end event
|
|
654
|
+
*/
|
|
655
|
+
trackSessionEnd() {
|
|
656
|
+
if (typeof window === 'undefined')
|
|
657
|
+
return;
|
|
658
|
+
const hasConsent = this.consentManager.hasConsent('analytics');
|
|
659
|
+
const sessionDuration = Date.now() - this.sessionStartTime;
|
|
660
|
+
const properties = {
|
|
661
|
+
session_id: this.getSessionId(),
|
|
662
|
+
session_duration: Math.floor(sessionDuration / 1000), // In seconds for easier querying
|
|
663
|
+
duration: sessionDuration, // Keep for backward compatibility
|
|
664
|
+
event_count: this.sessionEventCount,
|
|
665
|
+
timestamp: Date.now(),
|
|
666
|
+
};
|
|
667
|
+
if (hasConsent && this.pageTrackingManager) {
|
|
668
|
+
const pageCount = this.pageTrackingManager.getPageViewCount();
|
|
669
|
+
properties.pages_per_session = pageCount;
|
|
670
|
+
properties.page_count = pageCount; // Keep for backward compatibility
|
|
671
|
+
}
|
|
672
|
+
this.trackSystemEvent('_grain_session_end', properties);
|
|
673
|
+
this.log('Session ended:', properties);
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Detect browser name
|
|
677
|
+
*/
|
|
678
|
+
getBrowser() {
|
|
679
|
+
if (typeof navigator === 'undefined')
|
|
680
|
+
return 'Unknown';
|
|
681
|
+
const ua = navigator.userAgent;
|
|
682
|
+
if (ua.includes('Firefox/'))
|
|
683
|
+
return 'Firefox';
|
|
684
|
+
if (ua.includes('Edg/'))
|
|
685
|
+
return 'Edge';
|
|
686
|
+
if (ua.includes('Chrome/'))
|
|
687
|
+
return 'Chrome';
|
|
688
|
+
if (ua.includes('Safari/') && !ua.includes('Chrome/'))
|
|
689
|
+
return 'Safari';
|
|
690
|
+
if (ua.includes('Opera/') || ua.includes('OPR/'))
|
|
691
|
+
return 'Opera';
|
|
692
|
+
return 'Unknown';
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Detect operating system
|
|
696
|
+
*/
|
|
697
|
+
getOS() {
|
|
698
|
+
if (typeof navigator === 'undefined')
|
|
699
|
+
return 'Unknown';
|
|
700
|
+
const ua = navigator.userAgent;
|
|
701
|
+
if (ua.includes('Win'))
|
|
702
|
+
return 'Windows';
|
|
703
|
+
if (ua.includes('Mac'))
|
|
704
|
+
return 'macOS';
|
|
705
|
+
if (ua.includes('Linux'))
|
|
706
|
+
return 'Linux';
|
|
707
|
+
if (ua.includes('Android'))
|
|
708
|
+
return 'Android';
|
|
709
|
+
if (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad'))
|
|
710
|
+
return 'iOS';
|
|
711
|
+
return 'Unknown';
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Detect device type (Mobile, Tablet, Desktop)
|
|
715
|
+
*/
|
|
716
|
+
getDeviceType() {
|
|
717
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined')
|
|
718
|
+
return 'Unknown';
|
|
719
|
+
const ua = navigator.userAgent;
|
|
720
|
+
const width = window.innerWidth;
|
|
721
|
+
// Check for tablet-specific indicators
|
|
722
|
+
if (ua.includes('iPad') || (ua.includes('Android') && !ua.includes('Mobile'))) {
|
|
723
|
+
return 'Tablet';
|
|
724
|
+
}
|
|
725
|
+
// Check for mobile indicators
|
|
726
|
+
if (ua.includes('Mobile') || ua.includes('iPhone') || ua.includes('Android')) {
|
|
727
|
+
return 'Mobile';
|
|
728
|
+
}
|
|
729
|
+
// Fallback to screen width detection
|
|
730
|
+
if (width < 768) {
|
|
731
|
+
return 'Mobile';
|
|
732
|
+
}
|
|
733
|
+
else if (width >= 768 && width < 1024) {
|
|
734
|
+
return 'Tablet';
|
|
735
|
+
}
|
|
736
|
+
return 'Desktop';
|
|
737
|
+
}
|
|
582
738
|
/**
|
|
583
739
|
* Handle consent granted - upgrade ephemeral session to persistent user
|
|
584
740
|
*/
|
|
@@ -712,6 +868,7 @@ export class GrainAnalytics {
|
|
|
712
868
|
}
|
|
713
869
|
this.eventQueue.push(formattedEvent);
|
|
714
870
|
this.eventCountSinceLastHeartbeat++;
|
|
871
|
+
this.sessionEventCount++;
|
|
715
872
|
this.log(`Queued event: ${event.eventName}`, event.properties);
|
|
716
873
|
// Check if we should flush immediately
|
|
717
874
|
if (opts.flush || this.eventQueue.length >= this.config.batchSize) {
|
package/dist/page-tracking.d.ts
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
export interface PageTrackingConfig {
|
|
6
6
|
stripQueryParams: boolean;
|
|
7
7
|
debug?: boolean;
|
|
8
|
+
tenantId: string;
|
|
8
9
|
}
|
|
9
10
|
export interface PageTracker {
|
|
10
11
|
trackSystemEvent(eventName: string, properties: Record<string, unknown>): void;
|
|
11
12
|
hasConsent(category?: string): boolean;
|
|
12
13
|
getEffectiveUserId(): string;
|
|
13
14
|
getEphemeralSessionId(): string;
|
|
15
|
+
getSessionId(): string;
|
|
14
16
|
}
|
|
15
17
|
export declare class PageTrackingManager {
|
|
16
18
|
private config;
|
|
@@ -19,6 +21,9 @@ export declare class PageTrackingManager {
|
|
|
19
21
|
private currentPath;
|
|
20
22
|
private originalPushState;
|
|
21
23
|
private originalReplaceState;
|
|
24
|
+
private previousPage;
|
|
25
|
+
private landingPage;
|
|
26
|
+
private pageViewCount;
|
|
22
27
|
constructor(tracker: PageTracker, config: PageTrackingConfig);
|
|
23
28
|
/**
|
|
24
29
|
* Setup History API listeners (pushState, replaceState, popstate)
|
|
@@ -40,6 +45,22 @@ export declare class PageTrackingManager {
|
|
|
40
45
|
* Track the current page
|
|
41
46
|
*/
|
|
42
47
|
private trackCurrentPage;
|
|
48
|
+
/**
|
|
49
|
+
* Extract domain from URL
|
|
50
|
+
*/
|
|
51
|
+
private extractDomain;
|
|
52
|
+
/**
|
|
53
|
+
* Detect browser name
|
|
54
|
+
*/
|
|
55
|
+
private getBrowser;
|
|
56
|
+
/**
|
|
57
|
+
* Detect operating system
|
|
58
|
+
*/
|
|
59
|
+
private getOS;
|
|
60
|
+
/**
|
|
61
|
+
* Detect device type (Mobile, Tablet, Desktop)
|
|
62
|
+
*/
|
|
63
|
+
private getDeviceType;
|
|
43
64
|
/**
|
|
44
65
|
* Extract path from URL, optionally stripping query parameters
|
|
45
66
|
*/
|
|
@@ -52,6 +73,10 @@ export declare class PageTrackingManager {
|
|
|
52
73
|
* Manually track a page view (for custom navigation)
|
|
53
74
|
*/
|
|
54
75
|
trackPage(page: string, properties?: Record<string, unknown>): void;
|
|
76
|
+
/**
|
|
77
|
+
* Get page view count for current session
|
|
78
|
+
*/
|
|
79
|
+
getPageViewCount(): number;
|
|
55
80
|
/**
|
|
56
81
|
* Destroy the page tracker
|
|
57
82
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-tracking.d.ts","sourceRoot":"","sources":["../src/page-tracking.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"page-tracking.d.ts","sourceRoot":"","sources":["../src/page-tracking.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/E,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,kBAAkB,IAAI,MAAM,CAAC;IAC7B,qBAAqB,IAAI,MAAM,CAAC;IAChC,YAAY,IAAI,MAAM,CAAC;CACxB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,oBAAoB,CAA4C;IACxE,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,aAAa,CAAK;gBAEd,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB;IAY5D;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAK/B;;OAEG;IACH,OAAO,CAAC,cAAc,CAGpB;IAEF;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAGtB;IAEF;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgHxB;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACH,OAAO,CAAC,KAAK;IAUb;;OAEG;IACH,OAAO,CAAC,aAAa;IAwBrB;;OAEG;IACH,OAAO,CAAC,WAAW;IAmBnB;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAyCnE;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACH,OAAO,IAAI,IAAI;CAyBhB"}
|