@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.
Files changed (59) hide show
  1. package/dist/attribution.d.ts +47 -0
  2. package/dist/attribution.d.ts.map +1 -0
  3. package/dist/attribution.js +228 -0
  4. package/dist/cjs/attribution.d.ts +47 -0
  5. package/dist/cjs/attribution.d.ts.map +1 -0
  6. package/dist/cjs/attribution.js +228 -0
  7. package/dist/cjs/attribution.js.map +1 -0
  8. package/dist/cjs/countries.d.ts +13 -0
  9. package/dist/cjs/countries.d.ts.map +1 -0
  10. package/dist/cjs/countries.js +2907 -0
  11. package/dist/cjs/countries.js.map +1 -0
  12. package/dist/cjs/heartbeat.d.ts +1 -0
  13. package/dist/cjs/heartbeat.d.ts.map +1 -1
  14. package/dist/cjs/heartbeat.js +1 -1
  15. package/dist/cjs/heartbeat.js.map +1 -1
  16. package/dist/cjs/index.d.ts +26 -0
  17. package/dist/cjs/index.d.ts.map +1 -1
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/cjs/page-tracking.d.ts +25 -0
  20. package/dist/cjs/page-tracking.d.ts.map +1 -1
  21. package/dist/cjs/page-tracking.js +162 -9
  22. package/dist/cjs/page-tracking.js.map +1 -1
  23. package/dist/countries.d.ts +13 -0
  24. package/dist/countries.d.ts.map +1 -0
  25. package/dist/countries.js +2907 -0
  26. package/dist/esm/attribution.d.ts +47 -0
  27. package/dist/esm/attribution.d.ts.map +1 -0
  28. package/dist/esm/attribution.js +218 -0
  29. package/dist/esm/attribution.js.map +1 -0
  30. package/dist/esm/countries.d.ts +13 -0
  31. package/dist/esm/countries.d.ts.map +1 -0
  32. package/dist/esm/countries.js +2902 -0
  33. package/dist/esm/countries.js.map +1 -0
  34. package/dist/esm/heartbeat.d.ts +1 -0
  35. package/dist/esm/heartbeat.d.ts.map +1 -1
  36. package/dist/esm/heartbeat.js +1 -1
  37. package/dist/esm/heartbeat.js.map +1 -1
  38. package/dist/esm/index.d.ts +26 -0
  39. package/dist/esm/index.d.ts.map +1 -1
  40. package/dist/esm/index.js.map +1 -1
  41. package/dist/esm/page-tracking.d.ts +25 -0
  42. package/dist/esm/page-tracking.d.ts.map +1 -1
  43. package/dist/esm/page-tracking.js +162 -9
  44. package/dist/esm/page-tracking.js.map +1 -1
  45. package/dist/heartbeat.d.ts +1 -0
  46. package/dist/heartbeat.d.ts.map +1 -1
  47. package/dist/heartbeat.js +1 -1
  48. package/dist/index.d.ts +26 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.global.dev.js +3361 -11
  51. package/dist/index.global.dev.js.map +3 -3
  52. package/dist/index.global.js +2 -2
  53. package/dist/index.global.js.map +4 -4
  54. package/dist/index.js +162 -1
  55. package/dist/index.mjs +157 -0
  56. package/dist/page-tracking.d.ts +25 -0
  57. package/dist/page-tracking.d.ts.map +1 -1
  58. package/dist/page-tracking.js +162 -9
  59. package/package.json +1 -1
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Attribution and Referral Tracking for Grain Analytics
3
+ * Handles referral categorization and first-touch attribution
4
+ */
5
+ export type ReferrerCategory = 'organic' | 'paid' | 'social' | 'direct' | 'email' | 'referral';
6
+ export interface UTMParameters {
7
+ utm_source?: string;
8
+ utm_medium?: string;
9
+ utm_campaign?: string;
10
+ utm_term?: string;
11
+ utm_content?: string;
12
+ }
13
+ export interface FirstTouchAttribution {
14
+ source: string;
15
+ medium: string;
16
+ campaign: string;
17
+ referrer: string;
18
+ referrer_category: ReferrerCategory;
19
+ timestamp: number;
20
+ }
21
+ /**
22
+ * Categorize referrer based on domain and parameters
23
+ */
24
+ export declare function categorizeReferrer(referrer: string, currentUrl?: string): ReferrerCategory;
25
+ /**
26
+ * Parse UTM parameters from URL
27
+ */
28
+ export declare function parseUTMParameters(url: string): UTMParameters;
29
+ /**
30
+ * Get first-touch attribution from localStorage
31
+ */
32
+ export declare function getFirstTouchAttribution(tenantId: string): FirstTouchAttribution | null;
33
+ /**
34
+ * Set first-touch attribution in localStorage
35
+ */
36
+ export declare function setFirstTouchAttribution(tenantId: string, attribution: FirstTouchAttribution): void;
37
+ /**
38
+ * Get or create first-touch attribution
39
+ */
40
+ export declare function getOrCreateFirstTouchAttribution(tenantId: string, referrer: string, currentUrl: string, utmParams: UTMParameters): FirstTouchAttribution;
41
+ export declare function getSessionUTMParameters(): UTMParameters | null;
42
+ export declare function setSessionUTMParameters(params: UTMParameters): void;
43
+ /**
44
+ * Clear session UTM parameters
45
+ */
46
+ export declare function clearSessionUTMParameters(): void;
47
+ //# sourceMappingURL=attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAE/F,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB;AAkFD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAE,MAAW,GAAG,gBAAgB,CAsC9F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAqB7D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAgBvF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,qBAAqB,GACjC,IAAI,CAWN;AAED;;GAEG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,aAAa,GACvB,qBAAqB,CAwBvB;AAOD,wBAAgB,uBAAuB,IAAI,aAAa,GAAG,IAAI,CAE9D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAEnE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ /**
3
+ * Attribution and Referral Tracking for Grain Analytics
4
+ * Handles referral categorization and first-touch attribution
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.categorizeReferrer = categorizeReferrer;
8
+ exports.parseUTMParameters = parseUTMParameters;
9
+ exports.getFirstTouchAttribution = getFirstTouchAttribution;
10
+ exports.setFirstTouchAttribution = setFirstTouchAttribution;
11
+ exports.getOrCreateFirstTouchAttribution = getOrCreateFirstTouchAttribution;
12
+ exports.getSessionUTMParameters = getSessionUTMParameters;
13
+ exports.setSessionUTMParameters = setSessionUTMParameters;
14
+ exports.clearSessionUTMParameters = clearSessionUTMParameters;
15
+ /**
16
+ * Known paid search parameters
17
+ */
18
+ const PAID_SEARCH_PARAMS = [
19
+ 'gclid', // Google Ads
20
+ 'msclkid', // Microsoft Ads
21
+ 'fbclid', // Facebook Ads
22
+ 'ttclid', // TikTok Ads
23
+ 'li_fat_id', // LinkedIn Ads
24
+ 'twclid', // Twitter Ads
25
+ 'ScCid', // Snapchat Ads
26
+ ];
27
+ /**
28
+ * Known social media domains
29
+ */
30
+ const SOCIAL_DOMAINS = [
31
+ 'facebook.com',
32
+ 'twitter.com',
33
+ 'x.com',
34
+ 'linkedin.com',
35
+ 'instagram.com',
36
+ 'pinterest.com',
37
+ 'reddit.com',
38
+ 'tiktok.com',
39
+ 'youtube.com',
40
+ 'snapchat.com',
41
+ 't.co', // Twitter short links
42
+ 'fb.me', // Facebook short links
43
+ 'lnkd.in', // LinkedIn short links
44
+ ];
45
+ /**
46
+ * Known organic search engines
47
+ */
48
+ const SEARCH_ENGINES = [
49
+ 'google.',
50
+ 'bing.com',
51
+ 'yahoo.com',
52
+ 'duckduckgo.com',
53
+ 'baidu.com',
54
+ 'yandex.com',
55
+ 'ecosia.org',
56
+ 'ask.com',
57
+ ];
58
+ /**
59
+ * Email client domains
60
+ */
61
+ const EMAIL_DOMAINS = [
62
+ 'mail.google.com',
63
+ 'outlook.live.com',
64
+ 'mail.yahoo.com',
65
+ 'mail.aol.com',
66
+ ];
67
+ /**
68
+ * Extract domain from URL
69
+ */
70
+ function extractDomain(url) {
71
+ try {
72
+ const urlObj = new URL(url);
73
+ return urlObj.hostname.toLowerCase();
74
+ }
75
+ catch {
76
+ return '';
77
+ }
78
+ }
79
+ /**
80
+ * Check if URL contains paid search parameters
81
+ */
82
+ function hasPaidSearchParams(url) {
83
+ try {
84
+ const urlObj = new URL(url);
85
+ return PAID_SEARCH_PARAMS.some(param => urlObj.searchParams.has(param));
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ /**
92
+ * Categorize referrer based on domain and parameters
93
+ */
94
+ function categorizeReferrer(referrer, currentUrl = '') {
95
+ // Direct traffic (no referrer)
96
+ if (!referrer || referrer.trim() === '') {
97
+ return 'direct';
98
+ }
99
+ const domain = extractDomain(referrer);
100
+ // Same domain = direct
101
+ if (currentUrl) {
102
+ const currentDomain = extractDomain(currentUrl);
103
+ if (domain === currentDomain) {
104
+ return 'direct';
105
+ }
106
+ }
107
+ // Check for paid search parameters
108
+ if (hasPaidSearchParams(referrer) || hasPaidSearchParams(currentUrl)) {
109
+ return 'paid';
110
+ }
111
+ // Email clients
112
+ if (EMAIL_DOMAINS.some(emailDomain => domain.includes(emailDomain))) {
113
+ return 'email';
114
+ }
115
+ // Social media
116
+ if (SOCIAL_DOMAINS.some(socialDomain => domain.includes(socialDomain))) {
117
+ return 'social';
118
+ }
119
+ // Organic search engines
120
+ if (SEARCH_ENGINES.some(searchEngine => domain.includes(searchEngine))) {
121
+ return 'organic';
122
+ }
123
+ // Everything else is referral
124
+ return 'referral';
125
+ }
126
+ /**
127
+ * Parse UTM parameters from URL
128
+ */
129
+ function parseUTMParameters(url) {
130
+ try {
131
+ const urlObj = new URL(url);
132
+ const params = {};
133
+ const utmSource = urlObj.searchParams.get('utm_source');
134
+ const utmMedium = urlObj.searchParams.get('utm_medium');
135
+ const utmCampaign = urlObj.searchParams.get('utm_campaign');
136
+ const utmTerm = urlObj.searchParams.get('utm_term');
137
+ const utmContent = urlObj.searchParams.get('utm_content');
138
+ if (utmSource)
139
+ params.utm_source = utmSource;
140
+ if (utmMedium)
141
+ params.utm_medium = utmMedium;
142
+ if (utmCampaign)
143
+ params.utm_campaign = utmCampaign;
144
+ if (utmTerm)
145
+ params.utm_term = utmTerm;
146
+ if (utmContent)
147
+ params.utm_content = utmContent;
148
+ return params;
149
+ }
150
+ catch {
151
+ return {};
152
+ }
153
+ }
154
+ /**
155
+ * Get first-touch attribution from localStorage
156
+ */
157
+ function getFirstTouchAttribution(tenantId) {
158
+ if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
159
+ return null;
160
+ }
161
+ try {
162
+ const key = `_grain_first_touch_${tenantId}`;
163
+ const stored = localStorage.getItem(key);
164
+ if (stored) {
165
+ return JSON.parse(stored);
166
+ }
167
+ }
168
+ catch (error) {
169
+ console.warn('[Grain Attribution] Failed to retrieve first-touch attribution:', error);
170
+ }
171
+ return null;
172
+ }
173
+ /**
174
+ * Set first-touch attribution in localStorage
175
+ */
176
+ function setFirstTouchAttribution(tenantId, attribution) {
177
+ if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
178
+ return;
179
+ }
180
+ try {
181
+ const key = `_grain_first_touch_${tenantId}`;
182
+ localStorage.setItem(key, JSON.stringify(attribution));
183
+ }
184
+ catch (error) {
185
+ console.warn('[Grain Attribution] Failed to store first-touch attribution:', error);
186
+ }
187
+ }
188
+ /**
189
+ * Get or create first-touch attribution
190
+ */
191
+ function getOrCreateFirstTouchAttribution(tenantId, referrer, currentUrl, utmParams) {
192
+ // Try to get existing first-touch
193
+ const existing = getFirstTouchAttribution(tenantId);
194
+ if (existing) {
195
+ return existing;
196
+ }
197
+ // Create new first-touch attribution
198
+ const referrerCategory = categorizeReferrer(referrer, currentUrl);
199
+ const referrerDomain = extractDomain(referrer);
200
+ const firstTouch = {
201
+ source: utmParams.utm_source || referrerDomain || 'direct',
202
+ medium: utmParams.utm_medium || referrerCategory,
203
+ campaign: utmParams.utm_campaign || 'none',
204
+ referrer: referrer || 'direct',
205
+ referrer_category: referrerCategory,
206
+ timestamp: Date.now(),
207
+ };
208
+ // Store it
209
+ setFirstTouchAttribution(tenantId, firstTouch);
210
+ return firstTouch;
211
+ }
212
+ /**
213
+ * Get session UTM parameters (memory-only, not persisted across page loads)
214
+ */
215
+ let sessionUTMParams = null;
216
+ function getSessionUTMParameters() {
217
+ return sessionUTMParams;
218
+ }
219
+ function setSessionUTMParameters(params) {
220
+ sessionUTMParams = params;
221
+ }
222
+ /**
223
+ * Clear session UTM parameters
224
+ */
225
+ function clearSessionUTMParameters() {
226
+ sessionUTMParams = null;
227
+ }
228
+ //# sourceMappingURL=attribution.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Attribution and Referral Tracking for Grain Analytics
3
+ * Handles referral categorization and first-touch attribution
4
+ */
5
+ export type ReferrerCategory = 'organic' | 'paid' | 'social' | 'direct' | 'email' | 'referral';
6
+ export interface UTMParameters {
7
+ utm_source?: string;
8
+ utm_medium?: string;
9
+ utm_campaign?: string;
10
+ utm_term?: string;
11
+ utm_content?: string;
12
+ }
13
+ export interface FirstTouchAttribution {
14
+ source: string;
15
+ medium: string;
16
+ campaign: string;
17
+ referrer: string;
18
+ referrer_category: ReferrerCategory;
19
+ timestamp: number;
20
+ }
21
+ /**
22
+ * Categorize referrer based on domain and parameters
23
+ */
24
+ export declare function categorizeReferrer(referrer: string, currentUrl?: string): ReferrerCategory;
25
+ /**
26
+ * Parse UTM parameters from URL
27
+ */
28
+ export declare function parseUTMParameters(url: string): UTMParameters;
29
+ /**
30
+ * Get first-touch attribution from localStorage
31
+ */
32
+ export declare function getFirstTouchAttribution(tenantId: string): FirstTouchAttribution | null;
33
+ /**
34
+ * Set first-touch attribution in localStorage
35
+ */
36
+ export declare function setFirstTouchAttribution(tenantId: string, attribution: FirstTouchAttribution): void;
37
+ /**
38
+ * Get or create first-touch attribution
39
+ */
40
+ export declare function getOrCreateFirstTouchAttribution(tenantId: string, referrer: string, currentUrl: string, utmParams: UTMParameters): FirstTouchAttribution;
41
+ export declare function getSessionUTMParameters(): UTMParameters | null;
42
+ export declare function setSessionUTMParameters(params: UTMParameters): void;
43
+ /**
44
+ * Clear session UTM parameters
45
+ */
46
+ export declare function clearSessionUTMParameters(): void;
47
+ //# sourceMappingURL=attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/attribution.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAE/F,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB;AAkFD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAE,MAAW,GAAG,gBAAgB,CAsC9F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAqB7D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAgBvF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,qBAAqB,GACjC,IAAI,CAWN;AAED;;GAEG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,aAAa,GACvB,qBAAqB,CAwBvB;AAOD,wBAAgB,uBAAuB,IAAI,aAAa,GAAG,IAAI,CAE9D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAEnE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ /**
3
+ * Attribution and Referral Tracking for Grain Analytics
4
+ * Handles referral categorization and first-touch attribution
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.categorizeReferrer = categorizeReferrer;
8
+ exports.parseUTMParameters = parseUTMParameters;
9
+ exports.getFirstTouchAttribution = getFirstTouchAttribution;
10
+ exports.setFirstTouchAttribution = setFirstTouchAttribution;
11
+ exports.getOrCreateFirstTouchAttribution = getOrCreateFirstTouchAttribution;
12
+ exports.getSessionUTMParameters = getSessionUTMParameters;
13
+ exports.setSessionUTMParameters = setSessionUTMParameters;
14
+ exports.clearSessionUTMParameters = clearSessionUTMParameters;
15
+ /**
16
+ * Known paid search parameters
17
+ */
18
+ const PAID_SEARCH_PARAMS = [
19
+ 'gclid', // Google Ads
20
+ 'msclkid', // Microsoft Ads
21
+ 'fbclid', // Facebook Ads
22
+ 'ttclid', // TikTok Ads
23
+ 'li_fat_id', // LinkedIn Ads
24
+ 'twclid', // Twitter Ads
25
+ 'ScCid', // Snapchat Ads
26
+ ];
27
+ /**
28
+ * Known social media domains
29
+ */
30
+ const SOCIAL_DOMAINS = [
31
+ 'facebook.com',
32
+ 'twitter.com',
33
+ 'x.com',
34
+ 'linkedin.com',
35
+ 'instagram.com',
36
+ 'pinterest.com',
37
+ 'reddit.com',
38
+ 'tiktok.com',
39
+ 'youtube.com',
40
+ 'snapchat.com',
41
+ 't.co', // Twitter short links
42
+ 'fb.me', // Facebook short links
43
+ 'lnkd.in', // LinkedIn short links
44
+ ];
45
+ /**
46
+ * Known organic search engines
47
+ */
48
+ const SEARCH_ENGINES = [
49
+ 'google.',
50
+ 'bing.com',
51
+ 'yahoo.com',
52
+ 'duckduckgo.com',
53
+ 'baidu.com',
54
+ 'yandex.com',
55
+ 'ecosia.org',
56
+ 'ask.com',
57
+ ];
58
+ /**
59
+ * Email client domains
60
+ */
61
+ const EMAIL_DOMAINS = [
62
+ 'mail.google.com',
63
+ 'outlook.live.com',
64
+ 'mail.yahoo.com',
65
+ 'mail.aol.com',
66
+ ];
67
+ /**
68
+ * Extract domain from URL
69
+ */
70
+ function extractDomain(url) {
71
+ try {
72
+ const urlObj = new URL(url);
73
+ return urlObj.hostname.toLowerCase();
74
+ }
75
+ catch {
76
+ return '';
77
+ }
78
+ }
79
+ /**
80
+ * Check if URL contains paid search parameters
81
+ */
82
+ function hasPaidSearchParams(url) {
83
+ try {
84
+ const urlObj = new URL(url);
85
+ return PAID_SEARCH_PARAMS.some(param => urlObj.searchParams.has(param));
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ /**
92
+ * Categorize referrer based on domain and parameters
93
+ */
94
+ function categorizeReferrer(referrer, currentUrl = '') {
95
+ // Direct traffic (no referrer)
96
+ if (!referrer || referrer.trim() === '') {
97
+ return 'direct';
98
+ }
99
+ const domain = extractDomain(referrer);
100
+ // Same domain = direct
101
+ if (currentUrl) {
102
+ const currentDomain = extractDomain(currentUrl);
103
+ if (domain === currentDomain) {
104
+ return 'direct';
105
+ }
106
+ }
107
+ // Check for paid search parameters
108
+ if (hasPaidSearchParams(referrer) || hasPaidSearchParams(currentUrl)) {
109
+ return 'paid';
110
+ }
111
+ // Email clients
112
+ if (EMAIL_DOMAINS.some(emailDomain => domain.includes(emailDomain))) {
113
+ return 'email';
114
+ }
115
+ // Social media
116
+ if (SOCIAL_DOMAINS.some(socialDomain => domain.includes(socialDomain))) {
117
+ return 'social';
118
+ }
119
+ // Organic search engines
120
+ if (SEARCH_ENGINES.some(searchEngine => domain.includes(searchEngine))) {
121
+ return 'organic';
122
+ }
123
+ // Everything else is referral
124
+ return 'referral';
125
+ }
126
+ /**
127
+ * Parse UTM parameters from URL
128
+ */
129
+ function parseUTMParameters(url) {
130
+ try {
131
+ const urlObj = new URL(url);
132
+ const params = {};
133
+ const utmSource = urlObj.searchParams.get('utm_source');
134
+ const utmMedium = urlObj.searchParams.get('utm_medium');
135
+ const utmCampaign = urlObj.searchParams.get('utm_campaign');
136
+ const utmTerm = urlObj.searchParams.get('utm_term');
137
+ const utmContent = urlObj.searchParams.get('utm_content');
138
+ if (utmSource)
139
+ params.utm_source = utmSource;
140
+ if (utmMedium)
141
+ params.utm_medium = utmMedium;
142
+ if (utmCampaign)
143
+ params.utm_campaign = utmCampaign;
144
+ if (utmTerm)
145
+ params.utm_term = utmTerm;
146
+ if (utmContent)
147
+ params.utm_content = utmContent;
148
+ return params;
149
+ }
150
+ catch {
151
+ return {};
152
+ }
153
+ }
154
+ /**
155
+ * Get first-touch attribution from localStorage
156
+ */
157
+ function getFirstTouchAttribution(tenantId) {
158
+ if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
159
+ return null;
160
+ }
161
+ try {
162
+ const key = `_grain_first_touch_${tenantId}`;
163
+ const stored = localStorage.getItem(key);
164
+ if (stored) {
165
+ return JSON.parse(stored);
166
+ }
167
+ }
168
+ catch (error) {
169
+ console.warn('[Grain Attribution] Failed to retrieve first-touch attribution:', error);
170
+ }
171
+ return null;
172
+ }
173
+ /**
174
+ * Set first-touch attribution in localStorage
175
+ */
176
+ function setFirstTouchAttribution(tenantId, attribution) {
177
+ if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
178
+ return;
179
+ }
180
+ try {
181
+ const key = `_grain_first_touch_${tenantId}`;
182
+ localStorage.setItem(key, JSON.stringify(attribution));
183
+ }
184
+ catch (error) {
185
+ console.warn('[Grain Attribution] Failed to store first-touch attribution:', error);
186
+ }
187
+ }
188
+ /**
189
+ * Get or create first-touch attribution
190
+ */
191
+ function getOrCreateFirstTouchAttribution(tenantId, referrer, currentUrl, utmParams) {
192
+ // Try to get existing first-touch
193
+ const existing = getFirstTouchAttribution(tenantId);
194
+ if (existing) {
195
+ return existing;
196
+ }
197
+ // Create new first-touch attribution
198
+ const referrerCategory = categorizeReferrer(referrer, currentUrl);
199
+ const referrerDomain = extractDomain(referrer);
200
+ const firstTouch = {
201
+ source: utmParams.utm_source || referrerDomain || 'direct',
202
+ medium: utmParams.utm_medium || referrerCategory,
203
+ campaign: utmParams.utm_campaign || 'none',
204
+ referrer: referrer || 'direct',
205
+ referrer_category: referrerCategory,
206
+ timestamp: Date.now(),
207
+ };
208
+ // Store it
209
+ setFirstTouchAttribution(tenantId, firstTouch);
210
+ return firstTouch;
211
+ }
212
+ /**
213
+ * Get session UTM parameters (memory-only, not persisted across page loads)
214
+ */
215
+ let sessionUTMParams = null;
216
+ function getSessionUTMParameters() {
217
+ return sessionUTMParams;
218
+ }
219
+ function setSessionUTMParameters(params) {
220
+ sessionUTMParams = params;
221
+ }
222
+ /**
223
+ * Clear session UTM parameters
224
+ */
225
+ function clearSessionUTMParameters() {
226
+ sessionUTMParams = null;
227
+ }
228
+ //# sourceMappingURL=attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.js","sourceRoot":"","sources":["../../src/attribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Get full country name from user's timezone
3
+ * @returns Full country name or null if timezone is unavailable
4
+ */
5
+ export declare function getCountry(): string | null;
6
+ /**
7
+ * Get country code (ISO 3166-1 alpha-2) from user's timezone
8
+ * Privacy-friendly alternative to IP geolocation
9
+ * @returns ISO 3166-1 alpha-2 country code (e.g., "US", "GB") or "Unknown"
10
+ */
11
+ export declare function getCountryCodeFromTimezone(): string;
12
+ export declare function getState(): string | null;
13
+ //# sourceMappingURL=countries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"countries.d.ts","sourceRoot":"","sources":["../../src/countries.ts"],"names":[],"mappings":"AAyyFA;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CAe1C;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAanD;AAED,wBAAgB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAUxC"}