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