saasco-sdk 0.1.45 → 0.2.3

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/index.esm.js DELETED
@@ -1,2152 +0,0 @@
1
- import { __awaiter } from 'tslib';
2
- import { isValid, parse } from 'psl';
3
- import { v4 } from '@lukeed/uuid';
4
- import { sha256 } from 'js-sha256';
5
- import { z } from 'zod';
6
-
7
- var version = "0.1.45";
8
-
9
- const timezones = {
10
- 'Asia/Barnaul': 'RU',
11
- 'Africa/Nouakchott': 'MR',
12
- 'Africa/Lusaka': 'ZM',
13
- 'Asia/Pyongyang': 'KP',
14
- 'Europe/Bratislava': 'SK',
15
- 'America/Belize': 'BZ',
16
- 'America/Maceio': 'BR',
17
- 'Pacific/Chuuk': 'FM',
18
- 'Indian/Comoro': 'KM',
19
- 'Pacific/Palau': 'PW',
20
- 'Asia/Jakarta': 'ID',
21
- 'Africa/Windhoek': 'NA',
22
- 'America/Chihuahua': 'MX',
23
- 'America/Nome': 'US',
24
- 'Africa/Mbabane': 'SZ',
25
- 'Africa/Porto-Novo': 'BJ',
26
- 'Europe/San_Marino': 'SM',
27
- 'Pacific/Fakaofo': 'TK',
28
- 'America/Denver': 'US',
29
- 'Europe/Belgrade': 'RS',
30
- 'America/Indiana/Tell_City': 'US',
31
- 'America/Fortaleza': 'BR',
32
- 'America/Halifax': 'CA',
33
- 'Europe/Bucharest': 'RO',
34
- 'America/Indiana/Petersburg': 'US',
35
- 'Europe/Kirov': 'RU',
36
- 'Europe/Athens': 'GR',
37
- 'America/Argentina/Ushuaia': 'AR',
38
- 'Europe/Monaco': 'MC',
39
- 'Europe/Vilnius': 'LT',
40
- 'Europe/Copenhagen': 'DK',
41
- 'Pacific/Kanton': 'KI',
42
- 'America/Caracas': 'VE',
43
- 'Asia/Almaty': 'KZ',
44
- 'Europe/Paris': 'FR',
45
- 'Africa/Blantyre': 'MW',
46
- 'Asia/Muscat': 'OM',
47
- 'America/North_Dakota/Beulah': 'US',
48
- 'America/Matamoros': 'MX',
49
- 'Asia/Irkutsk': 'RU',
50
- 'America/Costa_Rica': 'CR',
51
- 'America/Araguaina': 'BR',
52
- 'Atlantic/Canary': 'ES',
53
- 'America/Santo_Domingo': 'DO',
54
- 'America/Vancouver': 'CA',
55
- 'Africa/Addis_Ababa': 'ET',
56
- 'Africa/Accra': 'GH',
57
- 'Pacific/Kwajalein': 'MH',
58
- 'Asia/Baghdad': 'IQ',
59
- 'Australia/Adelaide': 'AU',
60
- 'Australia/Hobart': 'AU',
61
- 'America/Guayaquil': 'EC',
62
- 'America/Argentina/Tucuman': 'AR',
63
- 'Australia/Lindeman': 'AU',
64
- 'America/New_York': 'US',
65
- 'Pacific/Fiji': 'FJ',
66
- 'America/Antigua': 'AG',
67
- 'Africa/Casablanca': 'MA',
68
- 'America/Paramaribo': 'SR',
69
- 'Africa/Cairo': 'EG',
70
- 'America/Cayenne': 'GF',
71
- 'America/Detroit': 'US',
72
- 'Antarctica/Syowa': 'AQ',
73
- 'Africa/Douala': 'CM',
74
- 'America/Argentina/La_Rioja': 'AR',
75
- 'Africa/Lagos': 'NG',
76
- 'America/St_Barthelemy': 'BL',
77
- 'Asia/Nicosia': 'CY',
78
- 'Asia/Macau': 'MO',
79
- 'Europe/Riga': 'LV',
80
- 'Asia/Ashgabat': 'TM',
81
- 'Indian/Antananarivo': 'MG',
82
- 'America/Argentina/San_Juan': 'AR',
83
- 'Asia/Aden': 'YE',
84
- 'Asia/Tomsk': 'RU',
85
- 'America/Asuncion': 'PY',
86
- 'Pacific/Bougainville': 'PG',
87
- 'Asia/Vientiane': 'LA',
88
- 'America/Mazatlan': 'MX',
89
- 'Africa/Luanda': 'AO',
90
- 'Europe/Oslo': 'NO',
91
- 'Africa/Kinshasa': 'CD',
92
- 'Europe/Warsaw': 'PL',
93
- 'America/Grand_Turk': 'TC',
94
- 'Asia/Seoul': 'KR',
95
- 'Africa/Tripoli': 'LY',
96
- 'America/St_Thomas': 'VI',
97
- 'Asia/Kathmandu': 'NP',
98
- 'Pacific/Pitcairn': 'PN',
99
- 'Pacific/Nauru': 'NR',
100
- 'America/Curacao': 'CW',
101
- 'Asia/Kabul': 'AF',
102
- 'Pacific/Tongatapu': 'TO',
103
- 'Europe/Simferopol': 'UA',
104
- 'Asia/Ust-Nera': 'RU',
105
- 'Africa/Mogadishu': 'SO',
106
- 'Indian/Mayotte': 'YT',
107
- 'Pacific/Niue': 'NU',
108
- 'America/Thunder_Bay': 'CA',
109
- 'Atlantic/Azores': 'PT',
110
- 'Pacific/Gambier': 'PF',
111
- 'Europe/Stockholm': 'SE',
112
- 'Africa/Libreville': 'GA',
113
- 'America/Punta_Arenas': 'CL',
114
- 'America/Guatemala': 'GT',
115
- 'America/Noronha': 'BR',
116
- 'Europe/Helsinki': 'FI',
117
- 'Asia/Gaza': 'PS',
118
- 'Pacific/Kosrae': 'FM',
119
- 'America/Aruba': 'AW',
120
- 'America/Nassau': 'BS',
121
- 'Asia/Choibalsan': 'MN',
122
- 'America/Winnipeg': 'CA',
123
- 'America/Anguilla': 'AI',
124
- 'Asia/Thimphu': 'BT',
125
- 'Asia/Beirut': 'LB',
126
- 'Atlantic/Faroe': 'FO',
127
- 'Europe/Berlin': 'DE',
128
- 'Europe/Amsterdam': 'NL',
129
- 'Pacific/Honolulu': 'US',
130
- 'America/Regina': 'CA',
131
- 'America/Scoresbysund': 'GL',
132
- 'Europe/Vienna': 'AT',
133
- 'Europe/Tirane': 'AL',
134
- 'Africa/El_Aaiun': 'EH',
135
- 'America/Creston': 'CA',
136
- 'Asia/Qostanay': 'KZ',
137
- 'Asia/Ho_Chi_Minh': 'VN',
138
- 'Europe/Samara': 'RU',
139
- 'Europe/Rome': 'IT',
140
- 'Australia/Eucla': 'AU',
141
- 'America/El_Salvador': 'SV',
142
- 'America/Chicago': 'US',
143
- 'Africa/Abidjan': 'CI',
144
- 'Asia/Kamchatka': 'RU',
145
- 'Pacific/Tarawa': 'KI',
146
- 'America/Santiago': 'CL',
147
- 'America/Bahia': 'BR',
148
- 'Indian/Christmas': 'CX',
149
- 'Asia/Atyrau': 'KZ',
150
- 'Asia/Dushanbe': 'TJ',
151
- 'Europe/Ulyanovsk': 'RU',
152
- 'America/Yellowknife': 'CA',
153
- 'America/Recife': 'BR',
154
- 'Australia/Sydney': 'AU',
155
- 'America/Fort_Nelson': 'CA',
156
- 'Pacific/Efate': 'VU',
157
- 'Europe/Saratov': 'RU',
158
- 'Africa/Banjul': 'GM',
159
- 'Asia/Omsk': 'RU',
160
- 'Europe/Ljubljana': 'SI',
161
- 'Europe/Budapest': 'HU',
162
- 'Europe/Astrakhan': 'RU',
163
- 'America/Argentina/Buenos_Aires': 'AR',
164
- 'Pacific/Chatham': 'NZ',
165
- 'America/Argentina/Salta': 'AR',
166
- 'Africa/Niamey': 'NE',
167
- 'Asia/Pontianak': 'ID',
168
- 'Indian/Reunion': 'RE',
169
- 'Asia/Hong_Kong': 'HK',
170
- 'Antarctica/McMurdo': 'AQ',
171
- 'Africa/Malabo': 'GQ',
172
- 'America/Los_Angeles': 'US',
173
- 'America/Argentina/Cordoba': 'AR',
174
- 'Pacific/Pohnpei': 'FM',
175
- 'America/Tijuana': 'MX',
176
- 'America/Campo_Grande': 'BR',
177
- 'America/Dawson_Creek': 'CA',
178
- 'Asia/Novosibirsk': 'RU',
179
- 'Pacific/Pago_Pago': 'AS',
180
- 'Asia/Jerusalem': 'IL',
181
- 'Europe/Sarajevo': 'BA',
182
- 'Africa/Freetown': 'SL',
183
- 'Asia/Yekaterinburg': 'RU',
184
- 'America/Juneau': 'US',
185
- 'Africa/Ouagadougou': 'BF',
186
- 'Africa/Monrovia': 'LR',
187
- 'Europe/Kiev': 'UA',
188
- 'America/Argentina/San_Luis': 'AR',
189
- 'Asia/Tokyo': 'JP',
190
- 'Asia/Qatar': 'QA',
191
- 'America/La_Paz': 'BO',
192
- 'America/Bogota': 'CO',
193
- 'America/Thule': 'GL',
194
- 'Asia/Manila': 'PH',
195
- 'Asia/Hovd': 'MN',
196
- 'Asia/Tehran': 'IR',
197
- 'Atlantic/Madeira': 'PT',
198
- 'America/Metlakatla': 'US',
199
- 'Europe/Vatican': 'VA',
200
- 'Asia/Bishkek': 'KG',
201
- 'Asia/Dili': 'TL',
202
- 'Antarctica/Palmer': 'AQ',
203
- 'Atlantic/Cape_Verde': 'CV',
204
- 'Indian/Chagos': 'IO',
205
- 'America/Kentucky/Monticello': 'US',
206
- 'Africa/Algiers': 'DZ',
207
- 'Africa/Maseru': 'LS',
208
- 'Asia/Kuala_Lumpur': 'MY',
209
- 'Africa/Khartoum': 'SD',
210
- 'America/Argentina/Rio_Gallegos': 'AR',
211
- 'America/Blanc-Sablon': 'CA',
212
- 'Africa/Maputo': 'MZ',
213
- 'America/Tortola': 'VG',
214
- 'Atlantic/Bermuda': 'BM',
215
- 'America/Argentina/Catamarca': 'AR',
216
- 'America/Cayman': 'KY',
217
- 'America/Puerto_Rico': 'PR',
218
- 'Pacific/Majuro': 'MH',
219
- 'Europe/Busingen': 'DE',
220
- 'Pacific/Midway': 'UM',
221
- 'Indian/Cocos': 'CC',
222
- 'Asia/Singapore': 'SG',
223
- 'America/Boise': 'US',
224
- 'America/Nuuk': 'GL',
225
- 'America/Goose_Bay': 'CA',
226
- 'Australia/Broken_Hill': 'AU',
227
- 'Africa/Dar_es_Salaam': 'TZ',
228
- 'Africa/Asmara': 'ER',
229
- 'Asia/Samarkand': 'UZ',
230
- 'Asia/Tbilisi': 'GE',
231
- 'America/Argentina/Jujuy': 'AR',
232
- 'America/Indiana/Winamac': 'US',
233
- 'America/Porto_Velho': 'BR',
234
- 'Asia/Magadan': 'RU',
235
- 'Europe/Zaporozhye': 'UA',
236
- 'Antarctica/Casey': 'AQ',
237
- 'Asia/Shanghai': 'CN',
238
- 'Pacific/Norfolk': 'NF',
239
- 'Europe/Guernsey': 'GG',
240
- 'Australia/Brisbane': 'AU',
241
- 'Antarctica/DumontDUrville': 'AQ',
242
- 'America/Havana': 'CU',
243
- 'America/Atikokan': 'CA',
244
- 'America/Mexico_City': 'MX',
245
- 'America/Rankin_Inlet': 'CA',
246
- 'America/Cuiaba': 'BR',
247
- 'America/Resolute': 'CA',
248
- 'Africa/Ceuta': 'ES',
249
- 'Arctic/Longyearbyen': 'SJ',
250
- 'Pacific/Guam': 'GU',
251
- 'Asia/Damascus': 'SY',
252
- 'Asia/Colombo': 'LK',
253
- 'Asia/Yerevan': 'AM',
254
- 'America/Montserrat': 'MS',
255
- 'America/Belem': 'BR',
256
- 'Europe/Kaliningrad': 'RU',
257
- 'Atlantic/South_Georgia': 'GS',
258
- 'Asia/Tashkent': 'UZ',
259
- 'Asia/Kolkata': 'IN',
260
- 'America/St_Johns': 'CA',
261
- 'Asia/Srednekolymsk': 'RU',
262
- 'Asia/Yakutsk': 'RU',
263
- 'Europe/Prague': 'CZ',
264
- 'Africa/Djibouti': 'DJ',
265
- 'Asia/Dubai': 'AE',
266
- 'Europe/Uzhgorod': 'UA',
267
- 'America/Edmonton': 'CA',
268
- 'Asia/Famagusta': 'CY',
269
- 'America/Indiana/Knox': 'US',
270
- 'Asia/Hebron': 'PS',
271
- 'Asia/Taipei': 'TW',
272
- 'Europe/London': 'GB',
273
- 'Africa/Dakar': 'SN',
274
- 'Australia/Darwin': 'AU',
275
- 'America/Glace_Bay': 'CA',
276
- 'Antarctica/Vostok': 'AQ',
277
- 'America/Indiana/Vincennes': 'US',
278
- 'America/Nipigon': 'CA',
279
- 'Asia/Kuwait': 'KW',
280
- 'Pacific/Guadalcanal': 'SB',
281
- 'America/Toronto': 'CA',
282
- 'Africa/Gaborone': 'BW',
283
- 'Africa/Bujumbura': 'BI',
284
- 'Africa/Lubumbashi': 'CD',
285
- 'America/Merida': 'MX',
286
- 'America/Marigot': 'MF',
287
- 'Europe/Zagreb': 'HR',
288
- 'Pacific/Easter': 'CL',
289
- 'America/Santarem': 'BR',
290
- 'Pacific/Noumea': 'NC',
291
- 'America/Sitka': 'US',
292
- 'Atlantic/Stanley': 'FK',
293
- 'Pacific/Funafuti': 'TV',
294
- 'America/Iqaluit': 'CA',
295
- 'America/Rainy_River': 'CA',
296
- 'America/Anchorage': 'US',
297
- 'America/Lima': 'PE',
298
- 'Asia/Baku': 'AZ',
299
- 'America/Indiana/Vevay': 'US',
300
- 'Asia/Ulaanbaatar': 'MN',
301
- 'America/Managua': 'NI',
302
- 'Asia/Krasnoyarsk': 'RU',
303
- 'Asia/Qyzylorda': 'KZ',
304
- 'America/Eirunepe': 'BR',
305
- 'Europe/Podgorica': 'ME',
306
- 'Europe/Chisinau': 'MD',
307
- 'Europe/Mariehamn': 'AX',
308
- 'Europe/Volgograd': 'RU',
309
- 'Africa/Nairobi': 'KE',
310
- 'Europe/Isle_of_Man': 'IM',
311
- 'America/Menominee': 'US',
312
- 'Africa/Harare': 'ZW',
313
- 'Asia/Anadyr': 'RU',
314
- 'America/Moncton': 'CA',
315
- 'Indian/Maldives': 'MV',
316
- 'America/Whitehorse': 'CA',
317
- 'Antarctica/Mawson': 'AQ',
318
- 'Europe/Madrid': 'ES',
319
- 'America/Argentina/Mendoza': 'AR',
320
- 'America/Manaus': 'BR',
321
- 'Africa/Bangui': 'CF',
322
- 'Indian/Mauritius': 'MU',
323
- 'Africa/Tunis': 'TN',
324
- 'Australia/Lord_Howe': 'AU',
325
- 'America/Kentucky/Louisville': 'US',
326
- 'America/North_Dakota/Center': 'US',
327
- 'Asia/Novokuznetsk': 'RU',
328
- 'Asia/Makassar': 'ID',
329
- 'America/Port_of_Spain': 'TT',
330
- 'America/Bahia_Banderas': 'MX',
331
- 'Pacific/Auckland': 'NZ',
332
- 'America/Sao_Paulo': 'BR',
333
- 'Asia/Dhaka': 'BD',
334
- 'America/Pangnirtung': 'CA',
335
- 'Europe/Dublin': 'IE',
336
- 'Asia/Brunei': 'BN',
337
- 'Africa/Brazzaville': 'CG',
338
- 'America/Montevideo': 'UY',
339
- 'America/Jamaica': 'JM',
340
- 'America/Indiana/Indianapolis': 'US',
341
- 'America/Kralendijk': 'BQ',
342
- 'Europe/Gibraltar': 'GI',
343
- 'Pacific/Marquesas': 'PF',
344
- 'Pacific/Apia': 'WS',
345
- 'Europe/Jersey': 'JE',
346
- 'America/Phoenix': 'US',
347
- 'Africa/Ndjamena': 'TD',
348
- 'Asia/Karachi': 'PK',
349
- 'Africa/Kampala': 'UG',
350
- 'Asia/Sakhalin': 'RU',
351
- 'America/Martinique': 'MQ',
352
- 'Europe/Moscow': 'RU',
353
- 'Africa/Conakry': 'GN',
354
- 'America/Barbados': 'BB',
355
- 'Africa/Lome': 'TG',
356
- 'America/Ojinaga': 'MX',
357
- 'America/Tegucigalpa': 'HN',
358
- 'Asia/Bangkok': 'TH',
359
- 'Africa/Johannesburg': 'ZA',
360
- 'Europe/Vaduz': 'LI',
361
- 'Africa/Sao_Tome': 'ST',
362
- 'America/Cambridge_Bay': 'CA',
363
- 'America/Lower_Princes': 'SX',
364
- 'America/Miquelon': 'PM',
365
- 'America/St_Kitts': 'KN',
366
- 'Australia/Melbourne': 'AU',
367
- 'Europe/Minsk': 'BY',
368
- 'Asia/Vladivostok': 'RU',
369
- 'Europe/Sofia': 'BG',
370
- 'Antarctica/Davis': 'AQ',
371
- 'Pacific/Galapagos': 'EC',
372
- 'America/North_Dakota/New_Salem': 'US',
373
- 'Asia/Amman': 'JO',
374
- 'Pacific/Wallis': 'WF',
375
- 'America/Hermosillo': 'MX',
376
- 'Pacific/Kiritimati': 'KI',
377
- 'Antarctica/Macquarie': 'AU',
378
- 'America/Guyana': 'GY',
379
- 'Asia/Riyadh': 'SA',
380
- 'Pacific/Tahiti': 'PF',
381
- 'America/St_Vincent': 'VC',
382
- 'America/Cancun': 'MX',
383
- 'America/Grenada': 'GD',
384
- 'Pacific/Wake': 'UM',
385
- 'America/Dawson': 'CA',
386
- 'Europe/Brussels': 'BE',
387
- 'Indian/Kerguelen': 'TF',
388
- 'America/Yakutat': 'US',
389
- 'Indian/Mahe': 'SC',
390
- 'Atlantic/Reykjavik': 'IS',
391
- 'America/Panama': 'PA',
392
- 'America/Guadeloupe': 'GP',
393
- 'Europe/Malta': 'MT',
394
- 'Antarctica/Troll': 'AQ',
395
- 'Asia/Jayapura': 'ID',
396
- 'Asia/Bahrain': 'BH',
397
- 'Asia/Chita': 'RU',
398
- 'Europe/Tallinn': 'EE',
399
- 'Asia/Khandyga': 'RU',
400
- 'America/Rio_Branco': 'BR',
401
- 'Atlantic/St_Helena': 'SH',
402
- 'Africa/Juba': 'SS',
403
- 'America/Adak': 'US',
404
- 'Pacific/Saipan': 'MP',
405
- 'America/St_Lucia': 'LC',
406
- 'America/Inuvik': 'CA',
407
- 'Europe/Luxembourg': 'LU',
408
- 'Africa/Bissau': 'GW',
409
- 'Asia/Oral': 'KZ',
410
- 'America/Boa_Vista': 'BR',
411
- 'Europe/Skopje': 'MK',
412
- 'America/Port-au-Prince': 'HT',
413
- 'Pacific/Port_Moresby': 'PG',
414
- 'Europe/Andorra': 'AD',
415
- 'America/Indiana/Marengo': 'US',
416
- 'Africa/Kigali': 'RW',
417
- 'Africa/Bamako': 'ML',
418
- 'America/Dominica': 'DM',
419
- 'Asia/Aqtobe': 'KZ',
420
- 'Europe/Istanbul': 'TR',
421
- 'Pacific/Rarotonga': 'CK',
422
- 'America/Danmarkshavn': 'GL',
423
- 'Europe/Zurich': 'CH',
424
- 'Asia/Yangon': 'MM',
425
- 'America/Monterrey': 'MX',
426
- 'Europe/Lisbon': 'PT',
427
- 'Asia/Kuching': 'MY',
428
- 'Antarctica/Rothera': 'AQ',
429
- 'Australia/Perth': 'AU',
430
- 'Asia/Phnom_Penh': 'KH',
431
- 'America/Swift_Current': 'CA',
432
- 'Asia/Aqtau': 'KZ',
433
- 'Asia/Urumqi': 'CN',
434
- 'Asia/Calcutta': 'IN'
435
- };
436
-
437
- function getBrowserContext() {
438
- var _a;
439
- const isBrowser = typeof window !== 'undefined';
440
- if (!isBrowser) throw new Error('getBrowserContext can only be called in browser');
441
- const customNavigator = navigator;
442
- const $locale = (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages) && (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages.length) ? customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages[0] : (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.userLanguage) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.language) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.browserLanguage) || 'en';
443
- // https://caniuse.com/?search=Intl.DateTimeFormat().resolvedOptions().timeZone
444
- // Only has 96.63% global support, so we need to check for undefined
445
- const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
446
- const $location = timezones[timezone] || ((_a = $locale === null || $locale === void 0 ? void 0 : $locale.split('-')) === null || _a === void 0 ? void 0 : _a[1]) || 'undefined';
447
- const $href = window.location.href;
448
- const $pathname = window.location.pathname;
449
- const $referrer = document.referrer;
450
- const $screenDPI = window.devicePixelRatio;
451
- const $screenHeight = screen.height;
452
- const $screenWidth = screen.width;
453
- const $title = document.title;
454
- const $userAgent = window.navigator.userAgent;
455
- const params = new URLSearchParams(window.location.search);
456
- const $utmSource = params.get('utm_source');
457
- const $utmMedium = params.get('utm_medium');
458
- const $utmCampaign = params.get('utm_campaign');
459
- const $utmTerm = params.get('utm_term');
460
- const $utmContent = params.get('utm_content');
461
- const $utmId = params.get('utm_id');
462
- const $utmSourcePlatform = params.get('utm_source_platform');
463
- const $utmCampaignId = params.get('utm_campaign_id');
464
- const $utmCreativeFormat = params.get('utm_creative_format');
465
- const $utmMarketingTactic = params.get('utm_marketing_tactic');
466
- let $utmAdSource = params.get('utm_ad_source');
467
- let $utmAdId = params.get('utm_ad_id');
468
- // If the adId is not set directly attempt to get it from each ad network
469
- // Also set the ad source to the ad network if it's not set
470
- if (!$utmAdId) {
471
- const fbAdId = params.get('fbadid');
472
- if (fbAdId) {
473
- $utmAdId = fbAdId;
474
- if (!$utmAdSource) {
475
- $utmAdSource = 'facebook';
476
- }
477
- }
478
- const googleAdId = params.get('gadid');
479
- if (googleAdId) {
480
- $utmAdId = googleAdId;
481
- if (!$utmAdSource) {
482
- $utmAdSource = 'google';
483
- }
484
- }
485
- const xAdId = params.get('xadid');
486
- if (xAdId) {
487
- $utmAdId = xAdId;
488
- if (!$utmAdSource) {
489
- $utmAdSource = 'x';
490
- }
491
- }
492
- const pinterestAdId = params.get('padid');
493
- if (pinterestAdId) {
494
- $utmAdId = pinterestAdId;
495
- if (!$utmAdSource) {
496
- $utmAdSource = 'pinterest';
497
- }
498
- }
499
- const tiktokAdId = params.get('ttadid');
500
- if (tiktokAdId) {
501
- $utmAdId = tiktokAdId;
502
- if (!$utmAdSource) {
503
- $utmAdSource = 'tiktok';
504
- }
505
- }
506
- const snapchatAdId = params.get('scadid');
507
- if (snapchatAdId) {
508
- $utmAdId = snapchatAdId;
509
- if (!$utmAdSource) {
510
- $utmAdSource = 'snapchat';
511
- }
512
- }
513
- const redditAdId = params.get('radid');
514
- if (redditAdId) {
515
- $utmAdId = redditAdId;
516
- if (!$utmAdSource) {
517
- $utmAdSource = 'reddit';
518
- }
519
- }
520
- const linkedinAdId = params.get('liadid');
521
- if (linkedinAdId) {
522
- $utmAdId = linkedinAdId;
523
- if (!$utmAdSource) {
524
- $utmAdSource = 'linkedin';
525
- }
526
- }
527
- }
528
- return {
529
- $locale,
530
- $location,
531
- $href,
532
- $pathname,
533
- $referrer,
534
- $screenDPI,
535
- $screenHeight,
536
- $screenWidth,
537
- $title,
538
- $userAgent,
539
- $utmSource,
540
- $utmMedium,
541
- $utmCampaign,
542
- $utmTerm,
543
- $utmContent,
544
- $utmId,
545
- $utmSourcePlatform,
546
- $utmCampaignId,
547
- $utmCreativeFormat,
548
- $utmMarketingTactic,
549
- $utmAdSource,
550
- $utmAdId
551
- };
552
- }
553
-
554
- var LogLevel;
555
- (function (LogLevel) {
556
- LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
557
- LogLevel[LogLevel["WARN"] = 1] = "WARN";
558
- LogLevel[LogLevel["INFO"] = 2] = "INFO";
559
- LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
560
- })(LogLevel || (LogLevel = {}));
561
- class AnalyticsLogger {
562
- constructor(config) {
563
- this.config = config;
564
- }
565
- debug(...args) {
566
- if (this.config.level < LogLevel.DEBUG) return;
567
- const icon = '◍';
568
- const message = `${icon} ${this.config.label}`;
569
- console.debug(`%c ${message}`, `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
570
- }
571
- /**
572
- * Log debug information
573
- */
574
- info(...args) {
575
- if (this.config.level < LogLevel.INFO) return;
576
- const icon = '◍';
577
- const message = `${icon} ${this.config.label}`;
578
- console.info(`%c ${message}`, `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
579
- }
580
- /**
581
- * Log warning information
582
- */
583
- warn(...args) {
584
- if (this.config.level < LogLevel.WARN) return;
585
- const icon = '◍';
586
- const message = `${icon} ${this.config.label}`;
587
- console.warn(`%c ${message}`, `background: #ffa500; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
588
- }
589
- /**
590
- * Log error information
591
- */
592
- error(...args) {
593
- const icon = '◍';
594
- const message = `${icon} ${this.config.label} Error`;
595
- console.error(`%c ${message}`, 'background: red; color: white; padding-right: 6px; border-radius: 2px;', ...args);
596
- return {
597
- success: false,
598
- message: args.join(' ')
599
- };
600
- }
601
- }
602
-
603
- const isBrowser$4 = typeof window !== 'undefined';
604
- const isServer$4 = !isBrowser$4;
605
- /**
606
- * Get the logger level for a specific integration
607
- * Supports URL parameters like saasco-debug-{integration-type}=true
608
- * If debug is true or URL param is true, sets level to DEBUG (3), otherwise WARN
609
- */
610
- function getIntegrationLoggerLevel(integrationName, debug) {
611
- // Check URL parameters for debug overrides (browser only)
612
- let urlDebug = false;
613
- if (!isServer$4) {
614
- try {
615
- const urlParams = new URLSearchParams(window.location.search);
616
- const paramName = `saasco-debug-${integrationName}`;
617
- const paramValue = urlParams.get(paramName);
618
- if (paramValue === 'true') {
619
- urlDebug = true;
620
- }
621
- } catch (error) {
622
- // Ignore URL parsing errors
623
- }
624
- }
625
- // URL parameters override config, config overrides default (WARN)
626
- return urlDebug || debug ? LogLevel.DEBUG : LogLevel.WARN;
627
- }
628
-
629
- /**
630
- * Generate a collision-resistant UUID
631
- * Uses lukeed's UUID v4 implementation for consistent, fast UUID generation
632
- */
633
- function uuid() {
634
- return v4();
635
- }
636
-
637
- const isBrowser$3 = typeof window !== 'undefined';
638
- const isServer$3 = !isBrowser$3;
639
- const standardFacebookEvents = ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'CompleteRegistration', 'Contact', 'CustomizeProduct', 'Donate', 'FindLocation', 'InitiateCheckout', 'Lead', 'Purchase', 'Schedule', 'Search', 'StartTrial', 'SubmitApplication', 'Subscribe', 'ViewContent', 'PageView'];
640
- /**
641
- * Create a Facebook Pixel integration instance
642
- */
643
- function createFacebookPixelIntegration(config, debug) {
644
- let isPixelReady = false;
645
- // Create integration logger
646
- const integrationLoggerLevel = getIntegrationLoggerLevel('facebook-pixel', debug);
647
- const logger = new AnalyticsLogger({
648
- level: integrationLoggerLevel,
649
- label: 'Saasco facebook-pixel'
650
- });
651
- return {
652
- name: 'facebook-pixel',
653
- environments: ['client'],
654
- init: () => __awaiter(this, void 0, void 0, function* () {
655
- if (isServer$3) {
656
- logger.debug('Facebook Pixel init skipped on server');
657
- return;
658
- }
659
- logger.info('Initializing Facebook Pixel', {
660
- pixelId: config.pixelId
661
- });
662
- // Check if Facebook Pixel is already loaded
663
- if (window.fbq || window._fbq) {
664
- logger.warn('Facebook Pixel is already initialized');
665
- isPixelReady = true;
666
- return;
667
- }
668
- // Initialize Facebook Pixel
669
- return new Promise((resolve, reject) => {
670
- try {
671
- (function (f, b, e, v, n, t, s) {
672
- if (f.fbq) return;
673
- n = f.fbq = function () {
674
- n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
675
- };
676
- if (!f._fbq) f._fbq = n;
677
- n.push = n;
678
- n.loaded = !0;
679
- n.version = '2.0';
680
- n.queue = [];
681
- t = b.createElement(e);
682
- t.async = !0;
683
- t.src = v;
684
- // Add load event listener
685
- t.onload = () => {
686
- isPixelReady = true;
687
- logger.info('Facebook Pixel script loaded successfully');
688
- resolve();
689
- };
690
- t.onerror = () => {
691
- logger.error('Failed to load Facebook Pixel script');
692
- reject(new Error('Failed to load Facebook Pixel script'));
693
- };
694
- s = b.getElementsByTagName(e)[0];
695
- s.parentNode.insertBefore(t, s);
696
- })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
697
- // Prevent automatic PageView tracking
698
- window.fbq.disablePushState = true;
699
- // Allow duplicate PageView events
700
- window.fbq.allowDuplicatePageViews = true;
701
- if (config.automaticConfiguration === false) {
702
- logger.debug('Disabling automatic configuration');
703
- window.fbq('set', 'autoConfig', false, config.pixelId);
704
- }
705
- // Initialize pixel
706
- logger.debug('Initializing Facebook Pixel with ID', config.pixelId);
707
- window.fbq('init', config.pixelId);
708
- // If script loads synchronously, mark as ready
709
- if (window.fbq && typeof window.fbq === 'function') {
710
- isPixelReady = true;
711
- logger.info('Facebook Pixel initialized synchronously');
712
- resolve();
713
- }
714
- } catch (error) {
715
- logger.error('Error during Facebook Pixel initialization', error);
716
- reject(error);
717
- }
718
- });
719
- }),
720
- track: (eventName, properties, context) => {
721
- if (!isPixelReady || !window.fbq) {
722
- logger.warn('Facebook Pixel not ready, skipping track event', {
723
- eventName
724
- });
725
- return;
726
- }
727
- const fbEventName = getFacebookEventName(eventName, config.eventMapping);
728
- const eventsWithParams = ['Purchase', 'StartTrial', 'Subscribe'];
729
- const isStandardEvent = standardFacebookEvents.includes(fbEventName);
730
- const trackType = isStandardEvent ? 'track' : 'trackCustom';
731
- logger.debug('Tracking Facebook Pixel event', {
732
- originalEvent: eventName,
733
- fbEventName,
734
- trackType,
735
- isStandardEvent,
736
- properties
737
- });
738
- if (eventsWithParams.includes(fbEventName) && properties) {
739
- const fbParams = {};
740
- if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
741
- if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
742
- if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
743
- logger.info('Tracking Facebook Pixel event with parameters', {
744
- event: fbEventName,
745
- params: fbParams
746
- });
747
- window.fbq(trackType, fbEventName, fbParams);
748
- } else {
749
- logger.info('Tracking Facebook Pixel event', {
750
- event: fbEventName
751
- });
752
- window.fbq(trackType, fbEventName);
753
- }
754
- },
755
- identify: (userId, properties, context) => {
756
- try {
757
- if (typeof window !== 'undefined' && window.fbq) {
758
- const amValues = coerceMetaConversionsAmValues(Object.assign(Object.assign({}, properties || {}), userId ? {
759
- external_id: userId
760
- } : {}));
761
- // Advanced Matching
762
- // Add event to identify user and hash email field
763
- // FB pixel handles hashing automatically
764
- // https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching/
765
- const am = {};
766
- if (amValues.external_id) am['external_id'] = amValues.external_id;
767
- if (amValues.em) am['em'] = sha256(amValues.em);
768
- if (amValues.fn) am['fn'] = amValues.fn;
769
- if (amValues.ln) am['ln'] = amValues.ln;
770
- if (amValues.ph) am['ph'] = amValues.ph;
771
- if (amValues.ge) am['ge'] = amValues.ge;
772
- if (amValues.db) am['db'] = amValues.db;
773
- if (amValues.ct) am['ct'] = amValues.ct;
774
- if (amValues.st) am['st'] = amValues.st;
775
- if (amValues.zp) am['zp'] = amValues.zp;
776
- if (amValues.country) am['country'] = amValues.country;
777
- if (Object.keys(am).length > 0) {
778
- window.fbq('init', config.pixelId, am);
779
- }
780
- }
781
- } catch (error) {
782
- console.error('Error identifying user in Meta Pixel:', error);
783
- }
784
- }
785
- };
786
- }
787
- function getFacebookEventName(eventName, eventMapping) {
788
- // Convert default saasco Page View event by default
789
- if (eventName === 'Page View') return 'PageView';
790
- if (!eventMapping) return eventName;
791
- return eventMapping[eventName] || eventName;
792
- }
793
- /**
794
- * Get Advanced Matching values from properties
795
- *
796
- * Documentation for Pixel
797
- * https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching/
798
- *
799
- * Documentation for Conversions API
800
- * https://developers.secure.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters
801
- *
802
- * @param properties
803
- * @returns
804
- */
805
- function coerceMetaConversionsAmValues(properties) {
806
- const coerce = [{
807
- key: 'em',
808
- coerceFrom: ['email', 'user_email', 'userEmail', 'email_address', 'emailAddress', 'e_mail', 'E_Mail', 'mail', 'Mail', 'contact_email', 'contactEmail', 'primary_email', 'primaryEmail'],
809
- transform: value => {
810
- if (typeof value !== 'string') return undefined;
811
- const normalized = value.trim().toLowerCase();
812
- return normalized || undefined;
813
- }
814
- }, {
815
- key: 'fn',
816
- coerceFrom: ['first_name', 'firstName', 'firstname', 'given_name', 'givenName', 'user_first_name', 'userFirstName', 'name_first', 'nameFirst', 'f_name', 'fName'],
817
- transform: value => {
818
- if (typeof value !== 'string') return undefined;
819
- const normalized = value.trim().toLowerCase();
820
- return normalized || undefined;
821
- }
822
- }, {
823
- key: 'ln',
824
- coerceFrom: ['last_name', 'lastName', 'lastname', 'surname', 'user_last_name', 'userLastName', 'name_last', 'nameLast', 'l_name', 'lName'],
825
- transform: value => {
826
- if (typeof value !== 'string') return undefined;
827
- const normalized = value.trim().toLowerCase();
828
- return normalized || undefined;
829
- }
830
- }, {
831
- key: 'ph',
832
- coerceFrom: ['phone', 'phone_number', 'phoneNumber', 'mobile', 'mobile_number', 'mobileNumber', 'contact_number', 'contactNumber', 'tel', 'telephone'],
833
- transform: value => {
834
- if (typeof value !== 'string' && typeof value !== 'number') return undefined;
835
- const phoneStr = String(value).replace(/\D/g, '');
836
- return phoneStr || undefined;
837
- }
838
- }, {
839
- key: 'external_id',
840
- coerceFrom: ['external_id', 'externalId', 'user_id', 'userId', 'id', 'distinctId', 'distinct_id', 'customer_id', 'customerId', 'member_id', 'memberId', 'loyalty_id', 'loyaltyId'],
841
- transform: value => {
842
- if (value == null) return undefined;
843
- return String(value) || undefined;
844
- }
845
- }, {
846
- key: 'ge',
847
- coerceFrom: ['gender', 'user_gender', 'userGender', 'sex', 'user_sex', 'userSex'],
848
- transform: value => {
849
- if (typeof value !== 'string') return undefined;
850
- const normalized = value.trim().toLowerCase();
851
- if (normalized === 'female' || normalized === 'f') return 'f';
852
- if (normalized === 'male' || normalized === 'm') return 'm';
853
- return undefined;
854
- }
855
- }, {
856
- key: 'db',
857
- coerceFrom: ['birthday', 'birth_date', 'birthDate', 'date_of_birth', 'dateOfBirth', 'dob', 'Dob', 'birth_day', 'birthDay'],
858
- transform: value => {
859
- if (!value) return undefined;
860
- let dateStr;
861
- if (value instanceof Date) {
862
- dateStr = value.toISOString().split('T')[0];
863
- } else {
864
- dateStr = String(value);
865
- }
866
- const dateMatch = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/);
867
- if (dateMatch) {
868
- const [, year, month, day] = dateMatch;
869
- return `${year}${month}${day}`;
870
- }
871
- const numericMatch = dateStr.replace(/\D/g, '');
872
- if (numericMatch.length === 8) {
873
- return numericMatch;
874
- }
875
- return undefined;
876
- }
877
- }, {
878
- key: 'ct',
879
- coerceFrom: ['city', 'user_city', 'userCity', 'location_city', 'locationCity', 'address_city', 'addressCity'],
880
- transform: value => {
881
- if (typeof value !== 'string') return undefined;
882
- const normalized = value.trim().toLowerCase().replace(/\s+/g, '');
883
- return normalized || undefined;
884
- }
885
- }, {
886
- key: 'st',
887
- coerceFrom: ['state', 'user_state', 'userState', 'province', 'user_province', 'userProvince', 'region', 'user_region', 'userRegion', 'location_state', 'locationState', 'address_state', 'addressState'],
888
- transform: value => {
889
- if (typeof value !== 'string') return undefined;
890
- const normalized = value.trim().toLowerCase();
891
- if (normalized.length === 2) return normalized;
892
- return undefined;
893
- }
894
- }, {
895
- key: 'zp',
896
- coerceFrom: ['zip', 'zipcode', 'zip_code', 'postal_code', 'postalCode', 'postcode', 'user_zip', 'userZip', 'address_zip', 'addressZip'],
897
- transform: value => {
898
- if (typeof value !== 'string' && typeof value !== 'number') return undefined;
899
- return String(value).trim() || undefined;
900
- }
901
- }, {
902
- key: 'country',
903
- coerceFrom: ['country', 'country_code', 'countryCode', 'user_country', 'userCountry', 'location_country', 'locationCountry', 'address_country', 'addressCountry'],
904
- transform: value => {
905
- if (typeof value !== 'string') return undefined;
906
- const normalized = value.trim().toLowerCase();
907
- if (normalized.length === 2) return normalized;
908
- return undefined;
909
- }
910
- }];
911
- const result = {};
912
- for (const {
913
- key,
914
- coerceFrom,
915
- transform
916
- } of coerce) {
917
- const foundValue = findFirstProperty$1(properties, coerceFrom);
918
- if (foundValue !== null && transform) {
919
- const transformedValue = transform(foundValue);
920
- if (transformedValue) {
921
- result[key] = transformedValue;
922
- }
923
- } else if (foundValue !== null && typeof foundValue === 'string') {
924
- result[key] = foundValue;
925
- }
926
- }
927
- return result;
928
- }
929
- function findFirstProperty$1(properties, keys) {
930
- for (const key of keys) {
931
- if (properties[key] !== undefined) {
932
- return properties[key];
933
- }
934
- }
935
- return null;
936
- }
937
-
938
- const isBrowser$2 = typeof window !== 'undefined';
939
- const isServer$2 = !isBrowser$2;
940
- /**
941
- * Standard Pinterest events
942
- * Reference: https://www.pinterest.com/_/_/help/business/article/event-code
943
- */
944
- const standardPinterestEvents = ['checkout', 'addtocart', 'pagevisit', 'signup', 'watchvideo', 'lead', 'search', 'viewcategory', 'custom', 'addpaymentinfo', 'addtowishlist', 'initiatecheckout', 'subscribe', 'viewcontent'];
945
- /**
946
- * Create a Pinterest Tag integration instance
947
- * Documentation: https://help.pinterest.com/en/business/article/install-the-pinterest-tag
948
- */
949
- function createPinterestTagIntegration(config, debug) {
950
- let isTagReady = false;
951
- // Create integration logger
952
- const integrationLoggerLevel = getIntegrationLoggerLevel('pinterest-tag', debug);
953
- const logger = new AnalyticsLogger({
954
- level: integrationLoggerLevel,
955
- label: 'Saasco Pinterest Tag'
956
- });
957
- return {
958
- name: 'pinterest-tag',
959
- environments: ['client'],
960
- init: context => __awaiter(this, void 0, void 0, function* () {
961
- if (isServer$2) {
962
- logger.debug('Pinterest Tag init skipped on server');
963
- return;
964
- }
965
- logger.info('Initializing Pinterest Tag', {
966
- tagId: config.tagId
967
- });
968
- // Check if Pinterest Tag is already loaded
969
- if (window.pintrk || window._pintrk) {
970
- logger.warn('Pinterest Tag is already initialized');
971
- isTagReady = true;
972
- return;
973
- }
974
- // Initialize Pinterest Tag
975
- return new Promise((resolve, reject) => {
976
- try {
977
- // Pinterest Tag initialization script
978
- // Reference: https://help.pinterest.com/en/business/article/install-the-base-code
979
- (function (e) {
980
- if (!window.pintrk) {
981
- window.pintrk = function () {
982
- window.pintrk.queue.push(Array.prototype.slice.call(arguments));
983
- };
984
- const n = window.pintrk;
985
- n.queue = [];
986
- n.version = '3.0';
987
- const t = document.createElement('script');
988
- t.async = true;
989
- t.src = e;
990
- // Add load event listener
991
- t.onload = () => {
992
- // base script is loaded; now (re)load tag + fire page via runtime
993
- window.pintrk('load', config.tagId);
994
- // initialize the tag
995
- window.pintrk('page');
996
- // delay isTagReady to ensure the tag is loaded before sending events
997
- setTimeout(() => {
998
- logger.debug('Pinterest Tag initialized');
999
- isTagReady = true;
1000
- resolve();
1001
- }, 500);
1002
- };
1003
- t.onerror = () => {
1004
- logger.error('Failed to load Pinterest Tag script');
1005
- reject(new Error('Failed to load Pinterest Tag script'));
1006
- };
1007
- const r = document.getElementsByTagName('script')[0];
1008
- r.parentNode.insertBefore(t, r);
1009
- }
1010
- })('https://s.pinimg.com/ct/core.js');
1011
- } catch (error) {
1012
- logger.error('Failed to initialize Pinterest Tag', error);
1013
- reject(error);
1014
- }
1015
- });
1016
- }),
1017
- track: (eventName, properties) => {
1018
- if (!isTagReady || !window.pintrk) return;
1019
- const pinterestEventName = getPinterestEventName(eventName, config.eventMapping);
1020
- const eventData = buildEventData(properties);
1021
- logger.debug('Tracking event', {
1022
- eventName,
1023
- pinterestEventName,
1024
- eventData
1025
- });
1026
- // Track the event
1027
- window.pintrk('track', pinterestEventName, eventData);
1028
- },
1029
- identify: (userId, properties, context) => {
1030
- // Identify not supported with pinterest tag
1031
- logger.debug('Identify not supported with pinterest tag', {
1032
- userId,
1033
- properties,
1034
- context
1035
- });
1036
- window.pintrk('set', {
1037
- external_id: userId
1038
- });
1039
- window.pintrk('set', {
1040
- em: properties === null || properties === void 0 ? void 0 : properties['email']
1041
- });
1042
- return;
1043
- }
1044
- };
1045
- }
1046
- function getPinterestEventName(eventName, eventMapping) {
1047
- // Convert default saasco Page View event by default
1048
- if (eventName === 'Page View') return 'pagevisit';
1049
- if (!eventMapping) return eventName.toLowerCase();
1050
- return eventMapping[eventName] || eventName.toLowerCase();
1051
- }
1052
- /**
1053
- * Build event data for Pinterest tracking
1054
- * Reference: https://www.pinterest.com/_/_/help/business/article/event-code
1055
- */
1056
- function buildEventData(properties) {
1057
- if (!properties) return {};
1058
- const eventData = {};
1059
- // Map common properties to Pinterest event data
1060
- if (properties['event_id']) eventData['event_id'] = properties['event_id'];
1061
- if (properties['value']) eventData['value'] = properties['value'];
1062
- if (properties['currency']) eventData['currency'] = properties['currency'];
1063
- if (properties['order_quantity']) eventData['order_quantity'] = properties['order_quantity'];
1064
- if (properties['order_id']) eventData['order_id'] = properties['order_id'];
1065
- if (properties['promo_code']) eventData['promo_code'] = properties['promo_code'];
1066
- if (properties['property']) eventData['property'] = properties['property'];
1067
- if (properties['search_query']) eventData['search_query'] = properties['search_query'];
1068
- if (properties['video_title']) eventData['video_title'] = properties['video_title'];
1069
- if (properties['lead_type']) eventData['lead_type'] = properties['lead_type'];
1070
- if (properties['product_category']) eventData['product_category'] = properties['product_category'];
1071
- // Handle line items for e-commerce tracking
1072
- if (properties['line_items'] && Array.isArray(properties['line_items'])) {
1073
- eventData['line_items'] = properties['line_items'];
1074
- }
1075
- // Generate event_id if not provided
1076
- if (!eventData['event_id']) {
1077
- eventData['event_id'] = uuid();
1078
- }
1079
- return eventData;
1080
- }
1081
-
1082
- /**
1083
- * TikTok Pixel Integration
1084
- *
1085
- * This integration provides TikTok Pixel tracking capabilities including:
1086
- * - Event tracking for standard and custom events
1087
- * - Advanced matching with hashed user identification data
1088
- * - Automatic script loading and initialization
1089
- *
1090
- * Key Resources:
1091
- * - Advanced Matching: https://business-api.tiktok.com/portal/docs?rid=5ipocbxyw8v&id=1739585700402178
1092
- * - Standard Events: https://business-api.tiktok.com/portal/docs?id=1771101186666498
1093
- */
1094
- const isBrowser$1 = typeof window !== 'undefined';
1095
- const isServer$1 = !isBrowser$1;
1096
- /**
1097
- * Standard TikTok events supported by the pixel
1098
- *
1099
- * Note: This list reflects TikTok's official standard events. Events like 'CompletePayment'
1100
- * are not standard events and should be tracked as custom events if needed.
1101
- *
1102
- * Documentation: https://business-api.tiktok.com/portal/docs?id=1771101186666498
1103
- */
1104
- const standardTikTokEvents = ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'ApplicationApproval', 'CompleteRegistration', 'Contact', 'CustomizeProduct', 'Download', 'FindLocation', 'InitiateCheckout', 'Lead', 'Purchase', 'Schedule', 'Search', 'StartTrial', 'SubmitApplication', 'Subscribe', 'ViewContent', 'PageView'];
1105
- /**
1106
- * Create a TikTok Pixel integration instance
1107
- */
1108
- function createTikTokPixelIntegration(config, debug) {
1109
- let isPixelReady = false;
1110
- // Create integration logger
1111
- const integrationLoggerLevel = getIntegrationLoggerLevel('tiktok-pixel', debug);
1112
- const logger = new AnalyticsLogger({
1113
- level: integrationLoggerLevel,
1114
- label: 'Saasco tiktok-pixel'
1115
- });
1116
- return {
1117
- name: 'tiktok-pixel',
1118
- environments: ['client'],
1119
- init: () => __awaiter(this, void 0, void 0, function* () {
1120
- if (isServer$1) {
1121
- logger.debug('TikTok Pixel init skipped on server');
1122
- return;
1123
- }
1124
- logger.info('Initializing TikTok Pixel', {
1125
- pixelId: config.pixelId
1126
- });
1127
- // Check if TikTok Pixel is already loaded
1128
- if (window.ttq) {
1129
- logger.warn('TikTok Pixel is already initialized');
1130
- isPixelReady = true;
1131
- return;
1132
- }
1133
- // Initialize TikTok Pixel
1134
- return new Promise((resolve, reject) => {
1135
- try {
1136
- (function (w, d, t) {
1137
- w.TiktokAnalyticsObject = t;
1138
- const ttq = w[t] = w[t] || [];
1139
- ttq.methods = ['page', 'track', 'identify', 'instances', 'debug', 'on', 'off', 'once', 'ready', 'alias', 'group', 'enableCookie', 'disableCookie', 'holdConsent', 'revokeConsent', 'grantConsent'];
1140
- ttq.setAndDefer = function (t, e) {
1141
- t[e] = function () {
1142
- t.push([e].concat(Array.prototype.slice.call(arguments, 0)));
1143
- };
1144
- };
1145
- for (let i = 0; i < ttq.methods.length; i++) ttq.setAndDefer(ttq, ttq.methods[i]);
1146
- ttq.instance = function (t) {
1147
- const e = ttq._i[t] || [];
1148
- for (let n = 0; n < ttq.methods.length; n++) ttq.setAndDefer(e, ttq.methods[n]);
1149
- return e;
1150
- };
1151
- ttq.load = function (e, n) {
1152
- const r = 'https://analytics.tiktok.com/i18n/pixel/events.js';
1153
- const o = n && n.partner;
1154
- ttq._i = ttq._i || {};
1155
- ttq._i[e] = [];
1156
- ttq._i[e]._u = r;
1157
- ttq._t = ttq._t || {};
1158
- ttq._t[e] = +new Date();
1159
- ttq._o = ttq._o || {};
1160
- ttq._o[e] = n || {};
1161
- const script = document.createElement('script');
1162
- script.type = 'text/javascript';
1163
- script.async = true;
1164
- script.src = r + '?sdkid=' + e + '&lib=' + t;
1165
- // Add load event listener
1166
- script.onload = () => {
1167
- isPixelReady = true;
1168
- resolve();
1169
- };
1170
- script.onerror = () => {
1171
- reject(new Error('Failed to load TikTok Pixel script'));
1172
- };
1173
- const firstScript = document.getElementsByTagName('script')[0];
1174
- firstScript.parentNode.insertBefore(script, firstScript);
1175
- };
1176
- // Load the pixel with the provided ID
1177
- ttq.load(config.pixelId, config.testMode ? {
1178
- test_mode: true
1179
- } : undefined);
1180
- // If script loads synchronously, mark as ready
1181
- if (window.ttq && typeof window.ttq === 'function') {
1182
- isPixelReady = true;
1183
- resolve();
1184
- }
1185
- })(window, document, 'ttq');
1186
- } catch (error) {
1187
- reject(error);
1188
- }
1189
- });
1190
- }),
1191
- track: (eventName, properties, context) => {
1192
- if (!isPixelReady || !window.ttq) return;
1193
- const ttEventName = getTikTokEventName(eventName, config.eventMapping);
1194
- // Handle PageView separately as it uses ttq.page()
1195
- if (ttEventName === 'PageView') {
1196
- window.ttq.page();
1197
- return;
1198
- }
1199
- // Map parameters based on specific event requirements
1200
- const ttParams = mapTikTokEventParameters(ttEventName, properties);
1201
- if (ttParams && Object.keys(ttParams).length > 0) {
1202
- window.ttq.track(ttEventName, ttParams);
1203
- } else {
1204
- window.ttq.track(ttEventName);
1205
- }
1206
- },
1207
- identify: (userId, properties, context) => {
1208
- try {
1209
- if (typeof window !== 'undefined' && window.ttq) {
1210
- const identifyData = coerceTikTokIdentifyValues(Object.assign(Object.assign({}, properties || {}), userId ? {
1211
- external_id: userId
1212
- } : {}));
1213
- if (Object.keys(identifyData).length > 0) {
1214
- window.ttq.identify(identifyData);
1215
- }
1216
- }
1217
- } catch (error) {
1218
- console.error('Error identifying user in TikTok Pixel:', error);
1219
- }
1220
- }
1221
- };
1222
- }
1223
- function getTikTokEventName(eventName, eventMapping) {
1224
- // Convert default saasco Page View event by default
1225
- if (eventName === 'Page View') return 'PageView';
1226
- if (!eventMapping) return eventName;
1227
- return eventMapping[eventName] || eventName;
1228
- }
1229
- /**
1230
- * Get identification values from properties for TikTok Pixel
1231
- *
1232
- * Advanced Matching Documentation:
1233
- * https://business-api.tiktok.com/portal/docs?rid=5ipocbxyw8v&id=1739585700402178
1234
- *
1235
- * Standard Events Documentation:
1236
- * https://business-api.tiktok.com/portal/docs?id=1771101186666498
1237
- *
1238
- * @param properties
1239
- * @returns
1240
- */
1241
- function coerceTikTokIdentifyValues(properties) {
1242
- const coerce = [{
1243
- key: 'email',
1244
- coerceFrom: ['email', 'user_email', 'userEmail', 'email_address', 'emailAddress', 'e_mail', 'E_Mail', 'mail', 'Mail', 'contact_email', 'contactEmail', 'primary_email', 'primaryEmail'],
1245
- transform: value => {
1246
- if (typeof value !== 'string') return undefined;
1247
- const normalized = value.trim().toLowerCase();
1248
- // TikTok requires hashed email
1249
- return normalized ? sha256(normalized) : undefined;
1250
- }
1251
- }, {
1252
- key: 'phone_number',
1253
- coerceFrom: ['phone', 'phone_number', 'phoneNumber', 'mobile', 'mobile_number', 'mobileNumber', 'contact_number', 'contactNumber', 'tel', 'telephone'],
1254
- transform: value => {
1255
- if (typeof value !== 'string' && typeof value !== 'number') return undefined;
1256
- const phoneStr = String(value).replace(/\D/g, '');
1257
- // TikTok requires hashed phone number
1258
- return phoneStr ? sha256(phoneStr) : undefined;
1259
- }
1260
- }, {
1261
- key: 'external_id',
1262
- coerceFrom: ['external_id', 'externalId', 'user_id', 'userId', 'id', 'distinctId', 'distinct_id', 'customer_id', 'customerId', 'member_id', 'memberId'],
1263
- transform: value => {
1264
- if (value == null) return undefined;
1265
- const stringValue = String(value);
1266
- // TikTok requires hashed external_id
1267
- return stringValue ? sha256(stringValue) : undefined;
1268
- }
1269
- }];
1270
- const result = {};
1271
- for (const {
1272
- key,
1273
- coerceFrom,
1274
- transform
1275
- } of coerce) {
1276
- const foundValue = findFirstProperty(properties, coerceFrom);
1277
- if (foundValue !== null && transform) {
1278
- const transformedValue = transform(foundValue);
1279
- if (transformedValue) {
1280
- result[key] = transformedValue;
1281
- }
1282
- } else if (foundValue !== null && typeof foundValue === 'string') {
1283
- result[key] = foundValue;
1284
- }
1285
- }
1286
- return result;
1287
- }
1288
- function findFirstProperty(properties, keys) {
1289
- for (const key of keys) {
1290
- if (properties[key] !== undefined) {
1291
- return properties[key];
1292
- }
1293
- }
1294
- return null;
1295
- }
1296
- /**
1297
- * Maps event properties to TikTok-specific parameters based on event type
1298
- *
1299
- * Parameter Documentation: https://ads.tiktok.com/help/article/about-parameters
1300
- * Standard Events: https://business-api.tiktok.com/portal/docs?id=1771101186666498
1301
- */
1302
- function mapTikTokEventParameters(eventName, properties) {
1303
- if (!properties) return null;
1304
- const params = {};
1305
- // Helper to safely add parameters, skipping objects/null/undefined
1306
- const addParam = (key, value) => {
1307
- if (value != null && typeof value !== 'object') {
1308
- params[key] = value;
1309
- }
1310
- };
1311
- switch (eventName) {
1312
- case 'AddToCart':
1313
- case 'Purchase':
1314
- case 'ViewContent':
1315
- addParam('content_type', properties['content_type']);
1316
- addParam('content_ids', properties['content_ids']);
1317
- addParam('quantity', properties['quantity']);
1318
- addParam('description', properties['description']);
1319
- addParam('currency', properties['currency']);
1320
- addParam('value', properties['value']);
1321
- addParam('content_name', properties['content_name']);
1322
- addParam('content_category', properties['content_category']);
1323
- break;
1324
- case 'StartTrial':
1325
- addParam('content_ids', properties['content_ids']);
1326
- addParam('currency', properties['currency']);
1327
- addParam('value', properties['value']);
1328
- break;
1329
- case 'Search':
1330
- const searchString = properties['search_string'] || properties['query'] || properties['search_query'];
1331
- addParam('search_string', searchString);
1332
- break;
1333
- case 'InitiateCheckout':
1334
- case 'AddPaymentInfo':
1335
- addParam('value', properties['value']);
1336
- addParam('currency', properties['currency']);
1337
- addParam('content_ids', properties['content_ids']);
1338
- addParam('quantity', properties['quantity']);
1339
- break;
1340
- case 'CompleteRegistration':
1341
- case 'Subscribe':
1342
- addParam('value', properties['value']);
1343
- addParam('currency', properties['currency']);
1344
- break;
1345
- default:
1346
- // Pass through common parameters for any other events
1347
- addParam('value', properties['value']);
1348
- addParam('currency', properties['currency']);
1349
- break;
1350
- }
1351
- return Object.keys(params).length > 0 ? params : null;
1352
- }
1353
-
1354
- /*
1355
- Minimal analytics integration manager (v0)
1356
- */
1357
- class IntegrationManager {
1358
- constructor(config = {}) {
1359
- var _a, _b, _c, _d;
1360
- this.context = {};
1361
- this.integrations = new Map();
1362
- this.globalQueue = [];
1363
- this.config = {
1364
- loggerLevel: (_a = config.loggerLevel) !== null && _a !== void 0 ? _a : LogLevel.ERROR,
1365
- maxQueueSize: (_b = config.maxQueueSize) !== null && _b !== void 0 ? _b : 200,
1366
- maxIntegrationWaitTime: (_c = config.maxIntegrationWaitTime) !== null && _c !== void 0 ? _c : 10000,
1367
- flushInterval: (_d = config.flushInterval) !== null && _d !== void 0 ? _d : 5000
1368
- };
1369
- this.logger = new AnalyticsLogger({
1370
- label: 'Saasco Integrations Debug',
1371
- level: this.config.loggerLevel
1372
- });
1373
- this.initTime = Date.now();
1374
- this.currentEnvironment = typeof window !== 'undefined' ? 'client' : 'server';
1375
- this.setupPeriodicFlushing();
1376
- this.setupUnloadHandler();
1377
- }
1378
- /**
1379
- * Shallow-merge context to keep it simple + predictable in v0
1380
- */
1381
- setContext(next) {
1382
- this.context = Object.assign(Object.assign({}, this.context), next);
1383
- // Only log if we are setting something
1384
- if (Object.keys(this.context).length > 0) this.logger.info('setContext', this.context);
1385
- }
1386
- /**
1387
- * Register and init an integration. When init resolves, we mark it ready and
1388
- * immediately flush any queued events in FIFO order to *all* ready integrations.
1389
- */
1390
- registerIntegration(integration) {
1391
- return __awaiter(this, void 0, void 0, function* () {
1392
- this.logger.debug('Register integrations', this.integrations);
1393
- if (this.integrations.has(integration.name)) {
1394
- this.logger.warn(`Integration already registered: ${integration.name}`);
1395
- return;
1396
- }
1397
- // Check environment compatibility
1398
- const isCompatible = integration.environments.includes(this.currentEnvironment);
1399
- if (!isCompatible) {
1400
- this.logger.warn(`Skipping ${integration.name}: requires ${integration.environments.join(' or ')} but running in ${this.currentEnvironment}`);
1401
- return;
1402
- }
1403
- this.logger.debug(`Registering ${integration.name}`);
1404
- const state = {
1405
- integration,
1406
- status: 'idle'
1407
- };
1408
- this.integrations.set(integration.name, state);
1409
- if (integration.init) {
1410
- try {
1411
- state.status = 'loading';
1412
- const startTime = Date.now();
1413
- this.logger.debug(`Initializing ${integration.name}`);
1414
- yield integration.init(this.context);
1415
- state.status = 'ready';
1416
- const endTime = Date.now();
1417
- const duration = endTime - startTime;
1418
- this.logger.debug(`Integration ${integration.name} ready in ${duration}ms`);
1419
- this.flush();
1420
- } catch (e) {
1421
- state.status = 'error';
1422
- this.logger.error(`Failed to init ${integration.name}`, e);
1423
- }
1424
- } else {
1425
- state.status = 'ready';
1426
- this.logger.info(`Integration ${integration.name} ready (no init)`);
1427
- this.flush();
1428
- }
1429
- });
1430
- }
1431
- identify(userId, traits) {
1432
- // update context so subsequent track() carries new userId
1433
- this.setContext({
1434
- userId
1435
- });
1436
- this.send({
1437
- type: 'identify',
1438
- name: undefined,
1439
- properties: traits
1440
- });
1441
- }
1442
- track(name, properties) {
1443
- this.send({
1444
- type: 'track',
1445
- name,
1446
- properties
1447
- });
1448
- }
1449
- /**
1450
- * Core send path: if at least one integration is ready -> deliver immediately
1451
- * Else enqueue (bounded FIFO)
1452
- */
1453
- send(partial) {
1454
- const envelope = {
1455
- id: uuid(),
1456
- type: partial.type,
1457
- timestamp: Date.now(),
1458
- name: partial.name,
1459
- properties: partial.properties,
1460
- context: Object.assign({}, this.context)
1461
- };
1462
- // If we're not ready, enqueue
1463
- if (!this.isReady()) {
1464
- if (this.globalQueue.length >= this.config.maxQueueSize) {
1465
- // drop oldest
1466
- this.globalQueue.shift();
1467
- this.logger.warn('Queue full → dropped oldest');
1468
- }
1469
- this.globalQueue.push(envelope);
1470
- this.logger.debug(`Queued (${this.globalQueue.length})`, envelope);
1471
- return;
1472
- }
1473
- this.deliver(envelope);
1474
- }
1475
- deliver(evt) {
1476
- return __awaiter(this, void 0, void 0, function* () {
1477
- for (const {
1478
- integration,
1479
- status
1480
- } of this.integrations.values()) {
1481
- if (status !== 'ready') continue;
1482
- try {
1483
- if (evt.type === 'track' && integration.track && evt.name) {
1484
- this.logger.debug(`Track (${integration.name}) ${evt.name}`, {
1485
- properties: evt.properties,
1486
- context: evt.context
1487
- });
1488
- yield integration.track(evt.name, evt.properties, evt.context);
1489
- } else if (evt.type === 'identify' && integration.identify) {
1490
- this.logger.debug(`Identify (${integration.name})`, {
1491
- userId: evt.context.distinctId,
1492
- properties: evt.properties,
1493
- context: evt.context
1494
- });
1495
- yield integration.identify(evt.context.distinctId, evt.properties, evt.context);
1496
- }
1497
- } catch (e) {
1498
- this.logger.error(`deliver error in ${integration.name} (${evt.type})`, e);
1499
- }
1500
- }
1501
- });
1502
- }
1503
- /**
1504
- * Flush queued events FIFO once at least one integration is ready.
1505
- */
1506
- flush() {
1507
- // nothing to flush
1508
- if (this.globalQueue.length === 0) return;
1509
- // If we're not ready don't flush
1510
- if (!this.isReady()) return;
1511
- this.logger.debug(`flushing ${this.globalQueue.length} queued events`);
1512
- const toSend = this.globalQueue;
1513
- this.globalQueue = [];
1514
- // deliver all events
1515
- toSend.forEach(evt => this.deliver(evt));
1516
- }
1517
- readyCount() {
1518
- let n = 0;
1519
- for (const s of this.integrations.values()) if (s.status === 'ready') n++;
1520
- return n;
1521
- }
1522
- isReady() {
1523
- const timeElapsed = Date.now() - this.initTime;
1524
- // Return if integrations are not ready, or if we haven't waited the max integration wait time
1525
- if (this.readyCount() < this.integrations.size && timeElapsed < this.config.maxIntegrationWaitTime) {
1526
- return false;
1527
- }
1528
- if (this.readyCount() < this.integrations.size) {
1529
- this.logger.info(`proceeding with ${this.readyCount()}/${this.integrations.size} integrations ready after ${timeElapsed}ms wait`);
1530
- }
1531
- return true;
1532
- }
1533
- /**
1534
- * Setup periodic flushing if enabled
1535
- */
1536
- setupPeriodicFlushing() {
1537
- if (this.config.flushInterval <= 0) return;
1538
- this.flushTimer = setInterval(() => {
1539
- if (this.globalQueue.length > 0) {
1540
- this.flush();
1541
- }
1542
- }, this.config.flushInterval);
1543
- }
1544
- /**
1545
- * Setup page unload handler for client environment
1546
- */
1547
- setupUnloadHandler() {
1548
- if (this.currentEnvironment !== 'client' || typeof window === 'undefined') {
1549
- return;
1550
- }
1551
- this.unloadHandler = () => {
1552
- this.logger.info('page unloading, flushing remaining events');
1553
- this.flush();
1554
- };
1555
- // Use both beforeunload and pagehide for better coverage
1556
- window.addEventListener('beforeunload', this.unloadHandler);
1557
- window.addEventListener('pagehide', this.unloadHandler);
1558
- }
1559
- /** Debug helpers */
1560
- getStats() {
1561
- return {
1562
- currentEnvironment: this.currentEnvironment,
1563
- integrations: [...this.integrations.values()].map(s => ({
1564
- name: s.integration.name,
1565
- status: s.status,
1566
- environments: s.integration.environments
1567
- })),
1568
- queueLength: this.globalQueue.length,
1569
- readyCount: this.readyCount(),
1570
- periodicFlushEnabled: this.config.flushInterval > 0,
1571
- flushInterval: this.config.flushInterval,
1572
- context: this.context
1573
- };
1574
- }
1575
- }
1576
-
1577
- const isBrowser = typeof window !== 'undefined';
1578
- const isServer = !isBrowser;
1579
- const PREF = 'saasco-sdk';
1580
- const SESSION_DURATION = 1000 * 60 * 30;
1581
- const USER_DURATION = 1000 * 60 * 60 * 24 * 365;
1582
- const data = {};
1583
- function getRootDomain(url) {
1584
- if (isServer) return;
1585
- if (!url) return;
1586
- try {
1587
- const hostname = new URL(url).hostname;
1588
- if (!isValid(hostname)) return;
1589
- const parsed = parse(hostname);
1590
- if ('domain' in parsed && parsed.domain) return parsed.domain;
1591
- } catch (error) {
1592
- // invalid url
1593
- }
1594
- return;
1595
- }
1596
- function setCookie(name, value, ttl) {
1597
- if (isServer) return;
1598
- const expires = new Date(Date.now() + ttl).toUTCString();
1599
- const domain = getRootDomain(window.location.href);
1600
- const cookieString = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${domain ? `; domain=.${domain}` : ''}`;
1601
- document.cookie = cookieString;
1602
- }
1603
- function getCookie(name) {
1604
- if (isServer) return null;
1605
- const nameEQ = name + '=';
1606
- const ca = document.cookie.split(';');
1607
- for (let i = 0; i < ca.length; i++) {
1608
- let c = ca[i];
1609
- while (c.charAt(0) === ' ') c = c.substring(1, c.length);
1610
- if (c.indexOf(nameEQ) === 0) {
1611
- return decodeURIComponent(c.substring(nameEQ.length, c.length));
1612
- }
1613
- }
1614
- return null;
1615
- }
1616
- function deleteCookie(name) {
1617
- if (isServer) return;
1618
- const domain = getRootDomain(window.location.href);
1619
- const cookieString = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/${domain ? `; domain=.${domain}` : ''}`;
1620
- document.cookie = cookieString;
1621
- }
1622
- function migrateFromLocalStorage() {
1623
- if (isServer) return;
1624
- const keysToMigrate = [`${PREF}-session-id`, `${PREF}-anonymous-id`, `${PREF}-user-id`, `${PREF}-session-utm-context`, `${PREF}-super-context`];
1625
- keysToMigrate.forEach(key => {
1626
- try {
1627
- const value = localStorage.getItem(key);
1628
- if (value) {
1629
- // Parse the stored data to get the original TTL
1630
- const item = JSON.parse(value);
1631
- const now = new Date().getTime();
1632
- // Check if the data is still valid
1633
- if (item.expiry > now) {
1634
- // Calculate remaining TTL
1635
- const remainingTtl = item.expiry - now;
1636
- // Store in cookie with remaining TTL
1637
- setCookie(key, value, remainingTtl);
1638
- // Remove from localStorage after successful migration
1639
- localStorage.removeItem(key);
1640
- } else {
1641
- // Data has expired, just remove it
1642
- localStorage.removeItem(key);
1643
- }
1644
- }
1645
- } catch (error) {
1646
- // If there's an error parsing the data, remove the corrupted item
1647
- localStorage.removeItem(key);
1648
- }
1649
- });
1650
- }
1651
- function storeData(key, value, ttl) {
1652
- if (isServer) {
1653
- const item = {
1654
- value,
1655
- expiry: new Date().getTime() + ttl
1656
- };
1657
- if (value === undefined) {
1658
- delete data[key];
1659
- return;
1660
- }
1661
- data[key] = item;
1662
- return;
1663
- }
1664
- const fullKey = `${PREF}-${key}`;
1665
- if (value === undefined) {
1666
- deleteCookie(fullKey);
1667
- return;
1668
- }
1669
- const item = {
1670
- value,
1671
- expiry: new Date().getTime() + ttl
1672
- };
1673
- setCookie(fullKey, JSON.stringify(item), ttl);
1674
- }
1675
- function retrieveData(key) {
1676
- if (isServer) {
1677
- const item = data[key];
1678
- if (!item) return null;
1679
- if (Date.now() > item.expiry) {
1680
- delete data[key];
1681
- return null;
1682
- }
1683
- return item.value;
1684
- }
1685
- const fullKey = `${PREF}-${key}`;
1686
- const itemStr = getCookie(fullKey);
1687
- if (!itemStr) {
1688
- return null;
1689
- }
1690
- try {
1691
- const item = JSON.parse(itemStr);
1692
- if (Date.now() > item.expiry) {
1693
- deleteCookie(fullKey);
1694
- return null;
1695
- }
1696
- return item.value;
1697
- } catch (error) {
1698
- // If JSON parsing fails, remove the corrupted cookie
1699
- deleteCookie(fullKey);
1700
- return null;
1701
- }
1702
- }
1703
- function getSessionId() {
1704
- return retrieveData(`session-id`);
1705
- }
1706
- function setSessionId({
1707
- reset
1708
- } = {
1709
- reset: false
1710
- }) {
1711
- const sessionId = reset ? uuid() : getSessionId() || uuid();
1712
- storeData(`session-id`, sessionId, SESSION_DURATION);
1713
- return sessionId;
1714
- }
1715
- function getAnonymousId() {
1716
- return retrieveData(`anonymous-id`);
1717
- }
1718
- function setAnonymousId({
1719
- reset
1720
- } = {
1721
- reset: false
1722
- }) {
1723
- const anonymousId = reset ? uuid() : getAnonymousId() || uuid();
1724
- storeData(`anonymous-id`, anonymousId, USER_DURATION);
1725
- return anonymousId;
1726
- }
1727
- function getUserId() {
1728
- return retrieveData(`user-id`);
1729
- }
1730
- function setUserId(userId) {
1731
- storeData(`user-id`, userId, USER_DURATION);
1732
- }
1733
- function getSuperContext() {
1734
- return retrieveData('super-context') || {};
1735
- }
1736
- function setSuperContext(superContext) {
1737
- storeData('super-context', superContext, SESSION_DURATION);
1738
- }
1739
- function updateSuperContext(browserContext) {
1740
- const existingSuperContext = getSuperContext();
1741
- const defaultSuperContextKeys = ['$utmSource', '$utmMedium', '$utmCampaign', '$utmTerm', '$utmContent', '$utmId', '$utmSourcePlatform', '$utmCampaignId', '$utmCreativeFormat', '$utmMarketingTactic', '$utmAdSource', '$utmAdId'];
1742
- const superContext = defaultSuperContextKeys.reduce((acc, key) => {
1743
- const value = Object.assign({}, browserContext)[key];
1744
- if (value) acc[key] = value;
1745
- return acc;
1746
- }, existingSuperContext);
1747
- // Only set referrer if it's from a different root domain
1748
- // Prevents setting referrer when going between subdomains of the same root domain
1749
- if (browserContext.$referrer) {
1750
- const referrerRootDomain = getRootDomain(browserContext.$referrer);
1751
- const currentRootDomain = getRootDomain(browserContext.$href);
1752
- if (referrerRootDomain && currentRootDomain && referrerRootDomain !== currentRootDomain) {
1753
- superContext['$referrer'] = browserContext.$referrer;
1754
- }
1755
- }
1756
- setSuperContext(superContext);
1757
- return superContext;
1758
- }
1759
- class Saasco {
1760
- /**
1761
- * Creates an instance of Saasco SDK.
1762
- * @param config Configuration options.
1763
- * @param config.projectId The unique identifier for the project.
1764
- * @param config.proxy The URL of the proxy server to use, if any.
1765
- * @param config.autoPageTracking Whether to automatically track page views. Default is false.
1766
- * @param config.enabled Whether analytics is enabled. Default is true. Set to false for development and staging envioronments. Will still allow debug mode to be true, just no events will be sent
1767
- * @param config.debug Whether to log debug information. Default is false.
1768
- * @param config.trackUrlParams Whether to track URL parameters. Default is true.
1769
- * @param config.trackHashChanges Whether to track hash changes. Default is true.
1770
- * @param config.integrations Configuration for third-party integrations like Facebook Pixel.
1771
- */
1772
- constructor(config) {
1773
- this.config = config;
1774
- this.lastPageViewPath = '';
1775
- this.isInitialized = false;
1776
- const loggerLevel = getLoggerLevel(this.config);
1777
- // Initialize logger
1778
- this.logger = new AnalyticsLogger({
1779
- level: loggerLevel,
1780
- label: 'Saasco Debug'
1781
- });
1782
- this.logger.info('Saasco started');
1783
- if (!config.projectId) {
1784
- this.logger.error("Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
1785
- return;
1786
- }
1787
- // default enabled to true
1788
- this.config.enabled = this.config.enabled === undefined ? true : this.config.enabled;
1789
- // Set default auto page tracking config
1790
- this.config.autoPageTracking = Object.assign({
1791
- enabled: true,
1792
- trackQueryParams: true,
1793
- trackHash: true
1794
- }, this.config.autoPageTracking);
1795
- // Initialize integration manager
1796
- this.integrationManager = new IntegrationManager({
1797
- loggerLevel,
1798
- maxQueueSize: 1000
1799
- });
1800
- // Set initial context
1801
- this.integrationManager.setContext({});
1802
- // Initialize integrations immediately (works on both client and server)
1803
- this.initIntegrations().catch(error => {
1804
- this.logger.info('Error initializing integrations:', error);
1805
- });
1806
- }
1807
- init() {
1808
- if (this.isInitialized) {
1809
- this.logger.info('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
1810
- return;
1811
- }
1812
- // Export Saasco to the window object for easy access and debugging
1813
- if (isBrowser) {
1814
- window.saasco = this;
1815
- }
1816
- // Migrate existing localStorage data to cookies for cross-subdomain support
1817
- migrateFromLocalStorage();
1818
- this.logger.info('Saasco initialized', this.config);
1819
- if (this.config.debug) this.logger.info('Debug mode active. This will log all events to the console.');
1820
- if (!this.config.enabled) this.logger.info(`Analytics is disabled. No requests will be sent to the server and no integrations will be initialized. ${this.config.debug ? 'Debug mode is active and will still log information.' : ''}`);
1821
- this.initAutoPageTracking();
1822
- this.isInitialized = true;
1823
- }
1824
- disableDebug() {
1825
- this.logger.info('Debug mode deactivated.');
1826
- this.config.debug = false;
1827
- }
1828
- enableDebug() {
1829
- this.config.debug = true;
1830
- this.logger.info('Debug mode activated.');
1831
- }
1832
- /**
1833
- * Initialize third-party integrations
1834
- */
1835
- initIntegrations() {
1836
- return __awaiter(this, void 0, void 0, function* () {
1837
- this.logger.info('Initialize integrations');
1838
- if (!this.config.integrations || this.config.integrations.length === 0) return;
1839
- if (!this.config.enabled) {
1840
- this.logger.info('Analytics is disabled. Skipping integration initialization.');
1841
- return;
1842
- }
1843
- for (const integrationConfig of this.config.integrations) {
1844
- try {
1845
- if (integrationConfig.type === 'facebook-pixel') {
1846
- this.logger.info('Registering Facebook Pixel integration with ID:', integrationConfig.config.pixelId);
1847
- const fbIntegration = createFacebookPixelIntegration(integrationConfig.config, integrationConfig.debug);
1848
- yield this.integrationManager.registerIntegration(fbIntegration);
1849
- } else if (integrationConfig.type === 'pinterest-tag') {
1850
- this.logger.info('Registering Pinterest Tag integration with ID:', integrationConfig.config.tagId);
1851
- const pinterestIntegration = createPinterestTagIntegration(integrationConfig.config, integrationConfig.debug);
1852
- yield this.integrationManager.registerIntegration(pinterestIntegration);
1853
- } else if (integrationConfig.type === 'tiktok-pixel') {
1854
- this.logger.info('Registering TikTok Pixel integration with ID:', integrationConfig.config.pixelId);
1855
- const tiktokIntegration = createTikTokPixelIntegration(integrationConfig.config, integrationConfig.debug);
1856
- yield this.integrationManager.registerIntegration(tiktokIntegration);
1857
- }
1858
- } catch (error) {
1859
- this.logger.info(`Failed to register ${integrationConfig.type} integration:`, error);
1860
- }
1861
- }
1862
- });
1863
- }
1864
- track(actionOrPayload, propertiesOrNothing, contextOrNothing) {
1865
- if (!this.config.projectId) {
1866
- const response = this.logger.error("Unable to track event. Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
1867
- return Promise.resolve(response);
1868
- }
1869
- setSessionId();
1870
- setAnonymousId();
1871
- const hasAction = typeof actionOrPayload === 'string';
1872
- const hasPayload = typeof actionOrPayload === 'object';
1873
- // Must do payload on the server
1874
- if (isServer && hasAction) {
1875
- const response = this.logger.error('When calling track from the server you must pass a payload object with a userId in order to track events. For example: track({ event: "Song Played", userId: "user_123", properties: { song: "Song Name" } })');
1876
- return Promise.resolve(response);
1877
- }
1878
- // On server events we require a userId or an anonymousId
1879
- if (isServer && hasPayload && !actionOrPayload.userId && !actionOrPayload.anonymousId) {
1880
- const response = this.logger.error('When calling track from the server you must pass a payload object with either a userId or an anonymousId in order to track events. For example:\n\ntrack({ event: "Song Played", userId: "user_123", properties: { song: "Song Name" } }). \n\nIf providing an anonymousId this must be provided from the client side otherwise we will not have a user to connect the event to.');
1881
- return Promise.resolve(response);
1882
- }
1883
- const action = hasAction ? actionOrPayload : actionOrPayload.event;
1884
- const properties = hasAction ? propertiesOrNothing : actionOrPayload.properties;
1885
- const context = hasAction ? contextOrNothing : actionOrPayload.context;
1886
- const sessionId = (hasAction ? undefined : actionOrPayload.sessionId) || getSessionId();
1887
- const anonymousId = (hasAction ? undefined : actionOrPayload.anonymousId) || getAnonymousId();
1888
- const distinctId = (hasAction ? undefined : actionOrPayload.userId) || getUserId();
1889
- // Only gets browser and super context if in the browser
1890
- let browserContext;
1891
- let superContext;
1892
- if (isBrowser) {
1893
- browserContext = getBrowserContext();
1894
- superContext = updateSuperContext(browserContext);
1895
- }
1896
- const payloadJSON = Object.assign(Object.assign(Object.assign({}, browserContext), superContext), {
1897
- properties: properties || {}
1898
- });
1899
- const data = {
1900
- id: uuid(),
1901
- timestamp: new Date().toISOString(),
1902
- action,
1903
- version,
1904
- sessionId,
1905
- anonymousId,
1906
- distinctId,
1907
- projectId: this.config.projectId,
1908
- payload: JSON.stringify(payloadJSON),
1909
- source: isBrowser ? 'client' : 'server',
1910
- context: JSON.stringify(context || {})
1911
- };
1912
- if (data.action === 'Page View') {
1913
- this.logger.info('Page View', window.location.href, data);
1914
- } else {
1915
- this.logger.info('track', data);
1916
- }
1917
- if (!this.config.enabled) {
1918
- return Promise.resolve({
1919
- success: true,
1920
- message: 'Analytics is disabled'
1921
- });
1922
- }
1923
- // Only send track to integrations if analytics is enabled
1924
- this.integrationManager.track(action, properties);
1925
- // Send event to API
1926
- return this.doRequest('events', data);
1927
- }
1928
- /**
1929
- * The page method lets you record page views on your website
1930
- * This records the page title and path and names the event useing the reserved property "Page Viewed"
1931
- *
1932
- * Before implementing this make sure you have disabled the autoPageTracking in the config or you will get duplicate page views
1933
- */
1934
- page() {
1935
- var _a, _b;
1936
- if (isServer) {
1937
- console.warn('Saasco page tracking is only available in the browser');
1938
- return;
1939
- }
1940
- const url = new URL(window.location.href);
1941
- let pathToCompare = url.pathname;
1942
- // Include search params if configured
1943
- if ((_a = this.config.autoPageTracking) === null || _a === void 0 ? void 0 : _a.trackQueryParams) {
1944
- pathToCompare += url.search;
1945
- }
1946
- // Include hash if configured
1947
- if ((_b = this.config.autoPageTracking) === null || _b === void 0 ? void 0 : _b.trackHash) {
1948
- pathToCompare += url.hash;
1949
- }
1950
- // Only track if the path has changed
1951
- if (pathToCompare === this.lastPageViewPath) return;
1952
- this.lastPageViewPath = pathToCompare;
1953
- return this.track('Page View');
1954
- }
1955
- identify(distinctIdOrProperties, propertiesOrContext, contextOrNothing) {
1956
- if (!this.config.projectId) {
1957
- const response = this.logger.error("Unable to identify user. Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
1958
- return Promise.resolve(response);
1959
- }
1960
- const hasId = typeof distinctIdOrProperties === 'string' || typeof distinctIdOrProperties === 'number' || distinctIdOrProperties === null;
1961
- const distinctId = hasId ? distinctIdOrProperties === null || distinctIdOrProperties === void 0 ? void 0 : distinctIdOrProperties.toString() : `soft_${uuid()}`;
1962
- const properties = hasId ? propertiesOrContext : distinctIdOrProperties;
1963
- const context = hasId ? contextOrNothing : propertiesOrContext;
1964
- // When the user gets identified as null this will reset the users session and anonymous id
1965
- // This should only be called when distinctId is null and there is an existing userId.
1966
- // This means the user has logged out and we should reset the session and anonymous IDs
1967
- const userIdChangedToNull = distinctId === null && !!getUserId();
1968
- if (userIdChangedToNull) this.logger.info('User logged out');
1969
- const reset = userIdChangedToNull;
1970
- const sessionId = setSessionId({
1971
- reset
1972
- });
1973
- const anonymousId = setAnonymousId({
1974
- reset
1975
- });
1976
- // set the distinct Id to the userId
1977
- setUserId(distinctId);
1978
- // Update integration manager context and send identify (IntegrationManager will filter by environment)
1979
- this.integrationManager.setContext(Object.assign({
1980
- distinctId,
1981
- anonymousId,
1982
- sessionId
1983
- }, properties));
1984
- // No distinctId provided so we don't track the user
1985
- if (!distinctId) return Promise.resolve({
1986
- success: true,
1987
- message: 'No distinctId provided'
1988
- });
1989
- // Send identify to API
1990
- const data = {
1991
- id: uuid(),
1992
- timestamp: new Date().toISOString(),
1993
- projectId: this.config.projectId,
1994
- distinctId,
1995
- anonymousId,
1996
- version,
1997
- payload: properties || {},
1998
- context: context || {}
1999
- };
2000
- this.logger.info('identify', data);
2001
- // Only send identify to integrations if analytics is enabled
2002
- if (!this.config.enabled) {
2003
- return Promise.resolve({
2004
- success: true,
2005
- message: 'Analytics is disabled'
2006
- });
2007
- }
2008
- this.integrationManager.identify(distinctId, properties);
2009
- return this.doRequest('identify', data);
2010
- }
2011
- /**
2012
- * This should only be called when the user logs out
2013
- * It will reset the session, anonymous id, and user id
2014
- *
2015
- * All events after calling reset will be tracked as a new user
2016
- *
2017
- */
2018
- logout() {
2019
- // Clear super context data on logout
2020
- setSuperContext(null);
2021
- this.identify(null);
2022
- }
2023
- /**
2024
- * Handles sending data to the Saasco API
2025
- * If you have a proxy set up, it will send the data to the proxy and you can handle forawrding the data to the Saasco events API
2026
- * @param path
2027
- * @param data
2028
- */
2029
- doRequest(path, data) {
2030
- return __awaiter(this, void 0, void 0, function* () {
2031
- const base = this.config.proxy || 'https://www.saasco.com/api/';
2032
- const url = `${base}${path}`;
2033
- // If analytics is disabled, don't send the request
2034
- if (this.config.enabled === false) return {
2035
- success: false,
2036
- message: 'Analytics is disabled'
2037
- };
2038
- try {
2039
- const response = yield fetch(url, {
2040
- method: 'POST',
2041
- headers: {
2042
- 'Content-Type': 'application/json'
2043
- },
2044
- body: JSON.stringify(data)
2045
- });
2046
- const responseBody = yield response.json();
2047
- if (!response.ok) {
2048
- throw new Error(`${responseBody.message}: ${responseBody.errors ? `[${responseBody.errors.join(', ')}]` : ''}` || JSON.stringify(responseBody));
2049
- }
2050
- return {
2051
- success: true,
2052
- message: responseBody.message
2053
- };
2054
- } catch (error) {
2055
- this.logger.error(`\nError Message: "${error.message}"`, `\nPath: "/${path}"`, `\nRequest Data: ${JSON.stringify(data, null, 2)}`);
2056
- return {
2057
- success: false,
2058
- message: error.message
2059
- };
2060
- }
2061
- });
2062
- }
2063
- /**
2064
- * If autoPageTracking is enabled, this will automatically track page views
2065
- * It listens to url changes to track new pages every time the url changes
2066
- * @returns
2067
- */
2068
- initAutoPageTracking() {
2069
- var _a, _b;
2070
- // Disable auto page tracking if the config is set to false
2071
- if (!((_a = this.config.autoPageTracking) === null || _a === void 0 ? void 0 : _a.enabled)) return;
2072
- // Prevent running on the server
2073
- if (isServer) return console.warn('Saasco auto page tracking is only available in the browser');
2074
- // Prevent intitializing auto page tracking more than once
2075
- if (window.saascoAutoPageTrackingActive) {
2076
- this.logger.info('Auto Page Tracking already enabled');
2077
- return;
2078
- }
2079
- window.saascoAutoPageTrackingActive = true;
2080
- this.logger.info('Auto Page Tracking enabled');
2081
- // Track initial page load
2082
- this.page();
2083
- // Listen for hash changes if hash tracking is enabled
2084
- if ((_b = this.config.autoPageTracking) === null || _b === void 0 ? void 0 : _b.trackHash) {
2085
- window.addEventListener('hashchange', () => this.page());
2086
- }
2087
- // Listen to popstate for back and forward navigation
2088
- window.addEventListener('popstate', () => this.page());
2089
- // Wrap history push state to listen for URL changes
2090
- const historyPushState = history.pushState;
2091
- history.pushState = (...args) => {
2092
- const returnValue = historyPushState.apply(history, args);
2093
- this.page();
2094
- return returnValue;
2095
- };
2096
- // Wrap history replace state to listen for URL changes
2097
- const historyReplaceState = history.replaceState;
2098
- history.replaceState = (...args) => {
2099
- const returnValue = historyReplaceState.apply(history, args);
2100
- this.page();
2101
- return returnValue;
2102
- };
2103
- }
2104
- getIntegrationsStats() {
2105
- return this.integrationManager.getStats();
2106
- }
2107
- }
2108
- function getLoggerLevel(config) {
2109
- // Check URL parameters for debug overrides (browser only)
2110
- let urlDebug = false;
2111
- let urlDebugVerbose = false;
2112
- if (!isServer) {
2113
- try {
2114
- const urlParams = new URLSearchParams(window.location.search);
2115
- urlDebug = urlParams.get('saasco-debug') === 'true';
2116
- urlDebugVerbose = urlParams.get('saasco-debug-verbose') === 'true';
2117
- } catch (error) {
2118
- // Ignore URL parsing errors
2119
- }
2120
- }
2121
- // URL parameters override config
2122
- const effectiveDebug = urlDebug || config.debug;
2123
- const effectiveDebugVerbose = urlDebugVerbose || config.debugVerbose;
2124
- return !!effectiveDebugVerbose ? LogLevel.DEBUG : !!effectiveDebug ? LogLevel.INFO : LogLevel.ERROR;
2125
- }
2126
-
2127
- const browserContextSchema = z.object({
2128
- $locale: z.string(),
2129
- $location: z.string(),
2130
- $href: z.string(),
2131
- $pathname: z.string(),
2132
- $referrer: z.string(),
2133
- $screenDPI: z.number(),
2134
- $screenHeight: z.number(),
2135
- $screenWidth: z.number(),
2136
- $title: z.string(),
2137
- $userAgent: z.string(),
2138
- $utmSource: z.string().nullable(),
2139
- $utmMedium: z.string().nullable(),
2140
- $utmCampaign: z.string().nullable(),
2141
- $utmTerm: z.string().nullable(),
2142
- $utmContent: z.string().nullable(),
2143
- $utmId: z.string().nullable(),
2144
- $utmSourcePlatform: z.string().nullable(),
2145
- $utmCampaignId: z.string().nullable(),
2146
- $utmCreativeFormat: z.string().nullable(),
2147
- $utmMarketingTactic: z.string().nullable(),
2148
- $utmAdSource: z.string().nullable(),
2149
- $utmAdId: z.string().nullable()
2150
- });
2151
-
2152
- export { IntegrationManager, Saasco, browserContextSchema, createFacebookPixelIntegration, createPinterestTagIntegration, createTikTokPixelIntegration, getBrowserContext, standardFacebookEvents, standardPinterestEvents, standardTikTokEvents, timezones };