saasco-sdk 0.1.45 → 0.2.4

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