saasco-sdk 0.1.44 → 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,2185 +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.44";
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$2(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$2(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, context) => {
1018
- if (!isTagReady || !window.pintrk) return;
1019
- const pinterestEventName = getPinterestEventName(eventName, config.eventMapping);
1020
- const eventData = buildEventData(properties);
1021
- // Get enhanced matching data from properties
1022
- const enhancedMatchData = getPinterestEnhancedMatchData(context || {});
1023
- logger.debug('Tracking event', {
1024
- eventName,
1025
- pinterestEventName,
1026
- eventData,
1027
- enhancedMatchData
1028
- });
1029
- // Combine event data with enhanced matching data
1030
- const finalEventData = Object.assign(Object.assign({}, eventData), enhancedMatchData);
1031
- // Track the event
1032
- window.pintrk('track', pinterestEventName, finalEventData);
1033
- },
1034
- identify: (userId, properties, context) => {
1035
- // Identify not supported with pinterest tag
1036
- logger.debug('Identify not supported with pinterest tag', {
1037
- userId,
1038
- properties,
1039
- context
1040
- });
1041
- window.pintrk('set', {
1042
- external_id: userId
1043
- });
1044
- window.pintrk('set', {
1045
- em: properties === null || properties === void 0 ? void 0 : properties['email']
1046
- });
1047
- return;
1048
- }
1049
- };
1050
- }
1051
- function getPinterestEventName(eventName, eventMapping) {
1052
- // Convert default saasco Page View event by default
1053
- if (eventName === 'Page View') return 'pagevisit';
1054
- if (!eventMapping) return eventName.toLowerCase();
1055
- return eventMapping[eventName] || eventName.toLowerCase();
1056
- }
1057
- /**
1058
- * Build event data for Pinterest tracking
1059
- * Reference: https://www.pinterest.com/_/_/help/business/article/event-code
1060
- */
1061
- function buildEventData(properties) {
1062
- if (!properties) return {};
1063
- const eventData = {};
1064
- // Map common properties to Pinterest event data
1065
- if (properties['event_id']) eventData['event_id'] = properties['event_id'];
1066
- if (properties['value']) eventData['value'] = properties['value'];
1067
- if (properties['currency']) eventData['currency'] = properties['currency'];
1068
- if (properties['order_quantity']) eventData['order_quantity'] = properties['order_quantity'];
1069
- if (properties['order_id']) eventData['order_id'] = properties['order_id'];
1070
- if (properties['promo_code']) eventData['promo_code'] = properties['promo_code'];
1071
- if (properties['property']) eventData['property'] = properties['property'];
1072
- if (properties['search_query']) eventData['search_query'] = properties['search_query'];
1073
- if (properties['video_title']) eventData['video_title'] = properties['video_title'];
1074
- if (properties['lead_type']) eventData['lead_type'] = properties['lead_type'];
1075
- if (properties['product_category']) eventData['product_category'] = properties['product_category'];
1076
- // Handle line items for e-commerce tracking
1077
- if (properties['line_items'] && Array.isArray(properties['line_items'])) {
1078
- eventData['line_items'] = properties['line_items'];
1079
- }
1080
- // Generate event_id if not provided
1081
- if (!eventData['event_id']) {
1082
- eventData['event_id'] = uuid();
1083
- }
1084
- return eventData;
1085
- }
1086
- /**
1087
- * Get Pinterest Enhanced Matching values from properties
1088
- * Reference: https://help.pinterest.com/en/business/article/enhanced-match
1089
- */
1090
- function getPinterestEnhancedMatchData(properties) {
1091
- const enhancedMatch = {};
1092
- // Email
1093
- const email = findFirstProperty$1(properties, ['email', 'user_email', 'userEmail', 'email_address', 'emailAddress', 'e_mail', 'E_Mail', 'mail', 'Mail', 'contact_email', 'contactEmail', 'primary_email', 'primaryEmail']);
1094
- if (email && typeof email === 'string') {
1095
- // Pinterest expects hashed email for enhanced matching
1096
- const normalizedEmail = email.trim().toLowerCase();
1097
- enhancedMatch.em = normalizedEmail;
1098
- }
1099
- // External ID (User ID)
1100
- const externalId = findFirstProperty$1(properties, ['external_id', 'externalId', 'user_id', 'userId', 'id', 'distinctId', 'distinct_id', 'customer_id', 'customerId', 'member_id', 'memberId']);
1101
- if (externalId) {
1102
- enhancedMatch.external_id = String(externalId);
1103
- }
1104
- return enhancedMatch;
1105
- }
1106
- function findFirstProperty$1(properties, keys) {
1107
- for (const key of keys) {
1108
- if (properties[key] !== undefined) {
1109
- return properties[key];
1110
- }
1111
- }
1112
- return null;
1113
- }
1114
-
1115
- /**
1116
- * TikTok Pixel Integration
1117
- *
1118
- * This integration provides TikTok Pixel tracking capabilities including:
1119
- * - Event tracking for standard and custom events
1120
- * - Advanced matching with hashed user identification data
1121
- * - Automatic script loading and initialization
1122
- *
1123
- * Key Resources:
1124
- * - Advanced Matching: https://business-api.tiktok.com/portal/docs?rid=5ipocbxyw8v&id=1739585700402178
1125
- * - Standard Events: https://business-api.tiktok.com/portal/docs?id=1771101186666498
1126
- */
1127
- const isBrowser$1 = typeof window !== 'undefined';
1128
- const isServer$1 = !isBrowser$1;
1129
- /**
1130
- * Standard TikTok events supported by the pixel
1131
- *
1132
- * Note: This list reflects TikTok's official standard events. Events like 'CompletePayment'
1133
- * are not standard events and should be tracked as custom events if needed.
1134
- *
1135
- * Documentation: https://business-api.tiktok.com/portal/docs?id=1771101186666498
1136
- */
1137
- const standardTikTokEvents = ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'ApplicationApproval', 'CompleteRegistration', 'Contact', 'CustomizeProduct', 'Download', 'FindLocation', 'InitiateCheckout', 'Lead', 'Purchase', 'Schedule', 'Search', 'StartTrial', 'SubmitApplication', 'Subscribe', 'ViewContent', 'PageView'];
1138
- /**
1139
- * Create a TikTok Pixel integration instance
1140
- */
1141
- function createTikTokPixelIntegration(config, debug) {
1142
- let isPixelReady = false;
1143
- // Create integration logger
1144
- const integrationLoggerLevel = getIntegrationLoggerLevel('tiktok-pixel', debug);
1145
- const logger = new AnalyticsLogger({
1146
- level: integrationLoggerLevel,
1147
- label: 'Saasco tiktok-pixel'
1148
- });
1149
- return {
1150
- name: 'tiktok-pixel',
1151
- environments: ['client'],
1152
- init: () => __awaiter(this, void 0, void 0, function* () {
1153
- if (isServer$1) {
1154
- logger.debug('TikTok Pixel init skipped on server');
1155
- return;
1156
- }
1157
- logger.info('Initializing TikTok Pixel', {
1158
- pixelId: config.pixelId
1159
- });
1160
- // Check if TikTok Pixel is already loaded
1161
- if (window.ttq) {
1162
- logger.warn('TikTok Pixel is already initialized');
1163
- isPixelReady = true;
1164
- return;
1165
- }
1166
- // Initialize TikTok Pixel
1167
- return new Promise((resolve, reject) => {
1168
- try {
1169
- (function (w, d, t) {
1170
- w.TiktokAnalyticsObject = t;
1171
- const ttq = w[t] = w[t] || [];
1172
- ttq.methods = ['page', 'track', 'identify', 'instances', 'debug', 'on', 'off', 'once', 'ready', 'alias', 'group', 'enableCookie', 'disableCookie', 'holdConsent', 'revokeConsent', 'grantConsent'];
1173
- ttq.setAndDefer = function (t, e) {
1174
- t[e] = function () {
1175
- t.push([e].concat(Array.prototype.slice.call(arguments, 0)));
1176
- };
1177
- };
1178
- for (let i = 0; i < ttq.methods.length; i++) ttq.setAndDefer(ttq, ttq.methods[i]);
1179
- ttq.instance = function (t) {
1180
- const e = ttq._i[t] || [];
1181
- for (let n = 0; n < ttq.methods.length; n++) ttq.setAndDefer(e, ttq.methods[n]);
1182
- return e;
1183
- };
1184
- ttq.load = function (e, n) {
1185
- const r = 'https://analytics.tiktok.com/i18n/pixel/events.js';
1186
- const o = n && n.partner;
1187
- ttq._i = ttq._i || {};
1188
- ttq._i[e] = [];
1189
- ttq._i[e]._u = r;
1190
- ttq._t = ttq._t || {};
1191
- ttq._t[e] = +new Date();
1192
- ttq._o = ttq._o || {};
1193
- ttq._o[e] = n || {};
1194
- const script = document.createElement('script');
1195
- script.type = 'text/javascript';
1196
- script.async = true;
1197
- script.src = r + '?sdkid=' + e + '&lib=' + t;
1198
- // Add load event listener
1199
- script.onload = () => {
1200
- isPixelReady = true;
1201
- resolve();
1202
- };
1203
- script.onerror = () => {
1204
- reject(new Error('Failed to load TikTok Pixel script'));
1205
- };
1206
- const firstScript = document.getElementsByTagName('script')[0];
1207
- firstScript.parentNode.insertBefore(script, firstScript);
1208
- };
1209
- // Load the pixel with the provided ID
1210
- ttq.load(config.pixelId, config.testMode ? {
1211
- test_mode: true
1212
- } : undefined);
1213
- // If script loads synchronously, mark as ready
1214
- if (window.ttq && typeof window.ttq === 'function') {
1215
- isPixelReady = true;
1216
- resolve();
1217
- }
1218
- })(window, document, 'ttq');
1219
- } catch (error) {
1220
- reject(error);
1221
- }
1222
- });
1223
- }),
1224
- track: (eventName, properties, context) => {
1225
- if (!isPixelReady || !window.ttq) return;
1226
- const ttEventName = getTikTokEventName(eventName, config.eventMapping);
1227
- // Handle PageView separately as it uses ttq.page()
1228
- if (ttEventName === 'PageView') {
1229
- window.ttq.page();
1230
- return;
1231
- }
1232
- // Map parameters based on specific event requirements
1233
- const ttParams = mapTikTokEventParameters(ttEventName, properties);
1234
- if (ttParams && Object.keys(ttParams).length > 0) {
1235
- window.ttq.track(ttEventName, ttParams);
1236
- } else {
1237
- window.ttq.track(ttEventName);
1238
- }
1239
- },
1240
- identify: (userId, properties, context) => {
1241
- try {
1242
- if (typeof window !== 'undefined' && window.ttq) {
1243
- const identifyData = coerceTikTokIdentifyValues(Object.assign(Object.assign({}, properties || {}), userId ? {
1244
- external_id: userId
1245
- } : {}));
1246
- if (Object.keys(identifyData).length > 0) {
1247
- window.ttq.identify(identifyData);
1248
- }
1249
- }
1250
- } catch (error) {
1251
- console.error('Error identifying user in TikTok Pixel:', error);
1252
- }
1253
- }
1254
- };
1255
- }
1256
- function getTikTokEventName(eventName, eventMapping) {
1257
- // Convert default saasco Page View event by default
1258
- if (eventName === 'Page View') return 'PageView';
1259
- if (!eventMapping) return eventName;
1260
- return eventMapping[eventName] || eventName;
1261
- }
1262
- /**
1263
- * Get identification values from properties for TikTok Pixel
1264
- *
1265
- * Advanced Matching Documentation:
1266
- * https://business-api.tiktok.com/portal/docs?rid=5ipocbxyw8v&id=1739585700402178
1267
- *
1268
- * Standard Events Documentation:
1269
- * https://business-api.tiktok.com/portal/docs?id=1771101186666498
1270
- *
1271
- * @param properties
1272
- * @returns
1273
- */
1274
- function coerceTikTokIdentifyValues(properties) {
1275
- const coerce = [{
1276
- key: 'email',
1277
- coerceFrom: ['email', 'user_email', 'userEmail', 'email_address', 'emailAddress', 'e_mail', 'E_Mail', 'mail', 'Mail', 'contact_email', 'contactEmail', 'primary_email', 'primaryEmail'],
1278
- transform: value => {
1279
- if (typeof value !== 'string') return undefined;
1280
- const normalized = value.trim().toLowerCase();
1281
- // TikTok requires hashed email
1282
- return normalized ? sha256(normalized) : undefined;
1283
- }
1284
- }, {
1285
- key: 'phone_number',
1286
- coerceFrom: ['phone', 'phone_number', 'phoneNumber', 'mobile', 'mobile_number', 'mobileNumber', 'contact_number', 'contactNumber', 'tel', 'telephone'],
1287
- transform: value => {
1288
- if (typeof value !== 'string' && typeof value !== 'number') return undefined;
1289
- const phoneStr = String(value).replace(/\D/g, '');
1290
- // TikTok requires hashed phone number
1291
- return phoneStr ? sha256(phoneStr) : undefined;
1292
- }
1293
- }, {
1294
- key: 'external_id',
1295
- coerceFrom: ['external_id', 'externalId', 'user_id', 'userId', 'id', 'distinctId', 'distinct_id', 'customer_id', 'customerId', 'member_id', 'memberId'],
1296
- transform: value => {
1297
- if (value == null) return undefined;
1298
- const stringValue = String(value);
1299
- // TikTok requires hashed external_id
1300
- return stringValue ? sha256(stringValue) : undefined;
1301
- }
1302
- }];
1303
- const result = {};
1304
- for (const {
1305
- key,
1306
- coerceFrom,
1307
- transform
1308
- } of coerce) {
1309
- const foundValue = findFirstProperty(properties, coerceFrom);
1310
- if (foundValue !== null && transform) {
1311
- const transformedValue = transform(foundValue);
1312
- if (transformedValue) {
1313
- result[key] = transformedValue;
1314
- }
1315
- } else if (foundValue !== null && typeof foundValue === 'string') {
1316
- result[key] = foundValue;
1317
- }
1318
- }
1319
- return result;
1320
- }
1321
- function findFirstProperty(properties, keys) {
1322
- for (const key of keys) {
1323
- if (properties[key] !== undefined) {
1324
- return properties[key];
1325
- }
1326
- }
1327
- return null;
1328
- }
1329
- /**
1330
- * Maps event properties to TikTok-specific parameters based on event type
1331
- *
1332
- * Parameter Documentation: https://ads.tiktok.com/help/article/about-parameters
1333
- * Standard Events: https://business-api.tiktok.com/portal/docs?id=1771101186666498
1334
- */
1335
- function mapTikTokEventParameters(eventName, properties) {
1336
- if (!properties) return null;
1337
- const params = {};
1338
- // Helper to safely add parameters, skipping objects/null/undefined
1339
- const addParam = (key, value) => {
1340
- if (value != null && typeof value !== 'object') {
1341
- params[key] = value;
1342
- }
1343
- };
1344
- switch (eventName) {
1345
- case 'AddToCart':
1346
- case 'Purchase':
1347
- case 'ViewContent':
1348
- addParam('content_type', properties['content_type']);
1349
- addParam('content_ids', properties['content_ids']);
1350
- addParam('quantity', properties['quantity']);
1351
- addParam('description', properties['description']);
1352
- addParam('currency', properties['currency']);
1353
- addParam('value', properties['value']);
1354
- addParam('content_name', properties['content_name']);
1355
- addParam('content_category', properties['content_category']);
1356
- break;
1357
- case 'StartTrial':
1358
- addParam('content_ids', properties['content_ids']);
1359
- addParam('currency', properties['currency']);
1360
- addParam('value', properties['value']);
1361
- break;
1362
- case 'Search':
1363
- const searchString = properties['search_string'] || properties['query'] || properties['search_query'];
1364
- addParam('search_string', searchString);
1365
- break;
1366
- case 'InitiateCheckout':
1367
- case 'AddPaymentInfo':
1368
- addParam('value', properties['value']);
1369
- addParam('currency', properties['currency']);
1370
- addParam('content_ids', properties['content_ids']);
1371
- addParam('quantity', properties['quantity']);
1372
- break;
1373
- case 'CompleteRegistration':
1374
- case 'Subscribe':
1375
- addParam('value', properties['value']);
1376
- addParam('currency', properties['currency']);
1377
- break;
1378
- default:
1379
- // Pass through common parameters for any other events
1380
- addParam('value', properties['value']);
1381
- addParam('currency', properties['currency']);
1382
- break;
1383
- }
1384
- return Object.keys(params).length > 0 ? params : null;
1385
- }
1386
-
1387
- /*
1388
- Minimal analytics integration manager (v0)
1389
- */
1390
- class IntegrationManager {
1391
- constructor(config = {}) {
1392
- var _a, _b, _c, _d;
1393
- this.context = {};
1394
- this.integrations = new Map();
1395
- this.globalQueue = [];
1396
- this.config = {
1397
- loggerLevel: (_a = config.loggerLevel) !== null && _a !== void 0 ? _a : LogLevel.ERROR,
1398
- maxQueueSize: (_b = config.maxQueueSize) !== null && _b !== void 0 ? _b : 200,
1399
- maxIntegrationWaitTime: (_c = config.maxIntegrationWaitTime) !== null && _c !== void 0 ? _c : 10000,
1400
- flushInterval: (_d = config.flushInterval) !== null && _d !== void 0 ? _d : 5000
1401
- };
1402
- this.logger = new AnalyticsLogger({
1403
- label: 'Saasco Integrations Debug',
1404
- level: this.config.loggerLevel
1405
- });
1406
- this.initTime = Date.now();
1407
- this.currentEnvironment = typeof window !== 'undefined' ? 'client' : 'server';
1408
- this.setupPeriodicFlushing();
1409
- this.setupUnloadHandler();
1410
- }
1411
- /**
1412
- * Shallow-merge context to keep it simple + predictable in v0
1413
- */
1414
- setContext(next) {
1415
- this.context = Object.assign(Object.assign({}, this.context), next);
1416
- // Only log if we are setting something
1417
- if (Object.keys(this.context).length > 0) this.logger.info('setContext', this.context);
1418
- }
1419
- /**
1420
- * Register and init an integration. When init resolves, we mark it ready and
1421
- * immediately flush any queued events in FIFO order to *all* ready integrations.
1422
- */
1423
- registerIntegration(integration) {
1424
- return __awaiter(this, void 0, void 0, function* () {
1425
- this.logger.debug('Register integrations', this.integrations);
1426
- if (this.integrations.has(integration.name)) {
1427
- this.logger.warn(`Integration already registered: ${integration.name}`);
1428
- return;
1429
- }
1430
- // Check environment compatibility
1431
- const isCompatible = integration.environments.includes(this.currentEnvironment);
1432
- if (!isCompatible) {
1433
- this.logger.warn(`Skipping ${integration.name}: requires ${integration.environments.join(' or ')} but running in ${this.currentEnvironment}`);
1434
- return;
1435
- }
1436
- this.logger.debug(`Registering ${integration.name}`);
1437
- const state = {
1438
- integration,
1439
- status: 'idle'
1440
- };
1441
- this.integrations.set(integration.name, state);
1442
- if (integration.init) {
1443
- try {
1444
- state.status = 'loading';
1445
- const startTime = Date.now();
1446
- this.logger.debug(`Initializing ${integration.name}`);
1447
- yield integration.init(this.context);
1448
- state.status = 'ready';
1449
- const endTime = Date.now();
1450
- const duration = endTime - startTime;
1451
- this.logger.debug(`Integration ${integration.name} ready in ${duration}ms`);
1452
- this.flush();
1453
- } catch (e) {
1454
- state.status = 'error';
1455
- this.logger.error(`Failed to init ${integration.name}`, e);
1456
- }
1457
- } else {
1458
- state.status = 'ready';
1459
- this.logger.info(`Integration ${integration.name} ready (no init)`);
1460
- this.flush();
1461
- }
1462
- });
1463
- }
1464
- identify(userId, traits) {
1465
- // update context so subsequent track() carries new userId
1466
- this.setContext({
1467
- userId
1468
- });
1469
- this.send({
1470
- type: 'identify',
1471
- name: undefined,
1472
- properties: traits
1473
- });
1474
- }
1475
- track(name, properties) {
1476
- this.send({
1477
- type: 'track',
1478
- name,
1479
- properties
1480
- });
1481
- }
1482
- /**
1483
- * Core send path: if at least one integration is ready -> deliver immediately
1484
- * Else enqueue (bounded FIFO)
1485
- */
1486
- send(partial) {
1487
- const envelope = {
1488
- id: uuid(),
1489
- type: partial.type,
1490
- timestamp: Date.now(),
1491
- name: partial.name,
1492
- properties: partial.properties,
1493
- context: Object.assign({}, this.context)
1494
- };
1495
- // If we're not ready, enqueue
1496
- if (!this.isReady()) {
1497
- if (this.globalQueue.length >= this.config.maxQueueSize) {
1498
- // drop oldest
1499
- this.globalQueue.shift();
1500
- this.logger.warn('Queue full → dropped oldest');
1501
- }
1502
- this.globalQueue.push(envelope);
1503
- this.logger.debug(`Queued (${this.globalQueue.length})`, envelope);
1504
- return;
1505
- }
1506
- this.deliver(envelope);
1507
- }
1508
- deliver(evt) {
1509
- return __awaiter(this, void 0, void 0, function* () {
1510
- for (const {
1511
- integration,
1512
- status
1513
- } of this.integrations.values()) {
1514
- if (status !== 'ready') continue;
1515
- try {
1516
- if (evt.type === 'track' && integration.track && evt.name) {
1517
- this.logger.debug(`Track (${integration.name}) ${evt.name}`, {
1518
- properties: evt.properties,
1519
- context: evt.context
1520
- });
1521
- yield integration.track(evt.name, evt.properties, evt.context);
1522
- } else if (evt.type === 'identify' && integration.identify) {
1523
- this.logger.debug(`Identify (${integration.name})`, {
1524
- userId: evt.context.distinctId,
1525
- properties: evt.properties,
1526
- context: evt.context
1527
- });
1528
- yield integration.identify(evt.context.distinctId, evt.properties, evt.context);
1529
- }
1530
- } catch (e) {
1531
- this.logger.error(`deliver error in ${integration.name} (${evt.type})`, e);
1532
- }
1533
- }
1534
- });
1535
- }
1536
- /**
1537
- * Flush queued events FIFO once at least one integration is ready.
1538
- */
1539
- flush() {
1540
- // nothing to flush
1541
- if (this.globalQueue.length === 0) return;
1542
- // If we're not ready don't flush
1543
- if (!this.isReady()) return;
1544
- this.logger.debug(`flushing ${this.globalQueue.length} queued events`);
1545
- const toSend = this.globalQueue;
1546
- this.globalQueue = [];
1547
- // deliver all events
1548
- toSend.forEach(evt => this.deliver(evt));
1549
- }
1550
- readyCount() {
1551
- let n = 0;
1552
- for (const s of this.integrations.values()) if (s.status === 'ready') n++;
1553
- return n;
1554
- }
1555
- isReady() {
1556
- const timeElapsed = Date.now() - this.initTime;
1557
- // Return if integrations are not ready, or if we haven't waited the max integration wait time
1558
- if (this.readyCount() < this.integrations.size && timeElapsed < this.config.maxIntegrationWaitTime) {
1559
- return false;
1560
- }
1561
- if (this.readyCount() < this.integrations.size) {
1562
- this.logger.info(`proceeding with ${this.readyCount()}/${this.integrations.size} integrations ready after ${timeElapsed}ms wait`);
1563
- }
1564
- return true;
1565
- }
1566
- /**
1567
- * Setup periodic flushing if enabled
1568
- */
1569
- setupPeriodicFlushing() {
1570
- if (this.config.flushInterval <= 0) return;
1571
- this.flushTimer = setInterval(() => {
1572
- if (this.globalQueue.length > 0) {
1573
- this.flush();
1574
- }
1575
- }, this.config.flushInterval);
1576
- }
1577
- /**
1578
- * Setup page unload handler for client environment
1579
- */
1580
- setupUnloadHandler() {
1581
- if (this.currentEnvironment !== 'client' || typeof window === 'undefined') {
1582
- return;
1583
- }
1584
- this.unloadHandler = () => {
1585
- this.logger.info('page unloading, flushing remaining events');
1586
- this.flush();
1587
- };
1588
- // Use both beforeunload and pagehide for better coverage
1589
- window.addEventListener('beforeunload', this.unloadHandler);
1590
- window.addEventListener('pagehide', this.unloadHandler);
1591
- }
1592
- /** Debug helpers */
1593
- getStats() {
1594
- return {
1595
- currentEnvironment: this.currentEnvironment,
1596
- integrations: [...this.integrations.values()].map(s => ({
1597
- name: s.integration.name,
1598
- status: s.status,
1599
- environments: s.integration.environments
1600
- })),
1601
- queueLength: this.globalQueue.length,
1602
- readyCount: this.readyCount(),
1603
- periodicFlushEnabled: this.config.flushInterval > 0,
1604
- flushInterval: this.config.flushInterval,
1605
- context: this.context
1606
- };
1607
- }
1608
- }
1609
-
1610
- const isBrowser = typeof window !== 'undefined';
1611
- const isServer = !isBrowser;
1612
- const PREF = 'saasco-sdk';
1613
- const SESSION_DURATION = 1000 * 60 * 30;
1614
- const USER_DURATION = 1000 * 60 * 60 * 24 * 365;
1615
- const data = {};
1616
- function getRootDomain(url) {
1617
- if (isServer) return;
1618
- if (!url) return;
1619
- try {
1620
- const hostname = new URL(url).hostname;
1621
- if (!isValid(hostname)) return;
1622
- const parsed = parse(hostname);
1623
- if ('domain' in parsed && parsed.domain) return parsed.domain;
1624
- } catch (error) {
1625
- // invalid url
1626
- }
1627
- return;
1628
- }
1629
- function setCookie(name, value, ttl) {
1630
- if (isServer) return;
1631
- const expires = new Date(Date.now() + ttl).toUTCString();
1632
- const domain = getRootDomain(window.location.href);
1633
- const cookieString = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${domain ? `; domain=.${domain}` : ''}`;
1634
- document.cookie = cookieString;
1635
- }
1636
- function getCookie(name) {
1637
- if (isServer) return null;
1638
- const nameEQ = name + '=';
1639
- const ca = document.cookie.split(';');
1640
- for (let i = 0; i < ca.length; i++) {
1641
- let c = ca[i];
1642
- while (c.charAt(0) === ' ') c = c.substring(1, c.length);
1643
- if (c.indexOf(nameEQ) === 0) {
1644
- return decodeURIComponent(c.substring(nameEQ.length, c.length));
1645
- }
1646
- }
1647
- return null;
1648
- }
1649
- function deleteCookie(name) {
1650
- if (isServer) return;
1651
- const domain = getRootDomain(window.location.href);
1652
- const cookieString = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/${domain ? `; domain=.${domain}` : ''}`;
1653
- document.cookie = cookieString;
1654
- }
1655
- function migrateFromLocalStorage() {
1656
- if (isServer) return;
1657
- const keysToMigrate = [`${PREF}-session-id`, `${PREF}-anonymous-id`, `${PREF}-user-id`, `${PREF}-session-utm-context`, `${PREF}-super-context`];
1658
- keysToMigrate.forEach(key => {
1659
- try {
1660
- const value = localStorage.getItem(key);
1661
- if (value) {
1662
- // Parse the stored data to get the original TTL
1663
- const item = JSON.parse(value);
1664
- const now = new Date().getTime();
1665
- // Check if the data is still valid
1666
- if (item.expiry > now) {
1667
- // Calculate remaining TTL
1668
- const remainingTtl = item.expiry - now;
1669
- // Store in cookie with remaining TTL
1670
- setCookie(key, value, remainingTtl);
1671
- // Remove from localStorage after successful migration
1672
- localStorage.removeItem(key);
1673
- } else {
1674
- // Data has expired, just remove it
1675
- localStorage.removeItem(key);
1676
- }
1677
- }
1678
- } catch (error) {
1679
- // If there's an error parsing the data, remove the corrupted item
1680
- localStorage.removeItem(key);
1681
- }
1682
- });
1683
- }
1684
- function storeData(key, value, ttl) {
1685
- if (isServer) {
1686
- const item = {
1687
- value,
1688
- expiry: new Date().getTime() + ttl
1689
- };
1690
- if (value === undefined) {
1691
- delete data[key];
1692
- return;
1693
- }
1694
- data[key] = item;
1695
- return;
1696
- }
1697
- const fullKey = `${PREF}-${key}`;
1698
- if (value === undefined) {
1699
- deleteCookie(fullKey);
1700
- return;
1701
- }
1702
- const item = {
1703
- value,
1704
- expiry: new Date().getTime() + ttl
1705
- };
1706
- setCookie(fullKey, JSON.stringify(item), ttl);
1707
- }
1708
- function retrieveData(key) {
1709
- if (isServer) {
1710
- const item = data[key];
1711
- if (!item) return null;
1712
- if (Date.now() > item.expiry) {
1713
- delete data[key];
1714
- return null;
1715
- }
1716
- return item.value;
1717
- }
1718
- const fullKey = `${PREF}-${key}`;
1719
- const itemStr = getCookie(fullKey);
1720
- if (!itemStr) {
1721
- return null;
1722
- }
1723
- try {
1724
- const item = JSON.parse(itemStr);
1725
- if (Date.now() > item.expiry) {
1726
- deleteCookie(fullKey);
1727
- return null;
1728
- }
1729
- return item.value;
1730
- } catch (error) {
1731
- // If JSON parsing fails, remove the corrupted cookie
1732
- deleteCookie(fullKey);
1733
- return null;
1734
- }
1735
- }
1736
- function getSessionId() {
1737
- return retrieveData(`session-id`);
1738
- }
1739
- function setSessionId({
1740
- reset
1741
- } = {
1742
- reset: false
1743
- }) {
1744
- const sessionId = reset ? uuid() : getSessionId() || uuid();
1745
- storeData(`session-id`, sessionId, SESSION_DURATION);
1746
- return sessionId;
1747
- }
1748
- function getAnonymousId() {
1749
- return retrieveData(`anonymous-id`);
1750
- }
1751
- function setAnonymousId({
1752
- reset
1753
- } = {
1754
- reset: false
1755
- }) {
1756
- const anonymousId = reset ? uuid() : getAnonymousId() || uuid();
1757
- storeData(`anonymous-id`, anonymousId, USER_DURATION);
1758
- return anonymousId;
1759
- }
1760
- function getUserId() {
1761
- return retrieveData(`user-id`);
1762
- }
1763
- function setUserId(userId) {
1764
- storeData(`user-id`, userId, USER_DURATION);
1765
- }
1766
- function getSuperContext() {
1767
- return retrieveData('super-context') || {};
1768
- }
1769
- function setSuperContext(superContext) {
1770
- storeData('super-context', superContext, SESSION_DURATION);
1771
- }
1772
- function updateSuperContext(browserContext) {
1773
- const existingSuperContext = getSuperContext();
1774
- const defaultSuperContextKeys = ['$utmSource', '$utmMedium', '$utmCampaign', '$utmTerm', '$utmContent', '$utmId', '$utmSourcePlatform', '$utmCampaignId', '$utmCreativeFormat', '$utmMarketingTactic', '$utmAdSource', '$utmAdId'];
1775
- const superContext = defaultSuperContextKeys.reduce((acc, key) => {
1776
- const value = Object.assign({}, browserContext)[key];
1777
- if (value) acc[key] = value;
1778
- return acc;
1779
- }, existingSuperContext);
1780
- // Only set referrer if it's from a different root domain
1781
- // Prevents setting referrer when going between subdomains of the same root domain
1782
- if (browserContext.$referrer) {
1783
- const referrerRootDomain = getRootDomain(browserContext.$referrer);
1784
- const currentRootDomain = getRootDomain(browserContext.$href);
1785
- if (referrerRootDomain && currentRootDomain && referrerRootDomain !== currentRootDomain) {
1786
- superContext['$referrer'] = browserContext.$referrer;
1787
- }
1788
- }
1789
- setSuperContext(superContext);
1790
- return superContext;
1791
- }
1792
- class Saasco {
1793
- /**
1794
- * Creates an instance of Saasco SDK.
1795
- * @param config Configuration options.
1796
- * @param config.projectId The unique identifier for the project.
1797
- * @param config.proxy The URL of the proxy server to use, if any.
1798
- * @param config.autoPageTracking Whether to automatically track page views. Default is false.
1799
- * @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
1800
- * @param config.debug Whether to log debug information. Default is false.
1801
- * @param config.trackUrlParams Whether to track URL parameters. Default is true.
1802
- * @param config.trackHashChanges Whether to track hash changes. Default is true.
1803
- * @param config.integrations Configuration for third-party integrations like Facebook Pixel.
1804
- */
1805
- constructor(config) {
1806
- this.config = config;
1807
- this.lastPageViewPath = '';
1808
- this.isInitialized = false;
1809
- const loggerLevel = getLoggerLevel(this.config);
1810
- // Initialize logger
1811
- this.logger = new AnalyticsLogger({
1812
- level: loggerLevel,
1813
- label: 'Saasco Debug'
1814
- });
1815
- this.logger.info('Saasco started');
1816
- if (!config.projectId) {
1817
- 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.");
1818
- return;
1819
- }
1820
- // default enabled to true
1821
- this.config.enabled = this.config.enabled === undefined ? true : this.config.enabled;
1822
- // Set default auto page tracking config
1823
- this.config.autoPageTracking = Object.assign({
1824
- enabled: true,
1825
- trackQueryParams: true,
1826
- trackHash: true
1827
- }, this.config.autoPageTracking);
1828
- // Initialize integration manager
1829
- this.integrationManager = new IntegrationManager({
1830
- loggerLevel,
1831
- maxQueueSize: 1000
1832
- });
1833
- // Set initial context
1834
- this.integrationManager.setContext({});
1835
- // Initialize integrations immediately (works on both client and server)
1836
- this.initIntegrations().catch(error => {
1837
- this.logger.info('Error initializing integrations:', error);
1838
- });
1839
- }
1840
- init() {
1841
- if (this.isInitialized) {
1842
- this.logger.info('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
1843
- return;
1844
- }
1845
- // Export Saasco to the window object for easy access and debugging
1846
- if (isBrowser) {
1847
- window.saasco = this;
1848
- }
1849
- // Migrate existing localStorage data to cookies for cross-subdomain support
1850
- migrateFromLocalStorage();
1851
- this.logger.info('Saasco initialized', this.config);
1852
- if (this.config.debug) this.logger.info('Debug mode active. This will log all events to the console.');
1853
- 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.' : ''}`);
1854
- this.initAutoPageTracking();
1855
- this.isInitialized = true;
1856
- }
1857
- disableDebug() {
1858
- this.logger.info('Debug mode deactivated.');
1859
- this.config.debug = false;
1860
- }
1861
- enableDebug() {
1862
- this.config.debug = true;
1863
- this.logger.info('Debug mode activated.');
1864
- }
1865
- /**
1866
- * Initialize third-party integrations
1867
- */
1868
- initIntegrations() {
1869
- return __awaiter(this, void 0, void 0, function* () {
1870
- this.logger.info('Initialize integrations');
1871
- if (!this.config.integrations || this.config.integrations.length === 0) return;
1872
- if (!this.config.enabled) {
1873
- this.logger.info('Analytics is disabled. Skipping integration initialization.');
1874
- return;
1875
- }
1876
- for (const integrationConfig of this.config.integrations) {
1877
- try {
1878
- if (integrationConfig.type === 'facebook-pixel') {
1879
- this.logger.info('Registering Facebook Pixel integration with ID:', integrationConfig.config.pixelId);
1880
- const fbIntegration = createFacebookPixelIntegration(integrationConfig.config, integrationConfig.debug);
1881
- yield this.integrationManager.registerIntegration(fbIntegration);
1882
- } else if (integrationConfig.type === 'pinterest-tag') {
1883
- this.logger.info('Registering Pinterest Tag integration with ID:', integrationConfig.config.tagId);
1884
- const pinterestIntegration = createPinterestTagIntegration(integrationConfig.config, integrationConfig.debug);
1885
- yield this.integrationManager.registerIntegration(pinterestIntegration);
1886
- } else if (integrationConfig.type === 'tiktok-pixel') {
1887
- this.logger.info('Registering TikTok Pixel integration with ID:', integrationConfig.config.pixelId);
1888
- const tiktokIntegration = createTikTokPixelIntegration(integrationConfig.config, integrationConfig.debug);
1889
- yield this.integrationManager.registerIntegration(tiktokIntegration);
1890
- }
1891
- } catch (error) {
1892
- this.logger.info(`Failed to register ${integrationConfig.type} integration:`, error);
1893
- }
1894
- }
1895
- });
1896
- }
1897
- track(actionOrPayload, propertiesOrNothing, contextOrNothing) {
1898
- if (!this.config.projectId) {
1899
- 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.");
1900
- return Promise.resolve(response);
1901
- }
1902
- setSessionId();
1903
- setAnonymousId();
1904
- const hasAction = typeof actionOrPayload === 'string';
1905
- const hasPayload = typeof actionOrPayload === 'object';
1906
- // Must do payload on the server
1907
- if (isServer && hasAction) {
1908
- 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" } })');
1909
- return Promise.resolve(response);
1910
- }
1911
- // On server events we require a userId or an anonymousId
1912
- if (isServer && hasPayload && !actionOrPayload.userId && !actionOrPayload.anonymousId) {
1913
- 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.');
1914
- return Promise.resolve(response);
1915
- }
1916
- const action = hasAction ? actionOrPayload : actionOrPayload.event;
1917
- const properties = hasAction ? propertiesOrNothing : actionOrPayload.properties;
1918
- const context = hasAction ? contextOrNothing : actionOrPayload.context;
1919
- const sessionId = (hasAction ? undefined : actionOrPayload.sessionId) || getSessionId();
1920
- const anonymousId = (hasAction ? undefined : actionOrPayload.anonymousId) || getAnonymousId();
1921
- const distinctId = (hasAction ? undefined : actionOrPayload.userId) || getUserId();
1922
- // Only gets browser and super context if in the browser
1923
- let browserContext;
1924
- let superContext;
1925
- if (isBrowser) {
1926
- browserContext = getBrowserContext();
1927
- superContext = updateSuperContext(browserContext);
1928
- }
1929
- const payloadJSON = Object.assign(Object.assign(Object.assign({}, browserContext), superContext), {
1930
- properties: properties || {}
1931
- });
1932
- const data = {
1933
- id: uuid(),
1934
- timestamp: new Date().toISOString(),
1935
- action,
1936
- version,
1937
- sessionId,
1938
- anonymousId,
1939
- distinctId,
1940
- projectId: this.config.projectId,
1941
- payload: JSON.stringify(payloadJSON),
1942
- source: isBrowser ? 'client' : 'server',
1943
- context: JSON.stringify(context || {})
1944
- };
1945
- if (data.action === 'Page View') {
1946
- this.logger.info('Page View', window.location.href, data);
1947
- } else {
1948
- this.logger.info('track', data);
1949
- }
1950
- if (!this.config.enabled) {
1951
- return Promise.resolve({
1952
- success: true,
1953
- message: 'Analytics is disabled'
1954
- });
1955
- }
1956
- // Only send track to integrations if analytics is enabled
1957
- this.integrationManager.track(action, properties);
1958
- // Send event to API
1959
- return this.doRequest('events', data);
1960
- }
1961
- /**
1962
- * The page method lets you record page views on your website
1963
- * This records the page title and path and names the event useing the reserved property "Page Viewed"
1964
- *
1965
- * Before implementing this make sure you have disabled the autoPageTracking in the config or you will get duplicate page views
1966
- */
1967
- page() {
1968
- var _a, _b;
1969
- if (isServer) {
1970
- console.warn('Saasco page tracking is only available in the browser');
1971
- return;
1972
- }
1973
- const url = new URL(window.location.href);
1974
- let pathToCompare = url.pathname;
1975
- // Include search params if configured
1976
- if ((_a = this.config.autoPageTracking) === null || _a === void 0 ? void 0 : _a.trackQueryParams) {
1977
- pathToCompare += url.search;
1978
- }
1979
- // Include hash if configured
1980
- if ((_b = this.config.autoPageTracking) === null || _b === void 0 ? void 0 : _b.trackHash) {
1981
- pathToCompare += url.hash;
1982
- }
1983
- // Only track if the path has changed
1984
- if (pathToCompare === this.lastPageViewPath) return;
1985
- this.lastPageViewPath = pathToCompare;
1986
- return this.track('Page View');
1987
- }
1988
- identify(distinctIdOrProperties, propertiesOrContext, contextOrNothing) {
1989
- if (!this.config.projectId) {
1990
- 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.");
1991
- return Promise.resolve(response);
1992
- }
1993
- const hasId = typeof distinctIdOrProperties === 'string' || typeof distinctIdOrProperties === 'number' || distinctIdOrProperties === null;
1994
- const distinctId = hasId ? distinctIdOrProperties === null || distinctIdOrProperties === void 0 ? void 0 : distinctIdOrProperties.toString() : `soft_${uuid()}`;
1995
- const properties = hasId ? propertiesOrContext : distinctIdOrProperties;
1996
- const context = hasId ? contextOrNothing : propertiesOrContext;
1997
- // When the user gets identified as null this will reset the users session and anonymous id
1998
- // This should only be called when distinctId is null and there is an existing userId.
1999
- // This means the user has logged out and we should reset the session and anonymous IDs
2000
- const userIdChangedToNull = distinctId === null && !!getUserId();
2001
- if (userIdChangedToNull) this.logger.info('User logged out');
2002
- const reset = userIdChangedToNull;
2003
- const sessionId = setSessionId({
2004
- reset
2005
- });
2006
- const anonymousId = setAnonymousId({
2007
- reset
2008
- });
2009
- // set the distinct Id to the userId
2010
- setUserId(distinctId);
2011
- // Update integration manager context and send identify (IntegrationManager will filter by environment)
2012
- this.integrationManager.setContext(Object.assign({
2013
- distinctId,
2014
- anonymousId,
2015
- sessionId
2016
- }, properties));
2017
- // No distinctId provided so we don't track the user
2018
- if (!distinctId) return Promise.resolve({
2019
- success: true,
2020
- message: 'No distinctId provided'
2021
- });
2022
- // Send identify to API
2023
- const data = {
2024
- id: uuid(),
2025
- timestamp: new Date().toISOString(),
2026
- projectId: this.config.projectId,
2027
- distinctId,
2028
- anonymousId,
2029
- version,
2030
- payload: properties || {},
2031
- context: context || {}
2032
- };
2033
- this.logger.info('identify', data);
2034
- // Only send identify to integrations if analytics is enabled
2035
- if (!this.config.enabled) {
2036
- return Promise.resolve({
2037
- success: true,
2038
- message: 'Analytics is disabled'
2039
- });
2040
- }
2041
- this.integrationManager.identify(distinctId, properties);
2042
- return this.doRequest('identify', data);
2043
- }
2044
- /**
2045
- * This should only be called when the user logs out
2046
- * It will reset the session, anonymous id, and user id
2047
- *
2048
- * All events after calling reset will be tracked as a new user
2049
- *
2050
- */
2051
- logout() {
2052
- // Clear super context data on logout
2053
- setSuperContext(null);
2054
- this.identify(null);
2055
- }
2056
- /**
2057
- * Handles sending data to the Saasco API
2058
- * 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
2059
- * @param path
2060
- * @param data
2061
- */
2062
- doRequest(path, data) {
2063
- return __awaiter(this, void 0, void 0, function* () {
2064
- const base = this.config.proxy || 'https://www.saasco.com/api/';
2065
- const url = `${base}${path}`;
2066
- // If analytics is disabled, don't send the request
2067
- if (this.config.enabled === false) return {
2068
- success: false,
2069
- message: 'Analytics is disabled'
2070
- };
2071
- try {
2072
- const response = yield fetch(url, {
2073
- method: 'POST',
2074
- headers: {
2075
- 'Content-Type': 'application/json'
2076
- },
2077
- body: JSON.stringify(data)
2078
- });
2079
- const responseBody = yield response.json();
2080
- if (!response.ok) {
2081
- throw new Error(`${responseBody.message}: ${responseBody.errors ? `[${responseBody.errors.join(', ')}]` : ''}` || JSON.stringify(responseBody));
2082
- }
2083
- return {
2084
- success: true,
2085
- message: responseBody.message
2086
- };
2087
- } catch (error) {
2088
- this.logger.error(`\nError Message: "${error.message}"`, `\nPath: "/${path}"`, `\nRequest Data: ${JSON.stringify(data, null, 2)}`);
2089
- return {
2090
- success: false,
2091
- message: error.message
2092
- };
2093
- }
2094
- });
2095
- }
2096
- /**
2097
- * If autoPageTracking is enabled, this will automatically track page views
2098
- * It listens to url changes to track new pages every time the url changes
2099
- * @returns
2100
- */
2101
- initAutoPageTracking() {
2102
- var _a, _b;
2103
- // Disable auto page tracking if the config is set to false
2104
- if (!((_a = this.config.autoPageTracking) === null || _a === void 0 ? void 0 : _a.enabled)) return;
2105
- // Prevent running on the server
2106
- if (isServer) return console.warn('Saasco auto page tracking is only available in the browser');
2107
- // Prevent intitializing auto page tracking more than once
2108
- if (window.saascoAutoPageTrackingActive) {
2109
- this.logger.info('Auto Page Tracking already enabled');
2110
- return;
2111
- }
2112
- window.saascoAutoPageTrackingActive = true;
2113
- this.logger.info('Auto Page Tracking enabled');
2114
- // Track initial page load
2115
- this.page();
2116
- // Listen for hash changes if hash tracking is enabled
2117
- if ((_b = this.config.autoPageTracking) === null || _b === void 0 ? void 0 : _b.trackHash) {
2118
- window.addEventListener('hashchange', () => this.page());
2119
- }
2120
- // Listen to popstate for back and forward navigation
2121
- window.addEventListener('popstate', () => this.page());
2122
- // Wrap history push state to listen for URL changes
2123
- const historyPushState = history.pushState;
2124
- history.pushState = (...args) => {
2125
- const returnValue = historyPushState.apply(history, args);
2126
- this.page();
2127
- return returnValue;
2128
- };
2129
- // Wrap history replace state to listen for URL changes
2130
- const historyReplaceState = history.replaceState;
2131
- history.replaceState = (...args) => {
2132
- const returnValue = historyReplaceState.apply(history, args);
2133
- this.page();
2134
- return returnValue;
2135
- };
2136
- }
2137
- getIntegrationsStats() {
2138
- return this.integrationManager.getStats();
2139
- }
2140
- }
2141
- function getLoggerLevel(config) {
2142
- // Check URL parameters for debug overrides (browser only)
2143
- let urlDebug = false;
2144
- let urlDebugVerbose = false;
2145
- if (!isServer) {
2146
- try {
2147
- const urlParams = new URLSearchParams(window.location.search);
2148
- urlDebug = urlParams.get('saasco-debug') === 'true';
2149
- urlDebugVerbose = urlParams.get('saasco-debug-verbose') === 'true';
2150
- } catch (error) {
2151
- // Ignore URL parsing errors
2152
- }
2153
- }
2154
- // URL parameters override config
2155
- const effectiveDebug = urlDebug || config.debug;
2156
- const effectiveDebugVerbose = urlDebugVerbose || config.debugVerbose;
2157
- return !!effectiveDebugVerbose ? LogLevel.DEBUG : !!effectiveDebug ? LogLevel.INFO : LogLevel.ERROR;
2158
- }
2159
-
2160
- const browserContextSchema = z.object({
2161
- $locale: z.string(),
2162
- $location: z.string(),
2163
- $href: z.string(),
2164
- $pathname: z.string(),
2165
- $referrer: z.string(),
2166
- $screenDPI: z.number(),
2167
- $screenHeight: z.number(),
2168
- $screenWidth: z.number(),
2169
- $title: z.string(),
2170
- $userAgent: z.string(),
2171
- $utmSource: z.string().nullable(),
2172
- $utmMedium: z.string().nullable(),
2173
- $utmCampaign: z.string().nullable(),
2174
- $utmTerm: z.string().nullable(),
2175
- $utmContent: z.string().nullable(),
2176
- $utmId: z.string().nullable(),
2177
- $utmSourcePlatform: z.string().nullable(),
2178
- $utmCampaignId: z.string().nullable(),
2179
- $utmCreativeFormat: z.string().nullable(),
2180
- $utmMarketingTactic: z.string().nullable(),
2181
- $utmAdSource: z.string().nullable(),
2182
- $utmAdId: z.string().nullable()
2183
- });
2184
-
2185
- export { IntegrationManager, Saasco, browserContextSchema, createFacebookPixelIntegration, createPinterestTagIntegration, createTikTokPixelIntegration, getBrowserContext, standardFacebookEvents, standardPinterestEvents, standardTikTokEvents, timezones };