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/dist/index.js ADDED
@@ -0,0 +1,3006 @@
1
+ // src/lib/analytics.ts
2
+ import { isValid, parse } from "psl";
3
+
4
+ // package.json
5
+ var version = "0.2.4";
6
+
7
+ // src/lib/timezones.ts
8
+ var timezones = {
9
+ "Africa/Abidjan": "CI",
10
+ "Africa/Accra": "GH",
11
+ "Africa/Addis_Ababa": "ET",
12
+ "Africa/Algiers": "DZ",
13
+ "Africa/Asmara": "ER",
14
+ "Africa/Bamako": "ML",
15
+ "Africa/Bangui": "CF",
16
+ "Africa/Banjul": "GM",
17
+ "Africa/Bissau": "GW",
18
+ "Africa/Blantyre": "MW",
19
+ "Africa/Brazzaville": "CG",
20
+ "Africa/Bujumbura": "BI",
21
+ "Africa/Cairo": "EG",
22
+ "Africa/Casablanca": "MA",
23
+ "Africa/Ceuta": "ES",
24
+ "Africa/Conakry": "GN",
25
+ "Africa/Dakar": "SN",
26
+ "Africa/Dar_es_Salaam": "TZ",
27
+ "Africa/Djibouti": "DJ",
28
+ "Africa/Douala": "CM",
29
+ "Africa/El_Aaiun": "EH",
30
+ "Africa/Freetown": "SL",
31
+ "Africa/Gaborone": "BW",
32
+ "Africa/Harare": "ZW",
33
+ "Africa/Johannesburg": "ZA",
34
+ "Africa/Juba": "SS",
35
+ "Africa/Kampala": "UG",
36
+ "Africa/Khartoum": "SD",
37
+ "Africa/Kigali": "RW",
38
+ "Africa/Kinshasa": "CD",
39
+ "Africa/Lagos": "NG",
40
+ "Africa/Libreville": "GA",
41
+ "Africa/Lome": "TG",
42
+ "Africa/Luanda": "AO",
43
+ "Africa/Lubumbashi": "CD",
44
+ "Africa/Lusaka": "ZM",
45
+ "Africa/Malabo": "GQ",
46
+ "Africa/Maputo": "MZ",
47
+ "Africa/Maseru": "LS",
48
+ "Africa/Mbabane": "SZ",
49
+ "Africa/Mogadishu": "SO",
50
+ "Africa/Monrovia": "LR",
51
+ "Africa/Nairobi": "KE",
52
+ "Africa/Ndjamena": "TD",
53
+ "Africa/Niamey": "NE",
54
+ "Africa/Nouakchott": "MR",
55
+ "Africa/Ouagadougou": "BF",
56
+ "Africa/Porto-Novo": "BJ",
57
+ "Africa/Sao_Tome": "ST",
58
+ "Africa/Tripoli": "LY",
59
+ "Africa/Tunis": "TN",
60
+ "Africa/Windhoek": "NA",
61
+ "America/Adak": "US",
62
+ "America/Anchorage": "US",
63
+ "America/Anguilla": "AI",
64
+ "America/Antigua": "AG",
65
+ "America/Araguaina": "BR",
66
+ "America/Argentina/Buenos_Aires": "AR",
67
+ "America/Argentina/Catamarca": "AR",
68
+ "America/Argentina/Cordoba": "AR",
69
+ "America/Argentina/Jujuy": "AR",
70
+ "America/Argentina/La_Rioja": "AR",
71
+ "America/Argentina/Mendoza": "AR",
72
+ "America/Argentina/Rio_Gallegos": "AR",
73
+ "America/Argentina/Salta": "AR",
74
+ "America/Argentina/San_Juan": "AR",
75
+ "America/Argentina/San_Luis": "AR",
76
+ "America/Argentina/Tucuman": "AR",
77
+ "America/Argentina/Ushuaia": "AR",
78
+ "America/Aruba": "AW",
79
+ "America/Asuncion": "PY",
80
+ "America/Atikokan": "CA",
81
+ "America/Bahia": "BR",
82
+ "America/Bahia_Banderas": "MX",
83
+ "America/Barbados": "BB",
84
+ "America/Belem": "BR",
85
+ "America/Belize": "BZ",
86
+ "America/Blanc-Sablon": "CA",
87
+ "America/Boa_Vista": "BR",
88
+ "America/Bogota": "CO",
89
+ "America/Boise": "US",
90
+ "America/Cambridge_Bay": "CA",
91
+ "America/Campo_Grande": "BR",
92
+ "America/Cancun": "MX",
93
+ "America/Caracas": "VE",
94
+ "America/Cayenne": "GF",
95
+ "America/Cayman": "KY",
96
+ "America/Chicago": "US",
97
+ "America/Chihuahua": "MX",
98
+ "America/Costa_Rica": "CR",
99
+ "America/Creston": "CA",
100
+ "America/Cuiaba": "BR",
101
+ "America/Curacao": "CW",
102
+ "America/Danmarkshavn": "GL",
103
+ "America/Dawson": "CA",
104
+ "America/Dawson_Creek": "CA",
105
+ "America/Denver": "US",
106
+ "America/Detroit": "US",
107
+ "America/Dominica": "DM",
108
+ "America/Edmonton": "CA",
109
+ "America/Eirunepe": "BR",
110
+ "America/El_Salvador": "SV",
111
+ "America/Fort_Nelson": "CA",
112
+ "America/Fortaleza": "BR",
113
+ "America/Glace_Bay": "CA",
114
+ "America/Goose_Bay": "CA",
115
+ "America/Grand_Turk": "TC",
116
+ "America/Grenada": "GD",
117
+ "America/Guadeloupe": "GP",
118
+ "America/Guatemala": "GT",
119
+ "America/Guayaquil": "EC",
120
+ "America/Guyana": "GY",
121
+ "America/Halifax": "CA",
122
+ "America/Havana": "CU",
123
+ "America/Hermosillo": "MX",
124
+ "America/Indiana/Indianapolis": "US",
125
+ "America/Indiana/Knox": "US",
126
+ "America/Indiana/Marengo": "US",
127
+ "America/Indiana/Petersburg": "US",
128
+ "America/Indiana/Tell_City": "US",
129
+ "America/Indiana/Vevay": "US",
130
+ "America/Indiana/Vincennes": "US",
131
+ "America/Indiana/Winamac": "US",
132
+ "America/Inuvik": "CA",
133
+ "America/Iqaluit": "CA",
134
+ "America/Jamaica": "JM",
135
+ "America/Juneau": "US",
136
+ "America/Kentucky/Louisville": "US",
137
+ "America/Kentucky/Monticello": "US",
138
+ "America/Kralendijk": "BQ",
139
+ "America/La_Paz": "BO",
140
+ "America/Lima": "PE",
141
+ "America/Los_Angeles": "US",
142
+ "America/Lower_Princes": "SX",
143
+ "America/Maceio": "BR",
144
+ "America/Managua": "NI",
145
+ "America/Manaus": "BR",
146
+ "America/Marigot": "MF",
147
+ "America/Martinique": "MQ",
148
+ "America/Matamoros": "MX",
149
+ "America/Mazatlan": "MX",
150
+ "America/Menominee": "US",
151
+ "America/Merida": "MX",
152
+ "America/Metlakatla": "US",
153
+ "America/Mexico_City": "MX",
154
+ "America/Miquelon": "PM",
155
+ "America/Moncton": "CA",
156
+ "America/Monterrey": "MX",
157
+ "America/Montevideo": "UY",
158
+ "America/Montserrat": "MS",
159
+ "America/Nassau": "BS",
160
+ "America/New_York": "US",
161
+ "America/Nipigon": "CA",
162
+ "America/Nome": "US",
163
+ "America/Noronha": "BR",
164
+ "America/North_Dakota/Beulah": "US",
165
+ "America/North_Dakota/Center": "US",
166
+ "America/North_Dakota/New_Salem": "US",
167
+ "America/Nuuk": "GL",
168
+ "America/Ojinaga": "MX",
169
+ "America/Panama": "PA",
170
+ "America/Pangnirtung": "CA",
171
+ "America/Paramaribo": "SR",
172
+ "America/Phoenix": "US",
173
+ "America/Port-au-Prince": "HT",
174
+ "America/Port_of_Spain": "TT",
175
+ "America/Porto_Velho": "BR",
176
+ "America/Puerto_Rico": "PR",
177
+ "America/Punta_Arenas": "CL",
178
+ "America/Rainy_River": "CA",
179
+ "America/Rankin_Inlet": "CA",
180
+ "America/Recife": "BR",
181
+ "America/Regina": "CA",
182
+ "America/Resolute": "CA",
183
+ "America/Rio_Branco": "BR",
184
+ "America/Santarem": "BR",
185
+ "America/Santiago": "CL",
186
+ "America/Santo_Domingo": "DO",
187
+ "America/Sao_Paulo": "BR",
188
+ "America/Scoresbysund": "GL",
189
+ "America/Sitka": "US",
190
+ "America/St_Barthelemy": "BL",
191
+ "America/St_Johns": "CA",
192
+ "America/St_Kitts": "KN",
193
+ "America/St_Lucia": "LC",
194
+ "America/St_Thomas": "VI",
195
+ "America/St_Vincent": "VC",
196
+ "America/Swift_Current": "CA",
197
+ "America/Tegucigalpa": "HN",
198
+ "America/Thule": "GL",
199
+ "America/Thunder_Bay": "CA",
200
+ "America/Tijuana": "MX",
201
+ "America/Toronto": "CA",
202
+ "America/Tortola": "VG",
203
+ "America/Vancouver": "CA",
204
+ "America/Whitehorse": "CA",
205
+ "America/Winnipeg": "CA",
206
+ "America/Yakutat": "US",
207
+ "America/Yellowknife": "CA",
208
+ "Antarctica/Casey": "AQ",
209
+ "Antarctica/Davis": "AQ",
210
+ "Antarctica/DumontDUrville": "AQ",
211
+ "Antarctica/Macquarie": "AU",
212
+ "Antarctica/Mawson": "AQ",
213
+ "Antarctica/McMurdo": "AQ",
214
+ "Antarctica/Palmer": "AQ",
215
+ "Antarctica/Rothera": "AQ",
216
+ "Antarctica/Syowa": "AQ",
217
+ "Antarctica/Troll": "AQ",
218
+ "Antarctica/Vostok": "AQ",
219
+ "Arctic/Longyearbyen": "SJ",
220
+ "Asia/Aden": "YE",
221
+ "Asia/Almaty": "KZ",
222
+ "Asia/Amman": "JO",
223
+ "Asia/Anadyr": "RU",
224
+ "Asia/Aqtau": "KZ",
225
+ "Asia/Aqtobe": "KZ",
226
+ "Asia/Ashgabat": "TM",
227
+ "Asia/Atyrau": "KZ",
228
+ "Asia/Baghdad": "IQ",
229
+ "Asia/Bahrain": "BH",
230
+ "Asia/Baku": "AZ",
231
+ "Asia/Bangkok": "TH",
232
+ "Asia/Barnaul": "RU",
233
+ "Asia/Beirut": "LB",
234
+ "Asia/Bishkek": "KG",
235
+ "Asia/Brunei": "BN",
236
+ "Asia/Calcutta": "IN",
237
+ "Asia/Chita": "RU",
238
+ "Asia/Choibalsan": "MN",
239
+ "Asia/Colombo": "LK",
240
+ "Asia/Damascus": "SY",
241
+ "Asia/Dhaka": "BD",
242
+ "Asia/Dili": "TL",
243
+ "Asia/Dubai": "AE",
244
+ "Asia/Dushanbe": "TJ",
245
+ "Asia/Famagusta": "CY",
246
+ "Asia/Gaza": "PS",
247
+ "Asia/Hebron": "PS",
248
+ "Asia/Ho_Chi_Minh": "VN",
249
+ "Asia/Hong_Kong": "HK",
250
+ "Asia/Hovd": "MN",
251
+ "Asia/Irkutsk": "RU",
252
+ "Asia/Jakarta": "ID",
253
+ "Asia/Jayapura": "ID",
254
+ "Asia/Jerusalem": "IL",
255
+ "Asia/Kabul": "AF",
256
+ "Asia/Kamchatka": "RU",
257
+ "Asia/Karachi": "PK",
258
+ "Asia/Kathmandu": "NP",
259
+ "Asia/Khandyga": "RU",
260
+ "Asia/Kolkata": "IN",
261
+ "Asia/Krasnoyarsk": "RU",
262
+ "Asia/Kuala_Lumpur": "MY",
263
+ "Asia/Kuching": "MY",
264
+ "Asia/Kuwait": "KW",
265
+ "Asia/Macau": "MO",
266
+ "Asia/Magadan": "RU",
267
+ "Asia/Makassar": "ID",
268
+ "Asia/Manila": "PH",
269
+ "Asia/Muscat": "OM",
270
+ "Asia/Nicosia": "CY",
271
+ "Asia/Novokuznetsk": "RU",
272
+ "Asia/Novosibirsk": "RU",
273
+ "Asia/Omsk": "RU",
274
+ "Asia/Oral": "KZ",
275
+ "Asia/Phnom_Penh": "KH",
276
+ "Asia/Pontianak": "ID",
277
+ "Asia/Pyongyang": "KP",
278
+ "Asia/Qatar": "QA",
279
+ "Asia/Qostanay": "KZ",
280
+ "Asia/Qyzylorda": "KZ",
281
+ "Asia/Riyadh": "SA",
282
+ "Asia/Sakhalin": "RU",
283
+ "Asia/Samarkand": "UZ",
284
+ "Asia/Seoul": "KR",
285
+ "Asia/Shanghai": "CN",
286
+ "Asia/Singapore": "SG",
287
+ "Asia/Srednekolymsk": "RU",
288
+ "Asia/Taipei": "TW",
289
+ "Asia/Tashkent": "UZ",
290
+ "Asia/Tbilisi": "GE",
291
+ "Asia/Tehran": "IR",
292
+ "Asia/Thimphu": "BT",
293
+ "Asia/Tokyo": "JP",
294
+ "Asia/Tomsk": "RU",
295
+ "Asia/Ulaanbaatar": "MN",
296
+ "Asia/Urumqi": "CN",
297
+ "Asia/Ust-Nera": "RU",
298
+ "Asia/Vientiane": "LA",
299
+ "Asia/Vladivostok": "RU",
300
+ "Asia/Yakutsk": "RU",
301
+ "Asia/Yangon": "MM",
302
+ "Asia/Yekaterinburg": "RU",
303
+ "Asia/Yerevan": "AM",
304
+ "Atlantic/Azores": "PT",
305
+ "Atlantic/Bermuda": "BM",
306
+ "Atlantic/Canary": "ES",
307
+ "Atlantic/Cape_Verde": "CV",
308
+ "Atlantic/Faroe": "FO",
309
+ "Atlantic/Madeira": "PT",
310
+ "Atlantic/Reykjavik": "IS",
311
+ "Atlantic/South_Georgia": "GS",
312
+ "Atlantic/St_Helena": "SH",
313
+ "Atlantic/Stanley": "FK",
314
+ "Australia/Adelaide": "AU",
315
+ "Australia/Brisbane": "AU",
316
+ "Australia/Broken_Hill": "AU",
317
+ "Australia/Darwin": "AU",
318
+ "Australia/Eucla": "AU",
319
+ "Australia/Hobart": "AU",
320
+ "Australia/Lindeman": "AU",
321
+ "Australia/Lord_Howe": "AU",
322
+ "Australia/Melbourne": "AU",
323
+ "Australia/Perth": "AU",
324
+ "Australia/Sydney": "AU",
325
+ "Europe/Amsterdam": "NL",
326
+ "Europe/Andorra": "AD",
327
+ "Europe/Astrakhan": "RU",
328
+ "Europe/Athens": "GR",
329
+ "Europe/Belgrade": "RS",
330
+ "Europe/Berlin": "DE",
331
+ "Europe/Bratislava": "SK",
332
+ "Europe/Brussels": "BE",
333
+ "Europe/Bucharest": "RO",
334
+ "Europe/Budapest": "HU",
335
+ "Europe/Busingen": "DE",
336
+ "Europe/Chisinau": "MD",
337
+ "Europe/Copenhagen": "DK",
338
+ "Europe/Dublin": "IE",
339
+ "Europe/Gibraltar": "GI",
340
+ "Europe/Guernsey": "GG",
341
+ "Europe/Helsinki": "FI",
342
+ "Europe/Isle_of_Man": "IM",
343
+ "Europe/Istanbul": "TR",
344
+ "Europe/Jersey": "JE",
345
+ "Europe/Kaliningrad": "RU",
346
+ "Europe/Kiev": "UA",
347
+ "Europe/Kirov": "RU",
348
+ "Europe/Lisbon": "PT",
349
+ "Europe/Ljubljana": "SI",
350
+ "Europe/London": "GB",
351
+ "Europe/Luxembourg": "LU",
352
+ "Europe/Madrid": "ES",
353
+ "Europe/Malta": "MT",
354
+ "Europe/Mariehamn": "AX",
355
+ "Europe/Minsk": "BY",
356
+ "Europe/Monaco": "MC",
357
+ "Europe/Moscow": "RU",
358
+ "Europe/Oslo": "NO",
359
+ "Europe/Paris": "FR",
360
+ "Europe/Podgorica": "ME",
361
+ "Europe/Prague": "CZ",
362
+ "Europe/Riga": "LV",
363
+ "Europe/Rome": "IT",
364
+ "Europe/Samara": "RU",
365
+ "Europe/San_Marino": "SM",
366
+ "Europe/Sarajevo": "BA",
367
+ "Europe/Saratov": "RU",
368
+ "Europe/Simferopol": "UA",
369
+ "Europe/Skopje": "MK",
370
+ "Europe/Sofia": "BG",
371
+ "Europe/Stockholm": "SE",
372
+ "Europe/Tallinn": "EE",
373
+ "Europe/Tirane": "AL",
374
+ "Europe/Ulyanovsk": "RU",
375
+ "Europe/Uzhgorod": "UA",
376
+ "Europe/Vaduz": "LI",
377
+ "Europe/Vatican": "VA",
378
+ "Europe/Vienna": "AT",
379
+ "Europe/Vilnius": "LT",
380
+ "Europe/Volgograd": "RU",
381
+ "Europe/Warsaw": "PL",
382
+ "Europe/Zagreb": "HR",
383
+ "Europe/Zaporozhye": "UA",
384
+ "Europe/Zurich": "CH",
385
+ "Indian/Antananarivo": "MG",
386
+ "Indian/Chagos": "IO",
387
+ "Indian/Christmas": "CX",
388
+ "Indian/Cocos": "CC",
389
+ "Indian/Comoro": "KM",
390
+ "Indian/Kerguelen": "TF",
391
+ "Indian/Mahe": "SC",
392
+ "Indian/Maldives": "MV",
393
+ "Indian/Mauritius": "MU",
394
+ "Indian/Mayotte": "YT",
395
+ "Indian/Reunion": "RE",
396
+ "Pacific/Apia": "WS",
397
+ "Pacific/Auckland": "NZ",
398
+ "Pacific/Bougainville": "PG",
399
+ "Pacific/Chatham": "NZ",
400
+ "Pacific/Chuuk": "FM",
401
+ "Pacific/Easter": "CL",
402
+ "Pacific/Efate": "VU",
403
+ "Pacific/Fakaofo": "TK",
404
+ "Pacific/Fiji": "FJ",
405
+ "Pacific/Funafuti": "TV",
406
+ "Pacific/Galapagos": "EC",
407
+ "Pacific/Gambier": "PF",
408
+ "Pacific/Guadalcanal": "SB",
409
+ "Pacific/Guam": "GU",
410
+ "Pacific/Honolulu": "US",
411
+ "Pacific/Kanton": "KI",
412
+ "Pacific/Kiritimati": "KI",
413
+ "Pacific/Kosrae": "FM",
414
+ "Pacific/Kwajalein": "MH",
415
+ "Pacific/Majuro": "MH",
416
+ "Pacific/Marquesas": "PF",
417
+ "Pacific/Midway": "UM",
418
+ "Pacific/Nauru": "NR",
419
+ "Pacific/Niue": "NU",
420
+ "Pacific/Norfolk": "NF",
421
+ "Pacific/Noumea": "NC",
422
+ "Pacific/Pago_Pago": "AS",
423
+ "Pacific/Palau": "PW",
424
+ "Pacific/Pitcairn": "PN",
425
+ "Pacific/Pohnpei": "FM",
426
+ "Pacific/Port_Moresby": "PG",
427
+ "Pacific/Rarotonga": "CK",
428
+ "Pacific/Saipan": "MP",
429
+ "Pacific/Tahiti": "PF",
430
+ "Pacific/Tarawa": "KI",
431
+ "Pacific/Tongatapu": "TO",
432
+ "Pacific/Wake": "UM",
433
+ "Pacific/Wallis": "WF"
434
+ };
435
+
436
+ // src/lib/utils/domain.ts
437
+ function getHostname(url) {
438
+ if (!url) {
439
+ return "";
440
+ }
441
+ try {
442
+ return new URL(url).hostname;
443
+ } catch {
444
+ return "";
445
+ }
446
+ }
447
+
448
+ // src/lib/utils/logger.ts
449
+ var AnalyticsLogger = class {
450
+ config;
451
+ constructor(config) {
452
+ this.config = config;
453
+ }
454
+ debug(...args) {
455
+ if (this.config.level < 3 /* DEBUG */) {
456
+ return;
457
+ }
458
+ const icon = "\u25CD";
459
+ const message = `${icon} ${this.config.label}`;
460
+ console.debug(
461
+ `%c ${message}`,
462
+ `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`,
463
+ ...args
464
+ );
465
+ }
466
+ /**
467
+ * Log debug information
468
+ */
469
+ info(...args) {
470
+ if (this.config.level < 2 /* INFO */) {
471
+ return;
472
+ }
473
+ const icon = "\u25CD";
474
+ const message = `${icon} ${this.config.label}`;
475
+ console.info(
476
+ `%c ${message}`,
477
+ `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`,
478
+ ...args
479
+ );
480
+ }
481
+ /**
482
+ * Log warning information
483
+ */
484
+ warn(...args) {
485
+ if (this.config.level < 1 /* WARN */) {
486
+ return;
487
+ }
488
+ const icon = "\u25CD";
489
+ const message = `${icon} ${this.config.label}`;
490
+ console.warn(
491
+ `%c ${message}`,
492
+ `background: #ffa500; color: #000; padding-right: 6px; border-radius: 2px;`,
493
+ ...args
494
+ );
495
+ }
496
+ /**
497
+ * Log error information
498
+ */
499
+ error(...args) {
500
+ const icon = "\u25CD";
501
+ const message = `${icon} ${this.config.label} Error`;
502
+ console.error(
503
+ `%c ${message}`,
504
+ "background: red; color: white; padding-right: 6px; border-radius: 2px;",
505
+ ...args
506
+ );
507
+ return {
508
+ message: args.join(" "),
509
+ success: false
510
+ };
511
+ }
512
+ };
513
+
514
+ // src/lib/utils/getIntegrationLoggerLevel.ts
515
+ var isBrowser = typeof window !== "undefined";
516
+ var isServer = !isBrowser;
517
+ function getIntegrationLoggerLevel(integrationName, debug) {
518
+ let urlDebug = false;
519
+ if (!isServer) {
520
+ try {
521
+ const urlParams = new URLSearchParams(window.location.search);
522
+ const paramName = `saasco-debug-${integrationName}`;
523
+ const paramValue = urlParams.get(paramName);
524
+ if (paramValue === "true") {
525
+ urlDebug = true;
526
+ }
527
+ } catch {
528
+ }
529
+ }
530
+ return urlDebug || debug ? 3 /* DEBUG */ : 1 /* WARN */;
531
+ }
532
+
533
+ // src/lib/utils/uuid.ts
534
+ import { v4 } from "@lukeed/uuid";
535
+ function uuid() {
536
+ return v4();
537
+ }
538
+
539
+ // src/lib/getBrowserContext.ts
540
+ function getBrowserContext() {
541
+ const isBrowser6 = typeof window !== "undefined";
542
+ if (!isBrowser6) {
543
+ throw new Error("getBrowserContext can only be called in browser");
544
+ }
545
+ const customNavigator = navigator;
546
+ const $locale = customNavigator?.languages && customNavigator?.languages.length ? customNavigator?.languages[0] : customNavigator?.userLanguage || customNavigator?.language || customNavigator?.browserLanguage || "en";
547
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
548
+ const $location = timezones[timezone] || $locale?.split("-")?.[1] || "undefined";
549
+ const params = new URLSearchParams(window.location.search);
550
+ const { $utmAdId, $utmAdSource } = resolveAdParams(params);
551
+ return {
552
+ $href: window.location.href,
553
+ $locale,
554
+ $location,
555
+ $pathname: window.location.pathname,
556
+ $referrer: document.referrer,
557
+ $referringDomain: getHostname(document.referrer),
558
+ $screenDPI: window.devicePixelRatio,
559
+ $screenHeight: screen.height,
560
+ $screenWidth: screen.width,
561
+ $title: document.title,
562
+ $userAgent: window.navigator.userAgent,
563
+ $utmAdId,
564
+ $utmAdSource,
565
+ $utmCampaign: params.get("utm_campaign"),
566
+ $utmCampaignId: params.get("utm_campaign_id"),
567
+ $utmContent: params.get("utm_content"),
568
+ $utmCreativeFormat: params.get("utm_creative_format"),
569
+ $utmId: params.get("utm_id"),
570
+ $utmMarketingTactic: params.get("utm_marketing_tactic"),
571
+ $utmMedium: params.get("utm_medium"),
572
+ $utmSource: params.get("utm_source"),
573
+ $utmSourcePlatform: params.get("utm_source_platform"),
574
+ $utmTerm: params.get("utm_term")
575
+ };
576
+ }
577
+ var AD_NETWORK_PARAMS = [
578
+ { param: "fbadid", source: "facebook" },
579
+ { param: "gadid", source: "google" },
580
+ { param: "xadid", source: "x" },
581
+ { param: "padid", source: "pinterest" },
582
+ { param: "ttadid", source: "tiktok" },
583
+ { param: "scadid", source: "snapchat" },
584
+ { param: "radid", source: "reddit" },
585
+ { param: "liadid", source: "linkedin" }
586
+ ];
587
+ function resolveAdParams(params) {
588
+ let $utmAdSource = params.get("utm_ad_source");
589
+ let $utmAdId = params.get("utm_ad_id");
590
+ if ($utmAdId) {
591
+ return { $utmAdId, $utmAdSource };
592
+ }
593
+ for (const { param, source } of AD_NETWORK_PARAMS) {
594
+ const networkAdId = params.get(param);
595
+ if (!networkAdId) {
596
+ continue;
597
+ }
598
+ $utmAdId = networkAdId;
599
+ if (!$utmAdSource) {
600
+ $utmAdSource = source;
601
+ }
602
+ }
603
+ return { $utmAdId, $utmAdSource };
604
+ }
605
+
606
+ // src/lib/integrations/facebook-pixel.ts
607
+ import { sha256 } from "js-sha256";
608
+ var isBrowser2 = typeof window !== "undefined";
609
+ var isServer2 = !isBrowser2;
610
+ var standardFacebookEvents = [
611
+ "AddPaymentInfo",
612
+ "AddToCart",
613
+ "AddToWishlist",
614
+ "CompleteRegistration",
615
+ "Contact",
616
+ "CustomizeProduct",
617
+ "Donate",
618
+ "FindLocation",
619
+ "InitiateCheckout",
620
+ "Lead",
621
+ "Purchase",
622
+ "Schedule",
623
+ "Search",
624
+ "StartTrial",
625
+ "SubmitApplication",
626
+ "Subscribe",
627
+ "ViewContent",
628
+ "PageView"
629
+ ];
630
+ function createFacebookPixelIntegration(config, debug) {
631
+ let isPixelReady = false;
632
+ const integrationLoggerLevel = getIntegrationLoggerLevel(
633
+ "facebook-pixel",
634
+ debug
635
+ );
636
+ const logger = new AnalyticsLogger({
637
+ level: integrationLoggerLevel,
638
+ label: "Saasco facebook-pixel"
639
+ });
640
+ return {
641
+ name: "facebook-pixel",
642
+ environments: ["client"],
643
+ init: async () => {
644
+ if (isServer2) {
645
+ logger.debug("Facebook Pixel init skipped on server");
646
+ return;
647
+ }
648
+ logger.info("Initializing Facebook Pixel", { pixelId: config.pixelId });
649
+ if (window.fbq || window._fbq) {
650
+ logger.warn("Facebook Pixel is already initialized");
651
+ isPixelReady = true;
652
+ return;
653
+ }
654
+ return new Promise((resolve, reject) => {
655
+ try {
656
+ (function init(f, b, e, v, n, t, s) {
657
+ if (f.fbq) {
658
+ return;
659
+ }
660
+ n = f.fbq = function fbq2() {
661
+ n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
662
+ };
663
+ if (!f._fbq) {
664
+ f._fbq = n;
665
+ }
666
+ n.push = n;
667
+ n.loaded = true;
668
+ n.version = "2.0";
669
+ n.queue = [];
670
+ t = b.createElement(e);
671
+ t.async = true;
672
+ t.src = v;
673
+ t.addEventListener("load", () => {
674
+ isPixelReady = true;
675
+ logger.info("Facebook Pixel script loaded successfully");
676
+ resolve();
677
+ });
678
+ t.addEventListener("error", () => {
679
+ logger.error("Failed to load Facebook Pixel script");
680
+ reject(new Error("Failed to load Facebook Pixel script"));
681
+ });
682
+ s = b.getElementsByTagName(e)[0];
683
+ s.parentNode.insertBefore(t, s);
684
+ })(
685
+ window,
686
+ document,
687
+ "script",
688
+ "https://connect.facebook.net/en_US/fbevents.js"
689
+ );
690
+ const { fbq } = window;
691
+ if (!fbq) {
692
+ reject(new Error("Facebook Pixel failed to initialize"));
693
+ return;
694
+ }
695
+ fbq.disablePushState = true;
696
+ fbq.allowDuplicatePageViews = true;
697
+ if (config.automaticConfiguration === false) {
698
+ logger.debug("Disabling automatic configuration");
699
+ fbq("set", "autoConfig", false, config.pixelId);
700
+ }
701
+ logger.debug("Initializing Facebook Pixel with ID", config.pixelId);
702
+ fbq("init", config.pixelId);
703
+ if (typeof fbq === "function") {
704
+ isPixelReady = true;
705
+ logger.info("Facebook Pixel initialized synchronously");
706
+ resolve();
707
+ }
708
+ } catch (error) {
709
+ logger.error("Error during Facebook Pixel initialization", error);
710
+ reject(error);
711
+ }
712
+ });
713
+ },
714
+ track: (eventName, properties, _context) => {
715
+ const { fbq } = window;
716
+ if (!isPixelReady || !fbq) {
717
+ logger.warn("Facebook Pixel not ready, skipping track event", {
718
+ eventName
719
+ });
720
+ return;
721
+ }
722
+ const fbEventName = getFacebookEventName(eventName, config.eventMapping);
723
+ const eventsWithParams = ["Purchase", "StartTrial", "Subscribe"];
724
+ const isStandardEvent = standardFacebookEvents.includes(
725
+ fbEventName
726
+ );
727
+ const trackType = isStandardEvent ? "track" : "trackCustom";
728
+ logger.debug("Tracking Facebook Pixel event", {
729
+ originalEvent: eventName,
730
+ fbEventName,
731
+ trackType,
732
+ isStandardEvent,
733
+ properties
734
+ });
735
+ if (eventsWithParams.includes(fbEventName) && properties) {
736
+ const fbParams = {};
737
+ if (properties["value"] !== void 0) {
738
+ fbParams["value"] = properties["value"];
739
+ }
740
+ if (properties["currency"] !== void 0) {
741
+ fbParams["currency"] = properties["currency"];
742
+ }
743
+ if (properties["predicted_ltv"] !== void 0) {
744
+ fbParams["predicted_ltv"] = properties["predicted_ltv"];
745
+ }
746
+ logger.info("Tracking Facebook Pixel event with parameters", {
747
+ event: fbEventName,
748
+ params: fbParams
749
+ });
750
+ fbq(trackType, fbEventName, fbParams);
751
+ } else {
752
+ logger.info("Tracking Facebook Pixel event", { event: fbEventName });
753
+ fbq(trackType, fbEventName);
754
+ }
755
+ },
756
+ identify: (userId, properties, _context) => {
757
+ try {
758
+ if (typeof window !== "undefined" && window.fbq) {
759
+ const amValues = coerceMetaConversionsAmValues({
760
+ ...properties,
761
+ ...userId ? { external_id: userId } : {}
762
+ });
763
+ const am = {};
764
+ if (amValues.external_id) {
765
+ am["external_id"] = amValues.external_id;
766
+ }
767
+ if (amValues.em) {
768
+ am["em"] = sha256(amValues.em);
769
+ }
770
+ if (amValues.fn) {
771
+ am["fn"] = amValues.fn;
772
+ }
773
+ if (amValues.ln) {
774
+ am["ln"] = amValues.ln;
775
+ }
776
+ if (amValues.ph) {
777
+ am["ph"] = amValues.ph;
778
+ }
779
+ if (amValues.ge) {
780
+ am["ge"] = amValues.ge;
781
+ }
782
+ if (amValues.db) {
783
+ am["db"] = amValues.db;
784
+ }
785
+ if (amValues.ct) {
786
+ am["ct"] = amValues.ct;
787
+ }
788
+ if (amValues.st) {
789
+ am["st"] = amValues.st;
790
+ }
791
+ if (amValues.zp) {
792
+ am["zp"] = amValues.zp;
793
+ }
794
+ if (amValues.country) {
795
+ am["country"] = amValues.country;
796
+ }
797
+ if (Object.keys(am).length > 0) {
798
+ window.fbq("init", config.pixelId, am);
799
+ }
800
+ }
801
+ } catch (error) {
802
+ console.error("Error identifying user in Meta Pixel:", error);
803
+ }
804
+ }
805
+ };
806
+ }
807
+ function getFacebookEventName(eventName, eventMapping) {
808
+ if (eventName === "Page View") {
809
+ return "PageView";
810
+ }
811
+ if (!eventMapping) {
812
+ return eventName;
813
+ }
814
+ return eventMapping[eventName] || eventName;
815
+ }
816
+ function coerceMetaConversionsAmValues(properties) {
817
+ const coerce = [
818
+ {
819
+ key: "em",
820
+ coerceFrom: [
821
+ "email",
822
+ "user_email",
823
+ "userEmail",
824
+ "email_address",
825
+ "emailAddress",
826
+ "e_mail",
827
+ "E_Mail",
828
+ "mail",
829
+ "Mail",
830
+ "contact_email",
831
+ "contactEmail",
832
+ "primary_email",
833
+ "primaryEmail"
834
+ ],
835
+ transform: (value) => {
836
+ if (typeof value !== "string") {
837
+ return;
838
+ }
839
+ const normalized = value.trim().toLowerCase();
840
+ return normalized || void 0;
841
+ }
842
+ },
843
+ {
844
+ key: "fn",
845
+ coerceFrom: [
846
+ "first_name",
847
+ "firstName",
848
+ "firstname",
849
+ "given_name",
850
+ "givenName",
851
+ "user_first_name",
852
+ "userFirstName",
853
+ "name_first",
854
+ "nameFirst",
855
+ "f_name",
856
+ "fName"
857
+ ],
858
+ transform: (value) => {
859
+ if (typeof value !== "string") {
860
+ return;
861
+ }
862
+ const normalized = value.trim().toLowerCase();
863
+ return normalized || void 0;
864
+ }
865
+ },
866
+ {
867
+ key: "ln",
868
+ coerceFrom: [
869
+ "last_name",
870
+ "lastName",
871
+ "lastname",
872
+ "surname",
873
+ "user_last_name",
874
+ "userLastName",
875
+ "name_last",
876
+ "nameLast",
877
+ "l_name",
878
+ "lName"
879
+ ],
880
+ transform: (value) => {
881
+ if (typeof value !== "string") {
882
+ return;
883
+ }
884
+ const normalized = value.trim().toLowerCase();
885
+ return normalized || void 0;
886
+ }
887
+ },
888
+ {
889
+ key: "ph",
890
+ coerceFrom: [
891
+ "phone",
892
+ "phone_number",
893
+ "phoneNumber",
894
+ "mobile",
895
+ "mobile_number",
896
+ "mobileNumber",
897
+ "contact_number",
898
+ "contactNumber",
899
+ "tel",
900
+ "telephone"
901
+ ],
902
+ transform: (value) => {
903
+ if (typeof value !== "string" && typeof value !== "number") {
904
+ return;
905
+ }
906
+ const phoneStr = String(value).replaceAll(/\D/gu, "");
907
+ return phoneStr || void 0;
908
+ }
909
+ },
910
+ {
911
+ key: "external_id",
912
+ coerceFrom: [
913
+ "external_id",
914
+ "externalId",
915
+ "user_id",
916
+ "userId",
917
+ "id",
918
+ "distinctId",
919
+ "distinct_id",
920
+ "customer_id",
921
+ "customerId",
922
+ "member_id",
923
+ "memberId",
924
+ "loyalty_id",
925
+ "loyaltyId"
926
+ ],
927
+ transform: (value) => {
928
+ if (value === null || value === void 0) {
929
+ return;
930
+ }
931
+ return String(value) || void 0;
932
+ }
933
+ },
934
+ {
935
+ key: "ge",
936
+ coerceFrom: [
937
+ "gender",
938
+ "user_gender",
939
+ "userGender",
940
+ "sex",
941
+ "user_sex",
942
+ "userSex"
943
+ ],
944
+ transform: (value) => {
945
+ if (typeof value !== "string") {
946
+ return;
947
+ }
948
+ const normalized = value.trim().toLowerCase();
949
+ if (normalized === "female" || normalized === "f") {
950
+ return "f";
951
+ }
952
+ if (normalized === "male" || normalized === "m") {
953
+ return "m";
954
+ }
955
+ }
956
+ },
957
+ {
958
+ key: "db",
959
+ coerceFrom: [
960
+ "birthday",
961
+ "birth_date",
962
+ "birthDate",
963
+ "date_of_birth",
964
+ "dateOfBirth",
965
+ "dob",
966
+ "Dob",
967
+ "birth_day",
968
+ "birthDay"
969
+ ],
970
+ transform: (value) => {
971
+ if (!value) {
972
+ return;
973
+ }
974
+ const dateStr = value instanceof Date ? value.toISOString().split("T")[0] : String(value);
975
+ const dateMatch = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/u);
976
+ if (dateMatch) {
977
+ const [, year, month, day] = dateMatch;
978
+ return `${year}${month}${day}`;
979
+ }
980
+ const numericMatch = dateStr.replaceAll(/\D/gu, "");
981
+ if (numericMatch.length === 8) {
982
+ return numericMatch;
983
+ }
984
+ }
985
+ },
986
+ {
987
+ key: "ct",
988
+ coerceFrom: [
989
+ "city",
990
+ "user_city",
991
+ "userCity",
992
+ "location_city",
993
+ "locationCity",
994
+ "address_city",
995
+ "addressCity"
996
+ ],
997
+ transform: (value) => {
998
+ if (typeof value !== "string") {
999
+ return;
1000
+ }
1001
+ const normalized = value.trim().toLowerCase().replaceAll(/\s+/gu, "");
1002
+ return normalized || void 0;
1003
+ }
1004
+ },
1005
+ {
1006
+ key: "st",
1007
+ coerceFrom: [
1008
+ "state",
1009
+ "user_state",
1010
+ "userState",
1011
+ "province",
1012
+ "user_province",
1013
+ "userProvince",
1014
+ "region",
1015
+ "user_region",
1016
+ "userRegion",
1017
+ "location_state",
1018
+ "locationState",
1019
+ "address_state",
1020
+ "addressState"
1021
+ ],
1022
+ transform: (value) => {
1023
+ if (typeof value !== "string") {
1024
+ return;
1025
+ }
1026
+ const normalized = value.trim().toLowerCase();
1027
+ if (normalized.length === 2) {
1028
+ return normalized;
1029
+ }
1030
+ }
1031
+ },
1032
+ {
1033
+ key: "zp",
1034
+ coerceFrom: [
1035
+ "zip",
1036
+ "zipcode",
1037
+ "zip_code",
1038
+ "postal_code",
1039
+ "postalCode",
1040
+ "postcode",
1041
+ "user_zip",
1042
+ "userZip",
1043
+ "address_zip",
1044
+ "addressZip"
1045
+ ],
1046
+ transform: (value) => {
1047
+ if (typeof value !== "string" && typeof value !== "number") {
1048
+ return;
1049
+ }
1050
+ return String(value).trim() || void 0;
1051
+ }
1052
+ },
1053
+ {
1054
+ key: "country",
1055
+ coerceFrom: [
1056
+ "country",
1057
+ "country_code",
1058
+ "countryCode",
1059
+ "user_country",
1060
+ "userCountry",
1061
+ "location_country",
1062
+ "locationCountry",
1063
+ "address_country",
1064
+ "addressCountry"
1065
+ ],
1066
+ transform: (value) => {
1067
+ if (typeof value !== "string") {
1068
+ return;
1069
+ }
1070
+ const normalized = value.trim().toLowerCase();
1071
+ if (normalized.length === 2) {
1072
+ return normalized;
1073
+ }
1074
+ }
1075
+ }
1076
+ ];
1077
+ const result = {};
1078
+ for (const { key, coerceFrom, transform } of coerce) {
1079
+ const foundValue = findFirstProperty(properties, coerceFrom);
1080
+ if (foundValue !== null && transform) {
1081
+ const transformedValue = transform(foundValue);
1082
+ if (transformedValue) {
1083
+ result[key] = transformedValue;
1084
+ }
1085
+ } else if (foundValue !== null && typeof foundValue === "string") {
1086
+ result[key] = foundValue;
1087
+ }
1088
+ }
1089
+ return result;
1090
+ }
1091
+ function findFirstProperty(properties, keys) {
1092
+ for (const key of keys) {
1093
+ if (properties[key] !== void 0) {
1094
+ return properties[key];
1095
+ }
1096
+ }
1097
+ return null;
1098
+ }
1099
+
1100
+ // src/lib/integrations/integration-manager.ts
1101
+ var IntegrationManager = class {
1102
+ context = {};
1103
+ integrations = /* @__PURE__ */ new Map();
1104
+ globalQueue = [];
1105
+ config;
1106
+ logger;
1107
+ initTime;
1108
+ currentEnvironment;
1109
+ flushTimer;
1110
+ unloadHandler;
1111
+ constructor(config = {}) {
1112
+ this.config = {
1113
+ flushInterval: config.flushInterval ?? 5e3,
1114
+ loggerLevel: config.loggerLevel ?? 0 /* ERROR */,
1115
+ maxIntegrationWaitTime: config.maxIntegrationWaitTime ?? 1e4,
1116
+ maxQueueSize: config.maxQueueSize ?? 200
1117
+ };
1118
+ this.logger = new AnalyticsLogger({
1119
+ label: "Saasco Integrations Debug",
1120
+ level: this.config.loggerLevel
1121
+ });
1122
+ this.initTime = Date.now();
1123
+ this.currentEnvironment = typeof window === "undefined" ? "server" : "client";
1124
+ this.setupPeriodicFlushing();
1125
+ this.setupUnloadHandler();
1126
+ }
1127
+ /**
1128
+ * Shallow-merge context to keep it simple + predictable in v0
1129
+ */
1130
+ setContext(next) {
1131
+ this.context = { ...this.context, ...next };
1132
+ if (Object.keys(this.context).length > 0) {
1133
+ this.logger.info("setContext", this.context);
1134
+ }
1135
+ }
1136
+ /**
1137
+ * Register and init an integration. When init resolves, we mark it ready and
1138
+ * immediately flush any queued events in FIFO order to *all* ready integrations.
1139
+ */
1140
+ async registerIntegration(integration) {
1141
+ this.logger.debug("Register integrations", this.integrations);
1142
+ if (this.integrations.has(integration.name)) {
1143
+ this.logger.warn(`Integration already registered: ${integration.name}`);
1144
+ return;
1145
+ }
1146
+ const isCompatible = integration.environments.includes(
1147
+ this.currentEnvironment
1148
+ );
1149
+ if (!isCompatible) {
1150
+ this.logger.warn(
1151
+ `Skipping ${integration.name}: requires ${integration.environments.join(
1152
+ " or "
1153
+ )} but running in ${this.currentEnvironment}`
1154
+ );
1155
+ return;
1156
+ }
1157
+ this.logger.debug(`Registering ${integration.name}`);
1158
+ const state = {
1159
+ integration,
1160
+ status: "idle"
1161
+ };
1162
+ this.integrations.set(integration.name, state);
1163
+ if (integration.init) {
1164
+ try {
1165
+ state.status = "loading";
1166
+ const startTime = Date.now();
1167
+ this.logger.debug(`Initializing ${integration.name}`);
1168
+ await integration.init(this.context);
1169
+ state.status = "ready";
1170
+ const endTime = Date.now();
1171
+ const duration = endTime - startTime;
1172
+ this.logger.debug(
1173
+ `Integration ${integration.name} ready in ${duration}ms`
1174
+ );
1175
+ this.flush();
1176
+ } catch (error) {
1177
+ state.status = "error";
1178
+ this.logger.error(`Failed to init ${integration.name}`, error);
1179
+ }
1180
+ } else {
1181
+ state.status = "ready";
1182
+ this.logger.info(`Integration ${integration.name} ready (no init)`);
1183
+ this.flush();
1184
+ }
1185
+ }
1186
+ identify(userId, traits) {
1187
+ this.setContext({ userId });
1188
+ this.send({ name: void 0, properties: traits, type: "identify" });
1189
+ }
1190
+ track(name, properties) {
1191
+ this.send({ name, properties, type: "track" });
1192
+ }
1193
+ /**
1194
+ * Core send path: if at least one integration is ready -> deliver immediately
1195
+ * Else enqueue (bounded FIFO)
1196
+ */
1197
+ send(partial) {
1198
+ const envelope = {
1199
+ context: { ...this.context },
1200
+ id: uuid(),
1201
+ name: partial.name,
1202
+ properties: partial.properties,
1203
+ timestamp: Date.now(),
1204
+ type: partial.type
1205
+ };
1206
+ if (!this.isReady()) {
1207
+ if (this.globalQueue.length >= this.config.maxQueueSize) {
1208
+ this.globalQueue.shift();
1209
+ this.logger.warn("Queue full \u2192 dropped oldest");
1210
+ }
1211
+ this.globalQueue.push(envelope);
1212
+ this.logger.debug(`Queued (${this.globalQueue.length})`, envelope);
1213
+ return;
1214
+ }
1215
+ this.deliver(envelope);
1216
+ }
1217
+ async deliver(evt) {
1218
+ for (const { integration, status } of this.integrations.values()) {
1219
+ if (status !== "ready") {
1220
+ continue;
1221
+ }
1222
+ try {
1223
+ if (evt.type === "track" && integration.track && evt.name) {
1224
+ this.logger.debug(`Track (${integration.name}) ${evt.name}`, {
1225
+ context: evt.context,
1226
+ properties: evt.properties
1227
+ });
1228
+ await integration.track(evt.name, evt.properties, evt.context);
1229
+ } else if (evt.type === "identify" && integration.identify) {
1230
+ this.logger.debug(`Identify (${integration.name})`, {
1231
+ context: evt.context,
1232
+ properties: evt.properties,
1233
+ userId: evt.context.distinctId
1234
+ });
1235
+ await integration.identify(
1236
+ evt.context.distinctId,
1237
+ evt.properties,
1238
+ evt.context
1239
+ );
1240
+ }
1241
+ } catch (error) {
1242
+ this.logger.error(
1243
+ `deliver error in ${integration.name} (${evt.type})`,
1244
+ error
1245
+ );
1246
+ }
1247
+ }
1248
+ }
1249
+ /**
1250
+ * Flush queued events FIFO once at least one integration is ready.
1251
+ */
1252
+ flush() {
1253
+ if (this.globalQueue.length === 0) {
1254
+ return;
1255
+ }
1256
+ if (!this.isReady()) {
1257
+ return;
1258
+ }
1259
+ this.logger.debug(`flushing ${this.globalQueue.length} queued events`);
1260
+ const toSend = this.globalQueue;
1261
+ this.globalQueue = [];
1262
+ for (const evt of toSend) {
1263
+ this.deliver(evt);
1264
+ }
1265
+ }
1266
+ readyCount() {
1267
+ let n = 0;
1268
+ for (const s of this.integrations.values()) {
1269
+ if (s.status === "ready") {
1270
+ n += 1;
1271
+ }
1272
+ }
1273
+ return n;
1274
+ }
1275
+ isReady() {
1276
+ const timeElapsed = Date.now() - this.initTime;
1277
+ if (this.readyCount() < this.integrations.size && timeElapsed < this.config.maxIntegrationWaitTime) {
1278
+ return false;
1279
+ }
1280
+ if (this.readyCount() < this.integrations.size) {
1281
+ this.logger.info(
1282
+ `proceeding with ${this.readyCount()}/${this.integrations.size} integrations ready after ${timeElapsed}ms wait`
1283
+ );
1284
+ }
1285
+ return true;
1286
+ }
1287
+ /**
1288
+ * Setup periodic flushing if enabled
1289
+ */
1290
+ setupPeriodicFlushing() {
1291
+ if (this.config.flushInterval <= 0) {
1292
+ return;
1293
+ }
1294
+ this.flushTimer = setInterval(() => {
1295
+ if (this.globalQueue.length > 0) {
1296
+ this.flush();
1297
+ }
1298
+ }, this.config.flushInterval);
1299
+ }
1300
+ /**
1301
+ * Setup page unload handler for client environment
1302
+ */
1303
+ setupUnloadHandler() {
1304
+ if (this.currentEnvironment !== "client" || typeof window === "undefined") {
1305
+ return;
1306
+ }
1307
+ this.unloadHandler = () => {
1308
+ this.logger.info("page unloading, flushing remaining events");
1309
+ this.flush();
1310
+ };
1311
+ window.addEventListener("beforeunload", this.unloadHandler);
1312
+ window.addEventListener("pagehide", this.unloadHandler);
1313
+ }
1314
+ /** Debug helpers */
1315
+ getStats() {
1316
+ return {
1317
+ context: this.context,
1318
+ currentEnvironment: this.currentEnvironment,
1319
+ flushInterval: this.config.flushInterval,
1320
+ integrations: [...this.integrations.values()].map((s) => ({
1321
+ environments: s.integration.environments,
1322
+ name: s.integration.name,
1323
+ status: s.status
1324
+ })),
1325
+ periodicFlushEnabled: this.config.flushInterval > 0,
1326
+ queueLength: this.globalQueue.length,
1327
+ readyCount: this.readyCount()
1328
+ };
1329
+ }
1330
+ };
1331
+
1332
+ // src/lib/integrations/pinterest-tag.ts
1333
+ var isBrowser3 = typeof window !== "undefined";
1334
+ var isServer3 = !isBrowser3;
1335
+ var standardPinterestEvents = [
1336
+ "checkout",
1337
+ "addtocart",
1338
+ "pagevisit",
1339
+ "signup",
1340
+ "watchvideo",
1341
+ "lead",
1342
+ "search",
1343
+ "viewcategory",
1344
+ "custom",
1345
+ "addpaymentinfo",
1346
+ "addtowishlist",
1347
+ "initiatecheckout",
1348
+ "subscribe",
1349
+ "viewcontent"
1350
+ ];
1351
+ function createPinterestTagIntegration(config, debug) {
1352
+ let isTagReady = false;
1353
+ const integrationLoggerLevel = getIntegrationLoggerLevel(
1354
+ "pinterest-tag",
1355
+ debug
1356
+ );
1357
+ const logger = new AnalyticsLogger({
1358
+ level: integrationLoggerLevel,
1359
+ label: "Saasco Pinterest Tag"
1360
+ });
1361
+ return {
1362
+ name: "pinterest-tag",
1363
+ environments: ["client"],
1364
+ init: async (_context) => {
1365
+ if (isServer3) {
1366
+ logger.debug("Pinterest Tag init skipped on server");
1367
+ return;
1368
+ }
1369
+ logger.info("Initializing Pinterest Tag", { tagId: config.tagId });
1370
+ if (window.pintrk || window._pintrk) {
1371
+ logger.warn("Pinterest Tag is already initialized");
1372
+ isTagReady = true;
1373
+ return;
1374
+ }
1375
+ return new Promise((resolve, reject) => {
1376
+ try {
1377
+ (function init(e) {
1378
+ if (!window.pintrk) {
1379
+ window.pintrk = function pintrk(...args) {
1380
+ window.pintrk.queue.push(args);
1381
+ };
1382
+ const n = window.pintrk;
1383
+ n.queue = [];
1384
+ n.version = "3.0";
1385
+ const t = document.createElement("script");
1386
+ t.async = true;
1387
+ t.src = e;
1388
+ t.addEventListener("load", () => {
1389
+ window.pintrk("load", config.tagId);
1390
+ window.pintrk("page");
1391
+ setTimeout(() => {
1392
+ logger.debug("Pinterest Tag initialized");
1393
+ isTagReady = true;
1394
+ resolve();
1395
+ }, 500);
1396
+ });
1397
+ t.addEventListener("error", () => {
1398
+ logger.error("Failed to load Pinterest Tag script");
1399
+ reject(new Error("Failed to load Pinterest Tag script"));
1400
+ });
1401
+ const r = document.getElementsByTagName("script")[0];
1402
+ r.parentNode.insertBefore(t, r);
1403
+ }
1404
+ })("https://s.pinimg.com/ct/core.js");
1405
+ } catch (error) {
1406
+ logger.error("Failed to initialize Pinterest Tag", error);
1407
+ reject(error);
1408
+ }
1409
+ });
1410
+ },
1411
+ track: (eventName, properties) => {
1412
+ if (!isTagReady || !window.pintrk) {
1413
+ return;
1414
+ }
1415
+ const pinterestEventName = getPinterestEventName(
1416
+ eventName,
1417
+ config.eventMapping
1418
+ );
1419
+ const eventData = buildEventData(properties);
1420
+ logger.debug("Tracking event", {
1421
+ eventName,
1422
+ pinterestEventName,
1423
+ eventData
1424
+ });
1425
+ window.pintrk("track", pinterestEventName, eventData);
1426
+ },
1427
+ identify: (userId, properties, context) => {
1428
+ const { pintrk } = window;
1429
+ if (!pintrk) {
1430
+ return;
1431
+ }
1432
+ logger.debug("Applying identify to Pinterest Tag", {
1433
+ userId,
1434
+ properties,
1435
+ context
1436
+ });
1437
+ pintrk("set", { external_id: userId });
1438
+ pintrk("set", { em: properties?.["email"] });
1439
+ }
1440
+ };
1441
+ }
1442
+ function getPinterestEventName(eventName, eventMapping) {
1443
+ if (eventName === "Page View") {
1444
+ return "pagevisit";
1445
+ }
1446
+ if (!eventMapping) {
1447
+ return eventName.toLowerCase();
1448
+ }
1449
+ return eventMapping[eventName] || eventName.toLowerCase();
1450
+ }
1451
+ function buildEventData(properties) {
1452
+ if (!properties) {
1453
+ return {};
1454
+ }
1455
+ const eventData = {};
1456
+ if (properties["event_id"]) {
1457
+ eventData["event_id"] = properties["event_id"];
1458
+ }
1459
+ if (properties["value"]) {
1460
+ eventData["value"] = properties["value"];
1461
+ }
1462
+ if (properties["currency"]) {
1463
+ eventData["currency"] = properties["currency"];
1464
+ }
1465
+ if (properties["order_quantity"]) {
1466
+ eventData["order_quantity"] = properties["order_quantity"];
1467
+ }
1468
+ if (properties["order_id"]) {
1469
+ eventData["order_id"] = properties["order_id"];
1470
+ }
1471
+ if (properties["promo_code"]) {
1472
+ eventData["promo_code"] = properties["promo_code"];
1473
+ }
1474
+ if (properties["property"]) {
1475
+ eventData["property"] = properties["property"];
1476
+ }
1477
+ if (properties["search_query"]) {
1478
+ eventData["search_query"] = properties["search_query"];
1479
+ }
1480
+ if (properties["video_title"]) {
1481
+ eventData["video_title"] = properties["video_title"];
1482
+ }
1483
+ if (properties["lead_type"]) {
1484
+ eventData["lead_type"] = properties["lead_type"];
1485
+ }
1486
+ if (properties["product_category"]) {
1487
+ eventData["product_category"] = properties["product_category"];
1488
+ }
1489
+ if (properties["line_items"] && Array.isArray(properties["line_items"])) {
1490
+ eventData["line_items"] = properties["line_items"];
1491
+ }
1492
+ if (!eventData["event_id"]) {
1493
+ eventData["event_id"] = uuid();
1494
+ }
1495
+ return eventData;
1496
+ }
1497
+
1498
+ // src/lib/integrations/tiktok-pixel.ts
1499
+ import { sha256 as sha2562 } from "js-sha256";
1500
+ var isBrowser4 = typeof window !== "undefined";
1501
+ var isServer4 = !isBrowser4;
1502
+ var standardTikTokEvents = [
1503
+ "AddPaymentInfo",
1504
+ "AddToCart",
1505
+ "AddToWishlist",
1506
+ "ApplicationApproval",
1507
+ "CompleteRegistration",
1508
+ "Contact",
1509
+ "CustomizeProduct",
1510
+ "Download",
1511
+ "FindLocation",
1512
+ "InitiateCheckout",
1513
+ "Lead",
1514
+ "Purchase",
1515
+ "Schedule",
1516
+ "Search",
1517
+ "StartTrial",
1518
+ "SubmitApplication",
1519
+ "Subscribe",
1520
+ "ViewContent",
1521
+ "PageView"
1522
+ ];
1523
+ function createTikTokPixelIntegration(config, debug) {
1524
+ let isPixelReady = false;
1525
+ const integrationLoggerLevel = getIntegrationLoggerLevel(
1526
+ "tiktok-pixel",
1527
+ debug
1528
+ );
1529
+ const logger = new AnalyticsLogger({
1530
+ level: integrationLoggerLevel,
1531
+ label: "Saasco tiktok-pixel"
1532
+ });
1533
+ return {
1534
+ name: "tiktok-pixel",
1535
+ environments: ["client"],
1536
+ init: async () => {
1537
+ if (isServer4) {
1538
+ logger.debug("TikTok Pixel init skipped on server");
1539
+ return;
1540
+ }
1541
+ logger.info("Initializing TikTok Pixel", { pixelId: config.pixelId });
1542
+ if (window.ttq) {
1543
+ logger.warn("TikTok Pixel is already initialized");
1544
+ isPixelReady = true;
1545
+ return;
1546
+ }
1547
+ return new Promise((resolve, reject) => {
1548
+ try {
1549
+ (function init(w, d, t) {
1550
+ w.TiktokAnalyticsObject = t;
1551
+ const ttq = w[t] ||= [];
1552
+ ttq.methods = [
1553
+ "page",
1554
+ "track",
1555
+ "identify",
1556
+ "instances",
1557
+ "debug",
1558
+ "on",
1559
+ "off",
1560
+ "once",
1561
+ "ready",
1562
+ "alias",
1563
+ "group",
1564
+ "enableCookie",
1565
+ "disableCookie",
1566
+ "holdConsent",
1567
+ "revokeConsent",
1568
+ "grantConsent"
1569
+ ];
1570
+ ttq.setAndDefer = function setAndDefer(t2, e) {
1571
+ t2[e] = function(...args) {
1572
+ t2.push([e, ...args]);
1573
+ };
1574
+ };
1575
+ for (const method of ttq.methods) {
1576
+ ttq.setAndDefer(ttq, method);
1577
+ }
1578
+ ttq.instance = function instance(t2) {
1579
+ const e = ttq._i[t2] || [];
1580
+ for (const method of ttq.methods) {
1581
+ ttq.setAndDefer(e, method);
1582
+ }
1583
+ return e;
1584
+ };
1585
+ ttq.load = function load(e, n) {
1586
+ const r = "https://analytics.tiktok.com/i18n/pixel/events.js";
1587
+ const _o = n && n.partner;
1588
+ ttq._i ||= {};
1589
+ ttq._i[e] = [];
1590
+ ttq._i[e]._u = r;
1591
+ ttq._t ||= {};
1592
+ ttq._t[e] = Date.now();
1593
+ ttq._o ||= {};
1594
+ ttq._o[e] = n || {};
1595
+ const script = document.createElement("script");
1596
+ script.type = "text/javascript";
1597
+ script.async = true;
1598
+ script.src = `${r}?sdkid=${e}&lib=${t}`;
1599
+ script.addEventListener("load", () => {
1600
+ isPixelReady = true;
1601
+ resolve();
1602
+ });
1603
+ script.addEventListener("error", () => {
1604
+ reject(new Error("Failed to load TikTok Pixel script"));
1605
+ });
1606
+ const firstScript = document.getElementsByTagName("script")[0];
1607
+ firstScript.parentNode.insertBefore(script, firstScript);
1608
+ };
1609
+ ttq.load(
1610
+ config.pixelId,
1611
+ config.testMode ? { test_mode: true } : void 0
1612
+ );
1613
+ if (window.ttq && typeof window.ttq === "function") {
1614
+ isPixelReady = true;
1615
+ resolve();
1616
+ }
1617
+ })(window, document, "ttq");
1618
+ } catch (error) {
1619
+ reject(error);
1620
+ }
1621
+ });
1622
+ },
1623
+ track: (eventName, properties, _context) => {
1624
+ const { ttq } = window;
1625
+ if (!isPixelReady || !ttq) {
1626
+ return;
1627
+ }
1628
+ const ttEventName = getTikTokEventName(eventName, config.eventMapping);
1629
+ if (ttEventName === "PageView") {
1630
+ ttq.page?.();
1631
+ return;
1632
+ }
1633
+ const ttParams = mapTikTokEventParameters(ttEventName, properties);
1634
+ if (ttParams && Object.keys(ttParams).length > 0) {
1635
+ ttq.track?.(ttEventName, ttParams);
1636
+ } else {
1637
+ ttq.track?.(ttEventName);
1638
+ }
1639
+ },
1640
+ identify: (userId, properties, _context) => {
1641
+ try {
1642
+ const ttq = typeof window === "undefined" ? void 0 : window.ttq;
1643
+ if (ttq) {
1644
+ const identifyData = coerceTikTokIdentifyValues({
1645
+ ...properties,
1646
+ ...userId ? { external_id: userId } : {}
1647
+ });
1648
+ if (Object.keys(identifyData).length > 0) {
1649
+ ttq.identify?.(identifyData);
1650
+ }
1651
+ }
1652
+ } catch (error) {
1653
+ console.error("Error identifying user in TikTok Pixel:", error);
1654
+ }
1655
+ }
1656
+ };
1657
+ }
1658
+ function getTikTokEventName(eventName, eventMapping) {
1659
+ if (eventName === "Page View") {
1660
+ return "PageView";
1661
+ }
1662
+ if (!eventMapping) {
1663
+ return eventName;
1664
+ }
1665
+ return eventMapping[eventName] || eventName;
1666
+ }
1667
+ function coerceTikTokIdentifyValues(properties) {
1668
+ const coerce = [
1669
+ {
1670
+ key: "email",
1671
+ coerceFrom: [
1672
+ "email",
1673
+ "user_email",
1674
+ "userEmail",
1675
+ "email_address",
1676
+ "emailAddress",
1677
+ "e_mail",
1678
+ "E_Mail",
1679
+ "mail",
1680
+ "Mail",
1681
+ "contact_email",
1682
+ "contactEmail",
1683
+ "primary_email",
1684
+ "primaryEmail"
1685
+ ],
1686
+ transform: (value) => {
1687
+ if (typeof value !== "string") {
1688
+ return;
1689
+ }
1690
+ const normalized = value.trim().toLowerCase();
1691
+ return normalized ? sha2562(normalized) : void 0;
1692
+ }
1693
+ },
1694
+ {
1695
+ key: "phone_number",
1696
+ coerceFrom: [
1697
+ "phone",
1698
+ "phone_number",
1699
+ "phoneNumber",
1700
+ "mobile",
1701
+ "mobile_number",
1702
+ "mobileNumber",
1703
+ "contact_number",
1704
+ "contactNumber",
1705
+ "tel",
1706
+ "telephone"
1707
+ ],
1708
+ transform: (value) => {
1709
+ if (typeof value !== "string" && typeof value !== "number") {
1710
+ return;
1711
+ }
1712
+ const phoneStr = String(value).replaceAll(/\D/gu, "");
1713
+ return phoneStr ? sha2562(phoneStr) : void 0;
1714
+ }
1715
+ },
1716
+ {
1717
+ key: "external_id",
1718
+ coerceFrom: [
1719
+ "external_id",
1720
+ "externalId",
1721
+ "user_id",
1722
+ "userId",
1723
+ "id",
1724
+ "distinctId",
1725
+ "distinct_id",
1726
+ "customer_id",
1727
+ "customerId",
1728
+ "member_id",
1729
+ "memberId"
1730
+ ],
1731
+ transform: (value) => {
1732
+ if (value === null || value === void 0) {
1733
+ return;
1734
+ }
1735
+ const stringValue = String(value);
1736
+ return stringValue ? sha2562(stringValue) : void 0;
1737
+ }
1738
+ }
1739
+ ];
1740
+ const result = {};
1741
+ for (const { key, coerceFrom, transform } of coerce) {
1742
+ const foundValue = findFirstProperty2(properties, coerceFrom);
1743
+ if (foundValue !== null && transform) {
1744
+ const transformedValue = transform(foundValue);
1745
+ if (transformedValue) {
1746
+ result[key] = transformedValue;
1747
+ }
1748
+ } else if (foundValue !== null && typeof foundValue === "string") {
1749
+ result[key] = foundValue;
1750
+ }
1751
+ }
1752
+ return result;
1753
+ }
1754
+ function findFirstProperty2(properties, keys) {
1755
+ for (const key of keys) {
1756
+ if (properties[key] !== void 0) {
1757
+ return properties[key];
1758
+ }
1759
+ }
1760
+ return null;
1761
+ }
1762
+ function mapTikTokEventParameters(eventName, properties) {
1763
+ if (!properties) {
1764
+ return null;
1765
+ }
1766
+ const params = {};
1767
+ const addParam = (key, value) => {
1768
+ if (value !== null && value !== void 0 && typeof value !== "object") {
1769
+ params[key] = value;
1770
+ }
1771
+ };
1772
+ switch (eventName) {
1773
+ case "AddToCart":
1774
+ case "Purchase":
1775
+ case "ViewContent": {
1776
+ addParam("content_type", properties["content_type"]);
1777
+ addParam("content_ids", properties["content_ids"]);
1778
+ addParam("quantity", properties["quantity"]);
1779
+ addParam("description", properties["description"]);
1780
+ addParam("currency", properties["currency"]);
1781
+ addParam("value", properties["value"]);
1782
+ addParam("content_name", properties["content_name"]);
1783
+ addParam("content_category", properties["content_category"]);
1784
+ break;
1785
+ }
1786
+ case "StartTrial": {
1787
+ addParam("content_ids", properties["content_ids"]);
1788
+ addParam("currency", properties["currency"]);
1789
+ addParam("value", properties["value"]);
1790
+ break;
1791
+ }
1792
+ case "Search": {
1793
+ const searchString = properties["search_string"] || properties["query"] || properties["search_query"];
1794
+ addParam("search_string", searchString);
1795
+ break;
1796
+ }
1797
+ case "InitiateCheckout":
1798
+ case "AddPaymentInfo": {
1799
+ addParam("value", properties["value"]);
1800
+ addParam("currency", properties["currency"]);
1801
+ addParam("content_ids", properties["content_ids"]);
1802
+ addParam("quantity", properties["quantity"]);
1803
+ break;
1804
+ }
1805
+ case "CompleteRegistration":
1806
+ case "Subscribe": {
1807
+ addParam("value", properties["value"]);
1808
+ addParam("currency", properties["currency"]);
1809
+ break;
1810
+ }
1811
+ default: {
1812
+ addParam("value", properties["value"]);
1813
+ addParam("currency", properties["currency"]);
1814
+ break;
1815
+ }
1816
+ }
1817
+ return Object.keys(params).length > 0 ? params : null;
1818
+ }
1819
+
1820
+ // src/lib/saascoOrigin.ts
1821
+ var SAASCO_ORIGIN = "https://www.saasco.com";
1822
+ var APEX_ORIGIN = "https://saasco.com";
1823
+ var CANONICAL_HOST = "www.saasco.com";
1824
+ var TRAILING_SLASHES_RE = /\/+$/u;
1825
+ function normalizeSaascoBaseUrl(baseUrl) {
1826
+ return baseUrl.replace(TRAILING_SLASHES_RE, "") === APEX_ORIGIN ? SAASCO_ORIGIN : baseUrl;
1827
+ }
1828
+ function normalizeSaascoUrl(url) {
1829
+ let parsed;
1830
+ try {
1831
+ parsed = new URL(url);
1832
+ } catch {
1833
+ return url;
1834
+ }
1835
+ if (parsed.origin !== APEX_ORIGIN) {
1836
+ return url;
1837
+ }
1838
+ parsed.host = CANONICAL_HOST;
1839
+ return parsed.toString();
1840
+ }
1841
+
1842
+ // src/lib/social-proof/storage.ts
1843
+ var WIDGET_PAYLOAD_PATH = "/api/social-proof/widget";
1844
+ var SOCIAL_PROOF_LOADER_FILE = "saasco-social-proof-loader.js";
1845
+ var SOCIAL_PROOF_QA_PARAM = "saasco_notifications";
1846
+ var WIDGET_PAYLOAD_CACHE_KEY_PREFIX = "saasco-notif-widget:";
1847
+ var WIDGET_PAYLOAD_CACHE_TTL_MS = 20 * 60 * 1e3;
1848
+ var DISMISSED_AT_KEY = "saasco-notif-dismissed-at";
1849
+ var DISMISS_SUPPRESSION_MS = 24 * 36e5;
1850
+ function readSession(key) {
1851
+ try {
1852
+ return window.sessionStorage.getItem(key);
1853
+ } catch {
1854
+ return null;
1855
+ }
1856
+ }
1857
+ function writeSession(key, value) {
1858
+ try {
1859
+ window.sessionStorage.setItem(key, value);
1860
+ } catch {
1861
+ }
1862
+ }
1863
+ function readFreshWidgetPayloadCache(cacheKey) {
1864
+ const raw = readSession(cacheKey);
1865
+ if (!raw) {
1866
+ return void 0;
1867
+ }
1868
+ try {
1869
+ const cached = JSON.parse(raw);
1870
+ if (typeof cached.fetchedAt === "number" && Date.now() - cached.fetchedAt < WIDGET_PAYLOAD_CACHE_TTL_MS && typeof cached.data?.enabled === "boolean") {
1871
+ return cached.data;
1872
+ }
1873
+ } catch {
1874
+ }
1875
+ return void 0;
1876
+ }
1877
+ function readLocal(key) {
1878
+ try {
1879
+ return window.localStorage.getItem(key);
1880
+ } catch {
1881
+ return null;
1882
+ }
1883
+ }
1884
+ function isDismissSuppressed() {
1885
+ const raw = readLocal(DISMISSED_AT_KEY);
1886
+ if (raw === null) {
1887
+ return false;
1888
+ }
1889
+ const dismissedAt = Number(raw);
1890
+ return Number.isFinite(dismissedAt) && Date.now() - dismissedAt < DISMISS_SUPPRESSION_MS;
1891
+ }
1892
+
1893
+ // src/lib/social-proof/widgetPayloadCheck.ts
1894
+ async function injectSocialProofLoader({
1895
+ baseUrl: baseUrlOverride,
1896
+ projectId,
1897
+ scriptUrl
1898
+ }) {
1899
+ if (typeof window === "undefined" || typeof document === "undefined") {
1900
+ return;
1901
+ }
1902
+ if (isSocialProofLoaderPresent()) {
1903
+ return;
1904
+ }
1905
+ if (!isSocialProofQaMode()) {
1906
+ const baseUrl = normalizeSaascoBaseUrl(
1907
+ baseUrlOverride ?? getUrlOrigin(scriptUrl) ?? SAASCO_ORIGIN
1908
+ );
1909
+ const enabled = await shouldInjectSocialProof({
1910
+ baseUrl,
1911
+ projectId
1912
+ });
1913
+ if (!enabled) {
1914
+ return;
1915
+ }
1916
+ }
1917
+ if (isSocialProofLoaderPresent()) {
1918
+ return;
1919
+ }
1920
+ window.__saascoSocialProofInjected = true;
1921
+ const script = document.createElement("script");
1922
+ script.async = true;
1923
+ script.src = normalizeSaascoUrl(scriptUrl);
1924
+ script.setAttribute("data-projectId", projectId);
1925
+ if (baseUrlOverride !== void 0) {
1926
+ script.setAttribute(
1927
+ "data-base-url",
1928
+ normalizeSaascoBaseUrl(baseUrlOverride)
1929
+ );
1930
+ }
1931
+ (document.body ?? document.head ?? document.documentElement).append(script);
1932
+ }
1933
+ async function shouldInjectSocialProof({
1934
+ baseUrl,
1935
+ projectId
1936
+ }) {
1937
+ if (isDismissSuppressed()) {
1938
+ return false;
1939
+ }
1940
+ const cacheKey = `${WIDGET_PAYLOAD_CACHE_KEY_PREFIX}${projectId}`;
1941
+ const cached = readFreshWidgetPayloadCache(cacheKey);
1942
+ if (cached !== void 0) {
1943
+ return isEnabledPayload(cached);
1944
+ }
1945
+ let data2;
1946
+ try {
1947
+ const response = await fetch(
1948
+ `${baseUrl}${WIDGET_PAYLOAD_PATH}?projectId=${encodeURIComponent(projectId)}`
1949
+ );
1950
+ if (!response.ok) {
1951
+ return false;
1952
+ }
1953
+ data2 = await response.json();
1954
+ } catch {
1955
+ return false;
1956
+ }
1957
+ writeSession(cacheKey, JSON.stringify({ data: data2, fetchedAt: Date.now() }));
1958
+ return isEnabledPayload(data2);
1959
+ }
1960
+ function isEnabledPayload(data2) {
1961
+ return typeof data2 === "object" && data2 !== null && data2.enabled === true;
1962
+ }
1963
+ function isSocialProofLoaderPresent() {
1964
+ return Boolean(
1965
+ window.__saascoSocialProofInjected || document.querySelector(`script[src*="${SOCIAL_PROOF_LOADER_FILE}"]`)
1966
+ );
1967
+ }
1968
+ function getUrlOrigin(url) {
1969
+ try {
1970
+ return new URL(
1971
+ url,
1972
+ typeof window === "undefined" ? void 0 : window.location.href
1973
+ ).origin;
1974
+ } catch {
1975
+ }
1976
+ }
1977
+ function isSocialProofQaMode() {
1978
+ if (typeof window === "undefined") {
1979
+ return false;
1980
+ }
1981
+ try {
1982
+ const mode = new URLSearchParams(window.location.search).get(
1983
+ SOCIAL_PROOF_QA_PARAM
1984
+ );
1985
+ return mode === "test" || mode === "reset";
1986
+ } catch {
1987
+ return false;
1988
+ }
1989
+ }
1990
+
1991
+ // src/lib/analytics.ts
1992
+ function getSupportGlobal() {
1993
+ return window.SaascoSupport;
1994
+ }
1995
+ var isBrowser5 = typeof window !== "undefined";
1996
+ var isServer5 = !isBrowser5;
1997
+ var PREF = "saasco-sdk";
1998
+ var SESSION_DURATION = 1e3 * 60 * 30;
1999
+ var USER_DURATION = 1e3 * 60 * 60 * 24 * 365;
2000
+ var SUPPORT_ATTR_MAP = {
2001
+ baseUrl: "data-base-url",
2002
+ placeholder: "data-placeholder"
2003
+ };
2004
+ var data = {};
2005
+ function getRootDomain(url) {
2006
+ if (isServer5) {
2007
+ return;
2008
+ }
2009
+ if (!url) {
2010
+ return;
2011
+ }
2012
+ try {
2013
+ const { hostname } = new URL(url);
2014
+ if (!isValid(hostname)) {
2015
+ return;
2016
+ }
2017
+ const parsed = parse(hostname);
2018
+ if ("domain" in parsed && parsed.domain) {
2019
+ return parsed.domain;
2020
+ }
2021
+ } catch {
2022
+ }
2023
+ }
2024
+ function isSameRootDomain(url1, url2) {
2025
+ const root1 = getRootDomain(url1);
2026
+ const root2 = getRootDomain(url2);
2027
+ return !!root1 && !!root2 && root1 === root2;
2028
+ }
2029
+ function setCookie(name, value, ttl) {
2030
+ if (isServer5) {
2031
+ return;
2032
+ }
2033
+ const expires = new Date(Date.now() + ttl).toUTCString();
2034
+ const domain = getRootDomain(window.location.href);
2035
+ const cookieString = `${name}=${encodeURIComponent(
2036
+ value
2037
+ )}; expires=${expires}; path=/${domain ? `; domain=.${domain}` : ""}`;
2038
+ document.cookie = cookieString;
2039
+ }
2040
+ function getCookie(name) {
2041
+ if (isServer5) {
2042
+ return null;
2043
+ }
2044
+ const nameEQ = `${name}=`;
2045
+ const ca = document.cookie.split(";");
2046
+ for (const part of ca) {
2047
+ let c = part;
2048
+ while (c.charAt(0) === " ") {
2049
+ c = c.slice(1);
2050
+ }
2051
+ if (c.indexOf(nameEQ) === 0) {
2052
+ return decodeURIComponent(c.slice(nameEQ.length));
2053
+ }
2054
+ }
2055
+ return null;
2056
+ }
2057
+ function deleteCookie(name) {
2058
+ if (isServer5) {
2059
+ return;
2060
+ }
2061
+ const domain = getRootDomain(window.location.href);
2062
+ const cookieString = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/${domain ? `; domain=.${domain}` : ""}`;
2063
+ document.cookie = cookieString;
2064
+ }
2065
+ function migrateFromLocalStorage() {
2066
+ if (isServer5) {
2067
+ return;
2068
+ }
2069
+ const keysToMigrate = [
2070
+ `${PREF}-session-id`,
2071
+ `${PREF}-anonymous-id`,
2072
+ `${PREF}-user-id`,
2073
+ `${PREF}-session-utm-context`,
2074
+ `${PREF}-super-context`
2075
+ ];
2076
+ for (const key of keysToMigrate) {
2077
+ try {
2078
+ const value = localStorage.getItem(key);
2079
+ if (value) {
2080
+ const item = JSON.parse(value);
2081
+ const now = Date.now();
2082
+ if (item.expiry > now) {
2083
+ const remainingTtl = item.expiry - now;
2084
+ setCookie(key, JSON.stringify(item.value), remainingTtl);
2085
+ localStorage.removeItem(key);
2086
+ } else {
2087
+ localStorage.removeItem(key);
2088
+ }
2089
+ }
2090
+ } catch {
2091
+ localStorage.removeItem(key);
2092
+ }
2093
+ }
2094
+ }
2095
+ function storeData(key, value, ttl) {
2096
+ if (isServer5) {
2097
+ const item = {
2098
+ value,
2099
+ expiry: Date.now() + ttl
2100
+ };
2101
+ if (value === void 0) {
2102
+ delete data[key];
2103
+ return;
2104
+ }
2105
+ data[key] = item;
2106
+ return;
2107
+ }
2108
+ const fullKey = `${PREF}-${key}`;
2109
+ if (value === void 0) {
2110
+ deleteCookie(fullKey);
2111
+ return;
2112
+ }
2113
+ setCookie(fullKey, JSON.stringify(value), ttl);
2114
+ }
2115
+ function retrieveData(key) {
2116
+ if (isServer5) {
2117
+ const item = data[key];
2118
+ if (!item) {
2119
+ return null;
2120
+ }
2121
+ if (Date.now() > item.expiry) {
2122
+ delete data[key];
2123
+ return null;
2124
+ }
2125
+ return item.value;
2126
+ }
2127
+ const fullKey = `${PREF}-${key}`;
2128
+ const itemStr = getCookie(fullKey);
2129
+ if (!itemStr) {
2130
+ return null;
2131
+ }
2132
+ try {
2133
+ const parsed = JSON.parse(itemStr);
2134
+ if (parsed !== null && typeof parsed === "object" && "value" in parsed && "expiry" in parsed) {
2135
+ return parsed.value;
2136
+ }
2137
+ return parsed;
2138
+ } catch {
2139
+ deleteCookie(fullKey);
2140
+ return null;
2141
+ }
2142
+ }
2143
+ function getSessionId() {
2144
+ return retrieveData(`session-id`);
2145
+ }
2146
+ function setSessionId({ reset } = { reset: false }) {
2147
+ const sessionId = reset ? uuid() : getSessionId() || uuid();
2148
+ storeData(`session-id`, sessionId, SESSION_DURATION);
2149
+ return sessionId;
2150
+ }
2151
+ function getAnonymousId() {
2152
+ return retrieveData(`anonymous-id`);
2153
+ }
2154
+ function setAnonymousId({ reset } = { reset: false }) {
2155
+ const anonymousId = reset ? uuid() : getAnonymousId() || uuid();
2156
+ storeData(`anonymous-id`, anonymousId, USER_DURATION);
2157
+ return anonymousId;
2158
+ }
2159
+ function getServerSideAnonymousId(userId) {
2160
+ return `server-${userId}`;
2161
+ }
2162
+ function isSoftDistinctId(distinctId) {
2163
+ return distinctId.startsWith("soft_");
2164
+ }
2165
+ function getUserId() {
2166
+ return retrieveData(`user-id`);
2167
+ }
2168
+ function setUserId(userId) {
2169
+ storeData(`user-id`, userId ?? void 0, USER_DURATION);
2170
+ }
2171
+ function buildSupportTraits(properties, envelope) {
2172
+ const traits = {};
2173
+ const add = (key, value) => {
2174
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2175
+ traits[key] = value;
2176
+ }
2177
+ };
2178
+ if (isBrowser5) {
2179
+ for (const [key, value] of Object.entries(getBrowserContext())) {
2180
+ add(key, value);
2181
+ }
2182
+ }
2183
+ for (const [key, value] of Object.entries(properties ?? {})) {
2184
+ add(key, value);
2185
+ }
2186
+ add("anonymousId", envelope.anonymousId);
2187
+ add("sessionId", envelope.sessionId);
2188
+ return traits;
2189
+ }
2190
+ function getSuperContext() {
2191
+ return retrieveData("super-context") || {};
2192
+ }
2193
+ function setSuperContext(superContext) {
2194
+ storeData("super-context", superContext, SESSION_DURATION);
2195
+ }
2196
+ function updateSuperContext(browserContext) {
2197
+ const existingSuperContext = getSuperContext();
2198
+ const sessionScopedKeys = [
2199
+ "$utmSource",
2200
+ "$utmMedium",
2201
+ "$utmCampaign",
2202
+ "$utmTerm",
2203
+ "$utmContent",
2204
+ "$utmId",
2205
+ "$utmSourcePlatform",
2206
+ "$utmCampaignId",
2207
+ "$utmCreativeFormat",
2208
+ "$utmMarketingTactic",
2209
+ "$utmAdSource",
2210
+ "$utmAdId"
2211
+ ];
2212
+ const superContext = existingSuperContext;
2213
+ for (const key of sessionScopedKeys) {
2214
+ if (!superContext[key]) {
2215
+ const value = { ...browserContext }[key];
2216
+ if (value) {
2217
+ superContext[key] = value;
2218
+ }
2219
+ }
2220
+ }
2221
+ if (browserContext.$referrer && !isSameRootDomain(browserContext.$referrer, browserContext.$href)) {
2222
+ superContext["$referrer"] = browserContext.$referrer;
2223
+ if (browserContext.$referringDomain) {
2224
+ superContext["$referringDomain"] = browserContext.$referringDomain;
2225
+ }
2226
+ }
2227
+ setSuperContext(superContext);
2228
+ return superContext;
2229
+ }
2230
+ var IDENTIFY_DEDUP_OMIT_PROPERTY_KEYS = /* @__PURE__ */ new Set([
2231
+ "exp",
2232
+ "iat",
2233
+ "jti",
2234
+ "nbf",
2235
+ "sub",
2236
+ "updatedAt"
2237
+ ]);
2238
+ var IDENTIFY_DEDUP_OMIT_META_KEYS = /* @__PURE__ */ new Set(["recentProjects"]);
2239
+ function getPropertiesForIdentifyDedup(properties) {
2240
+ const dedupProperties = {};
2241
+ for (const key of Object.keys(properties).sort()) {
2242
+ if (key.startsWith("$") || IDENTIFY_DEDUP_OMIT_PROPERTY_KEYS.has(key)) {
2243
+ continue;
2244
+ }
2245
+ if (key === "picture" && properties.image) {
2246
+ continue;
2247
+ }
2248
+ const value = properties[key];
2249
+ if (key === "meta" && value && typeof value === "object") {
2250
+ const meta = value;
2251
+ const dedupMeta = {};
2252
+ for (const metaKey of Object.keys(meta).sort()) {
2253
+ if (IDENTIFY_DEDUP_OMIT_META_KEYS.has(metaKey)) {
2254
+ continue;
2255
+ }
2256
+ dedupMeta[metaKey] = meta[metaKey];
2257
+ }
2258
+ if (Object.keys(dedupMeta).length > 0) {
2259
+ dedupProperties.meta = dedupMeta;
2260
+ }
2261
+ continue;
2262
+ }
2263
+ if (key === "projectIds" && Array.isArray(value)) {
2264
+ dedupProperties.projectIds = [...value].sort();
2265
+ continue;
2266
+ }
2267
+ dedupProperties[key] = value;
2268
+ }
2269
+ return dedupProperties;
2270
+ }
2271
+ function getIdentifyDedupKey(distinctId, properties, context) {
2272
+ const dedupProperties = getPropertiesForIdentifyDedup(properties ?? {});
2273
+ return `${distinctId}:${JSON.stringify(dedupProperties)}:${JSON.stringify(context ?? {})}`;
2274
+ }
2275
+ function readLastIdentifyKey() {
2276
+ if (isBrowser5) {
2277
+ return window.saascoLastIdentifyKey ?? "";
2278
+ }
2279
+ return "";
2280
+ }
2281
+ function writeLastIdentifyKey(key) {
2282
+ if (isBrowser5) {
2283
+ window.saascoLastIdentifyKey = key;
2284
+ }
2285
+ }
2286
+ var Saasco = class {
2287
+ /**
2288
+ * Creates an instance of the Saasco SDK.
2289
+ * @param config Configuration options.
2290
+ * @param config.projectId The unique identifier for the project.
2291
+ * @param config.proxy The URL of the proxy server to use, if any.
2292
+ * @param config.autoPageTracking Whether to automatically track page views. Default is false.
2293
+ * @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
2294
+ * @param config.debug Whether to log debug information. Default is false.
2295
+ * @param config.trackUrlParams Whether to track URL parameters. Default is true.
2296
+ * @param config.trackHashChanges Whether to track hash changes. Default is true.
2297
+ * @param config.integrations Configuration for third-party integrations like Facebook Pixel.
2298
+ */
2299
+ constructor(config) {
2300
+ this.config = config;
2301
+ const loggerLevel = getLoggerLevel(this.config);
2302
+ this.logger = new AnalyticsLogger({
2303
+ level: loggerLevel,
2304
+ label: "Saasco Debug"
2305
+ });
2306
+ this.logger.info("Saasco started");
2307
+ if (!config.projectId) {
2308
+ this.logger.error(
2309
+ "Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly."
2310
+ );
2311
+ return;
2312
+ }
2313
+ this.config.enabled = this.config.enabled === void 0 ? true : this.config.enabled;
2314
+ this.config.autoPageTracking = {
2315
+ enabled: true,
2316
+ trackQueryParams: true,
2317
+ trackHash: true,
2318
+ ...this.config.autoPageTracking
2319
+ };
2320
+ this.integrationManager = new IntegrationManager({
2321
+ loggerLevel,
2322
+ maxQueueSize: 1e3
2323
+ });
2324
+ this.integrationManager.setContext({});
2325
+ void (async () => {
2326
+ try {
2327
+ await this.initIntegrations();
2328
+ } catch (error) {
2329
+ this.logger.info("Error initializing integrations:", error);
2330
+ }
2331
+ })();
2332
+ }
2333
+ // Dedup state lives on window in the browser so it survives duplicate module
2334
+ // instances from bundlers/HMR. The dedup key keeps all caller-supplied traits
2335
+ // except known volatile fields (JWT claims, updatedAt, $-prefixed keys, etc.).
2336
+ lastPageViewPath = "";
2337
+ isInitialized = false;
2338
+ integrationManager;
2339
+ logger;
2340
+ // Last identity handed to `identify()`, mirrored into the lazily-loaded
2341
+ // support widget so chat conversations attach to a CRM contact. Held
2342
+ // here so an `identify()` that fires before the widget bundle finishes
2343
+ // loading is replayed once it does.
2344
+ lastSupportIdentity = null;
2345
+ init() {
2346
+ if (this.isInitialized) {
2347
+ this.logger.info(
2348
+ "Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times."
2349
+ );
2350
+ return;
2351
+ }
2352
+ if (isBrowser5) {
2353
+ window.saasco = this;
2354
+ }
2355
+ migrateFromLocalStorage();
2356
+ this.logger.info("Saasco initialized", this.config);
2357
+ if (this.config.debug) {
2358
+ this.logger.info(
2359
+ "Debug mode active. This will log all events to the console."
2360
+ );
2361
+ }
2362
+ if (!this.config.enabled) {
2363
+ this.logger.info(
2364
+ `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." : ""}`
2365
+ );
2366
+ }
2367
+ this.initAutoPageTracking();
2368
+ if (this.config.support && this.config.support.enabled !== false) {
2369
+ this.injectSupport();
2370
+ }
2371
+ if (this.config.socialProof?.enabled !== false) {
2372
+ void this.injectSocialProof();
2373
+ }
2374
+ this.isInitialized = true;
2375
+ }
2376
+ disableDebug() {
2377
+ this.logger.info("Debug mode deactivated.");
2378
+ this.config.debug = false;
2379
+ }
2380
+ enableDebug() {
2381
+ this.config.debug = true;
2382
+ this.logger.info("Debug mode activated.");
2383
+ }
2384
+ /**
2385
+ * Enables and injects the support widget when it was constructed with
2386
+ * `support: { enabled: false }`. Safe to call multiple times.
2387
+ */
2388
+ enableSupport() {
2389
+ if (!this.config.support) {
2390
+ this.logger.warn(
2391
+ "Support is not configured; enableSupport() was ignored."
2392
+ );
2393
+ return;
2394
+ }
2395
+ this.config.support.enabled = true;
2396
+ this.injectSupport();
2397
+ this.pushSupportIdentity();
2398
+ }
2399
+ /**
2400
+ * Enables and injects the social-proof widget (e.g. after constructing with
2401
+ * `socialProof: { enabled: false }`, or when no `socialProof` block was
2402
+ * passed). Injection still runs the server check, so the loader only
2403
+ * downloads when the project has the app enabled in the dashboard.
2404
+ * Safe to call multiple times — the loader injection is guarded against
2405
+ * double-injection.
2406
+ */
2407
+ enableSocialProof() {
2408
+ this.config.socialProof = {
2409
+ ...this.config.socialProof,
2410
+ enabled: true
2411
+ };
2412
+ void this.injectSocialProof();
2413
+ }
2414
+ /**
2415
+ * Initialize third-party integrations
2416
+ */
2417
+ async initIntegrations() {
2418
+ this.logger.info("Initialize integrations");
2419
+ if (!this.config.integrations || this.config.integrations.length === 0) {
2420
+ return;
2421
+ }
2422
+ if (!this.config.enabled) {
2423
+ this.logger.info(
2424
+ "Analytics is disabled. Skipping integration initialization."
2425
+ );
2426
+ return;
2427
+ }
2428
+ for (const integrationConfig of this.config.integrations) {
2429
+ try {
2430
+ if (integrationConfig.type === "facebook-pixel") {
2431
+ this.logger.info(
2432
+ "Registering Facebook Pixel integration with ID:",
2433
+ integrationConfig.config.pixelId
2434
+ );
2435
+ const fbIntegration = createFacebookPixelIntegration(
2436
+ integrationConfig.config,
2437
+ integrationConfig.debug
2438
+ );
2439
+ await this.integrationManager.registerIntegration(fbIntegration);
2440
+ } else if (integrationConfig.type === "pinterest-tag") {
2441
+ this.logger.info(
2442
+ "Registering Pinterest Tag integration with ID:",
2443
+ integrationConfig.config.tagId
2444
+ );
2445
+ const pinterestIntegration = createPinterestTagIntegration(
2446
+ integrationConfig.config,
2447
+ integrationConfig.debug
2448
+ );
2449
+ await this.integrationManager.registerIntegration(
2450
+ pinterestIntegration
2451
+ );
2452
+ } else if (integrationConfig.type === "tiktok-pixel") {
2453
+ this.logger.info(
2454
+ "Registering TikTok Pixel integration with ID:",
2455
+ integrationConfig.config.pixelId
2456
+ );
2457
+ const tiktokIntegration = createTikTokPixelIntegration(
2458
+ integrationConfig.config,
2459
+ integrationConfig.debug
2460
+ );
2461
+ await this.integrationManager.registerIntegration(tiktokIntegration);
2462
+ }
2463
+ } catch (error) {
2464
+ this.logger.info(
2465
+ `Failed to register ${integrationConfig.type} integration:`,
2466
+ error
2467
+ );
2468
+ }
2469
+ }
2470
+ }
2471
+ track(actionOrPayload, propertiesOrNothing, contextOrNothing) {
2472
+ if (!this.config.projectId) {
2473
+ return Promise.resolve(
2474
+ this.logger.error(
2475
+ "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."
2476
+ )
2477
+ );
2478
+ }
2479
+ const validationError = validateTrackCall(actionOrPayload, this.logger);
2480
+ if (validationError) {
2481
+ return Promise.resolve(validationError);
2482
+ }
2483
+ const hasAction = typeof actionOrPayload === "string";
2484
+ const action = hasAction ? actionOrPayload : actionOrPayload.event;
2485
+ const properties = hasAction ? propertiesOrNothing : actionOrPayload.properties;
2486
+ const context = hasAction ? contextOrNothing : actionOrPayload.context;
2487
+ const { anonymousId, distinctId, sessionId } = resolveTrackIdentity(actionOrPayload);
2488
+ let browserContext;
2489
+ let superContext;
2490
+ if (isBrowser5) {
2491
+ browserContext = getBrowserContext();
2492
+ superContext = updateSuperContext(browserContext);
2493
+ }
2494
+ const requestData = {
2495
+ id: uuid(),
2496
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2497
+ action,
2498
+ version,
2499
+ sessionId,
2500
+ anonymousId,
2501
+ // Send an explicit null when anonymous. The ingest schema requires the
2502
+ // `distinctId` key to be present (`z.string().nullish()`), and a bare
2503
+ // `undefined` is dropped by JSON.stringify, which 400s the event.
2504
+ distinctId: distinctId ?? null,
2505
+ projectId: this.config.projectId,
2506
+ payload: JSON.stringify({
2507
+ ...browserContext,
2508
+ ...superContext,
2509
+ properties: properties || {}
2510
+ }),
2511
+ source: isBrowser5 ? "client" : "server",
2512
+ context: JSON.stringify(context || {})
2513
+ };
2514
+ if (requestData.action === "Page View") {
2515
+ this.logger.info("Page View", window.location.href, requestData);
2516
+ } else {
2517
+ this.logger.info("track", requestData);
2518
+ }
2519
+ if (!this.config.enabled) {
2520
+ return Promise.resolve({
2521
+ success: true,
2522
+ message: "Analytics is disabled"
2523
+ });
2524
+ }
2525
+ this.integrationManager.track(action, properties);
2526
+ return this.doRequest("events", requestData);
2527
+ }
2528
+ /**
2529
+ * The page method lets you record page views on your website
2530
+ * This records the page title and path and names the event useing the reserved property "Page Viewed"
2531
+ *
2532
+ * Before implementing this make sure you have disabled the autoPageTracking in the config or you will get duplicate page views
2533
+ */
2534
+ page() {
2535
+ if (isServer5) {
2536
+ console.warn("Saasco page tracking is only available in the browser");
2537
+ return;
2538
+ }
2539
+ const url = new URL(window.location.href);
2540
+ let pathToCompare = url.pathname;
2541
+ if (this.config.autoPageTracking?.trackQueryParams) {
2542
+ pathToCompare += url.search;
2543
+ }
2544
+ if (this.config.autoPageTracking?.trackHash) {
2545
+ pathToCompare += url.hash;
2546
+ }
2547
+ if (pathToCompare === this.lastPageViewPath) {
2548
+ return;
2549
+ }
2550
+ this.lastPageViewPath = pathToCompare;
2551
+ return this.track("Page View");
2552
+ }
2553
+ identify(distinctIdOrProperties, propertiesOrContext, contextOrNothing) {
2554
+ if (!this.config.projectId) {
2555
+ return Promise.resolve(
2556
+ this.logger.error(
2557
+ "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."
2558
+ )
2559
+ );
2560
+ }
2561
+ const { context, distinctId, hasId, properties } = resolveIdentifyArgs(
2562
+ distinctIdOrProperties,
2563
+ propertiesOrContext,
2564
+ contextOrNothing
2565
+ );
2566
+ const userIdChangedToNull = distinctId === null && !!getUserId();
2567
+ if (userIdChangedToNull) {
2568
+ this.logger.info("User logged out");
2569
+ }
2570
+ const hasRealDistinctId = distinctId !== null && !isSoftDistinctId(distinctId);
2571
+ const { anonymousId, sessionId } = resolveIdentifySessionIds({
2572
+ distinctId: hasRealDistinctId ? distinctId : null,
2573
+ reset: userIdChangedToNull
2574
+ });
2575
+ setUserId(distinctId);
2576
+ this.updateSupportIdentity({
2577
+ distinctId: hasId && distinctId !== null ? distinctId : void 0,
2578
+ email: typeof properties?.["email"] === "string" ? properties["email"] : void 0,
2579
+ // Forward the full scalar payload (browser/track context + identify traits
2580
+ // + session envelope) so the widget can bind them via live context without
2581
+ // the host mirroring traits into `setStateSnapshot`.
2582
+ traits: buildSupportTraits(properties, { anonymousId, sessionId })
2583
+ });
2584
+ this.integrationManager.setContext({
2585
+ distinctId,
2586
+ anonymousId,
2587
+ sessionId,
2588
+ ...properties
2589
+ });
2590
+ if (!distinctId) {
2591
+ writeLastIdentifyKey("");
2592
+ return Promise.resolve({
2593
+ success: true,
2594
+ message: "No distinctId provided"
2595
+ });
2596
+ }
2597
+ const identifyKey = getIdentifyDedupKey(distinctId, properties, context);
2598
+ if (identifyKey === readLastIdentifyKey()) {
2599
+ this.logger.info("Skipping duplicate identify");
2600
+ return Promise.resolve({
2601
+ success: true,
2602
+ message: "Duplicate identify skipped"
2603
+ });
2604
+ }
2605
+ writeLastIdentifyKey(identifyKey);
2606
+ let browserContext;
2607
+ let superContext;
2608
+ if (isBrowser5) {
2609
+ browserContext = getBrowserContext();
2610
+ superContext = updateSuperContext(browserContext);
2611
+ }
2612
+ const requestData = {
2613
+ id: uuid(),
2614
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2615
+ projectId: this.config.projectId,
2616
+ distinctId,
2617
+ anonymousId,
2618
+ version,
2619
+ payload: {
2620
+ ...browserContext,
2621
+ ...properties,
2622
+ ...superContext
2623
+ },
2624
+ context: context || {}
2625
+ };
2626
+ this.logger.info("identify", requestData);
2627
+ if (!this.config.enabled) {
2628
+ return Promise.resolve({
2629
+ success: true,
2630
+ message: "Analytics is disabled"
2631
+ });
2632
+ }
2633
+ this.integrationManager.identify(distinctId, properties);
2634
+ return this.doRequest("identify", requestData);
2635
+ }
2636
+ /**
2637
+ * This should only be called when the user logs out
2638
+ * It will reset the session, anonymous id, and user id
2639
+ *
2640
+ * All events after calling reset will be tracked as a new user
2641
+ *
2642
+ */
2643
+ logout() {
2644
+ setSuperContext(null);
2645
+ this.identify(null);
2646
+ }
2647
+ /**
2648
+ * Handles sending data to the Analytics API
2649
+ * If you have a proxy set up, it will send the data to the proxy and you can handle forwarding the data to the Analytics events API
2650
+ * @param path - API path relative to the analytics base URL
2651
+ * @param data - Request payload to send
2652
+ */
2653
+ async doRequest(path, requestData) {
2654
+ const base = this.config.proxy || "https://t.saasco.com/";
2655
+ const url = `${base}${path}`;
2656
+ if (this.config.enabled === false) {
2657
+ return {
2658
+ success: false,
2659
+ message: "Analytics is disabled"
2660
+ };
2661
+ }
2662
+ let response;
2663
+ try {
2664
+ response = await fetch(url, {
2665
+ method: "POST",
2666
+ headers: {
2667
+ "Content-Type": "application/json"
2668
+ },
2669
+ body: JSON.stringify(requestData)
2670
+ });
2671
+ } catch (error) {
2672
+ const message = error instanceof Error ? error.message : String(error);
2673
+ this.logger.warn(
2674
+ `Network request failed`,
2675
+ `
2676
+ Error Message: "${message}"`,
2677
+ `
2678
+ Url: "${url}"`
2679
+ );
2680
+ return {
2681
+ success: false,
2682
+ message
2683
+ };
2684
+ }
2685
+ try {
2686
+ const responseBody = await response.json();
2687
+ if (!response.ok) {
2688
+ throw new Error(
2689
+ `${responseBody.message}: ${responseBody.errors ? `[${responseBody.errors.join(", ")}]` : ""}` || JSON.stringify(responseBody)
2690
+ );
2691
+ }
2692
+ return {
2693
+ success: true,
2694
+ message: responseBody.message ?? ""
2695
+ };
2696
+ } catch (error) {
2697
+ const message = error instanceof Error ? error.message : String(error);
2698
+ this.logger.error(
2699
+ `
2700
+ Error Message: "${message}"`,
2701
+ `
2702
+ Path: "/${path}"`,
2703
+ `
2704
+ Request Data: ${JSON.stringify(requestData, null, 2)}`
2705
+ );
2706
+ return {
2707
+ success: false,
2708
+ message
2709
+ };
2710
+ }
2711
+ }
2712
+ /**
2713
+ * If autoPageTracking is enabled, this will automatically track page views
2714
+ * It listens to url changes to track new pages every time the url changes
2715
+ * @returns void
2716
+ */
2717
+ initAutoPageTracking() {
2718
+ if (!this.config.autoPageTracking?.enabled) {
2719
+ return;
2720
+ }
2721
+ if (isServer5) {
2722
+ return console.warn(
2723
+ "Auto page tracking is only available in the browser"
2724
+ );
2725
+ }
2726
+ if (window.saascoAutoPageTrackingActive) {
2727
+ this.logger.info("Auto Page Tracking already enabled");
2728
+ return;
2729
+ }
2730
+ window.saascoAutoPageTrackingActive = true;
2731
+ this.logger.info("Auto Page Tracking enabled");
2732
+ this.page();
2733
+ if (this.config.autoPageTracking?.trackHash) {
2734
+ window.addEventListener("hashchange", () => this.page());
2735
+ }
2736
+ window.addEventListener("popstate", () => this.page());
2737
+ const historyPushState = history.pushState;
2738
+ history.pushState = (...args) => {
2739
+ const returnValue = historyPushState.apply(history, args);
2740
+ this.page();
2741
+ return returnValue;
2742
+ };
2743
+ const historyReplaceState = history.replaceState;
2744
+ history.replaceState = (...args) => {
2745
+ const returnValue = historyReplaceState.apply(history, args);
2746
+ this.page();
2747
+ return returnValue;
2748
+ };
2749
+ }
2750
+ getIntegrationsStats() {
2751
+ return this.integrationManager.getStats();
2752
+ }
2753
+ /**
2754
+ * Lazily injects the support **loader** from the same `/sdk/` origin as
2755
+ * the analytics bundle, forwarding the shared `projectId` and the widget
2756
+ * config as `data-*` attributes. The loader (no React) injects the
2757
+ * cross-origin embed iframe. Deferred (`async`) and guarded against
2758
+ * double-injection so re-running `init()` is a no-op.
2759
+ */
2760
+ injectSupport() {
2761
+ if (!isBrowser5 || typeof document === "undefined") {
2762
+ return;
2763
+ }
2764
+ const chat = this.config.support;
2765
+ if (!chat) {
2766
+ return;
2767
+ }
2768
+ if (window.__saascoSupportInjected || document.querySelector('script[src*="saasco-support-loader.js"]')) {
2769
+ return;
2770
+ }
2771
+ const scriptUrl = resolveLoaderScriptUrl(
2772
+ "saasco-support-loader.js",
2773
+ chat.scriptUrl
2774
+ );
2775
+ if (!scriptUrl) {
2776
+ this.logger.warn(
2777
+ "Support is enabled but the loader URL could not be resolved. Pass `support.scriptUrl` pointing at your hosted saasco-support-loader.js (or load the analytics SDK from the CDN)."
2778
+ );
2779
+ return;
2780
+ }
2781
+ window.__saascoSupportInjected = true;
2782
+ const script = document.createElement("script");
2783
+ script.async = true;
2784
+ script.src = normalizeSaascoUrl(scriptUrl);
2785
+ script.setAttribute("data-projectId", this.config.projectId);
2786
+ for (const [key, attr] of Object.entries(SUPPORT_ATTR_MAP)) {
2787
+ const value = chat[key];
2788
+ if (value !== void 0) {
2789
+ script.setAttribute(
2790
+ attr,
2791
+ attr === "data-base-url" ? normalizeSaascoBaseUrl(value) : value
2792
+ );
2793
+ }
2794
+ }
2795
+ script.addEventListener("load", () => {
2796
+ this.pushSupportIdentity();
2797
+ });
2798
+ (document.body ?? document.head ?? document.documentElement).append(script);
2799
+ }
2800
+ /**
2801
+ * Lazily injects the social-proof loader (server-gated). See
2802
+ * {@link injectSocialProofLoader}.
2803
+ */
2804
+ async injectSocialProof() {
2805
+ if (!isBrowser5) {
2806
+ return;
2807
+ }
2808
+ const { socialProof } = this.config;
2809
+ const scriptUrl = resolveLoaderScriptUrl(
2810
+ "saasco-social-proof-loader.js",
2811
+ socialProof?.scriptUrl
2812
+ ) ?? `${SAASCO_ORIGIN}/sdk/saasco-social-proof-loader.js`;
2813
+ await injectSocialProofLoader({
2814
+ baseUrl: socialProof?.baseUrl,
2815
+ projectId: this.config.projectId,
2816
+ scriptUrl
2817
+ });
2818
+ }
2819
+ /**
2820
+ * Records the latest CRM identity and forwards it to the support widget.
2821
+ * No-op when support isn't enabled.
2822
+ */
2823
+ updateSupportIdentity(identity) {
2824
+ if (!isBrowser5 || !this.config.support) {
2825
+ return;
2826
+ }
2827
+ const hasTraits = identity.traits && Object.keys(identity.traits).length > 0;
2828
+ this.lastSupportIdentity = identity.distinctId || identity.email || hasTraits ? identity : null;
2829
+ if (this.config.support.enabled === false) {
2830
+ return;
2831
+ }
2832
+ this.pushSupportIdentity();
2833
+ }
2834
+ /**
2835
+ * Pushes the current identity onto `window.SaascoSupport.identify`. The
2836
+ * widget bundle publishes that global asynchronously, so this short-polls for
2837
+ * it (same approach as tool registration); the load handler also calls this,
2838
+ * so a fresh page load with a stored distinctId still identifies the chat.
2839
+ */
2840
+ pushSupportIdentity() {
2841
+ if (!isBrowser5 || !this.config.support || this.config.support.enabled === false) {
2842
+ return;
2843
+ }
2844
+ const identity = this.lastSupportIdentity ?? {
2845
+ distinctId: getUserId() ?? void 0,
2846
+ // Anonymous (pre-identify) page load: still forward browser/track context
2847
+ // and the session envelope so live context has them from the first turn.
2848
+ traits: buildSupportTraits(void 0, {
2849
+ anonymousId: getAnonymousId() ?? void 0,
2850
+ sessionId: getSessionId() ?? void 0
2851
+ })
2852
+ };
2853
+ let attempts = 0;
2854
+ const tryPush = () => {
2855
+ const api = getSupportGlobal();
2856
+ if (!api) {
2857
+ attempts += 1;
2858
+ if (attempts > 40) {
2859
+ return;
2860
+ }
2861
+ setTimeout(tryPush, 25);
2862
+ return;
2863
+ }
2864
+ try {
2865
+ api.identify(identity);
2866
+ } catch (error) {
2867
+ this.logger.warn(
2868
+ "Failed to forward identity to the support widget:",
2869
+ error
2870
+ );
2871
+ }
2872
+ };
2873
+ tryPush();
2874
+ }
2875
+ };
2876
+ function resolveLoaderScriptUrl(loaderFile, override) {
2877
+ if (override) {
2878
+ return override.includes("saasco-sdk.js") ? override.replace("saasco-sdk.js", loaderFile) : override;
2879
+ }
2880
+ if (typeof document === "undefined") {
2881
+ return;
2882
+ }
2883
+ const scriptTags = document.querySelectorAll("script");
2884
+ for (const script of scriptTags) {
2885
+ const src = script.getAttribute("src");
2886
+ if (src?.includes("saasco-sdk.js")) {
2887
+ return src.replace("saasco-sdk.js", loaderFile);
2888
+ }
2889
+ }
2890
+ }
2891
+ function validateTrackCall(actionOrPayload, logger) {
2892
+ const hasAction = typeof actionOrPayload === "string";
2893
+ const hasPayload = typeof actionOrPayload === "object";
2894
+ if (isServer5 && hasAction) {
2895
+ return logger.error(
2896
+ '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" } })'
2897
+ );
2898
+ }
2899
+ if (isServer5 && hasPayload && !actionOrPayload.userId && !actionOrPayload.anonymousId) {
2900
+ return logger.error(
2901
+ '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.'
2902
+ );
2903
+ }
2904
+ return null;
2905
+ }
2906
+ function resolveTrackIdentity(actionOrPayload) {
2907
+ if (isServer5 && typeof actionOrPayload === "object") {
2908
+ const distinctId = actionOrPayload.userId?.toString();
2909
+ return {
2910
+ anonymousId: actionOrPayload.anonymousId?.toString() ?? (distinctId ? getServerSideAnonymousId(distinctId) : uuid()),
2911
+ distinctId,
2912
+ sessionId: actionOrPayload.sessionId ?? uuid()
2913
+ };
2914
+ }
2915
+ setSessionId();
2916
+ setAnonymousId();
2917
+ const payload = typeof actionOrPayload === "object" ? actionOrPayload : void 0;
2918
+ return {
2919
+ anonymousId: payload?.anonymousId?.toString() ?? getAnonymousId() ?? uuid(),
2920
+ distinctId: payload?.userId?.toString() ?? getUserId() ?? void 0,
2921
+ sessionId: payload?.sessionId ?? getSessionId() ?? uuid()
2922
+ };
2923
+ }
2924
+ function resolveIdentifyArgs(distinctIdOrProperties, propertiesOrContext, contextOrNothing) {
2925
+ const hasId = typeof distinctIdOrProperties === "string" || typeof distinctIdOrProperties === "number" || distinctIdOrProperties === null;
2926
+ return {
2927
+ context: hasId ? contextOrNothing : propertiesOrContext,
2928
+ // `null` is the logout signal callers depend on, so it has to survive the
2929
+ // conversion — optional chaining alone would hand back `undefined` and no
2930
+ // downstream `=== null` check would ever match.
2931
+ distinctId: hasId ? distinctIdOrProperties?.toString() ?? null : `soft_${uuid()}`,
2932
+ hasId,
2933
+ properties: hasId ? propertiesOrContext : distinctIdOrProperties
2934
+ };
2935
+ }
2936
+ function resolveIdentifySessionIds({
2937
+ distinctId,
2938
+ reset
2939
+ }) {
2940
+ if (isServer5 && distinctId !== null) {
2941
+ return {
2942
+ anonymousId: getServerSideAnonymousId(distinctId),
2943
+ sessionId: uuid()
2944
+ };
2945
+ }
2946
+ return {
2947
+ anonymousId: setAnonymousId({ reset }),
2948
+ sessionId: setSessionId({ reset })
2949
+ };
2950
+ }
2951
+ function getLoggerLevel(config) {
2952
+ let urlDebug = false;
2953
+ let urlDebugVerbose = false;
2954
+ if (!isServer5) {
2955
+ try {
2956
+ const urlParams = new URLSearchParams(window.location.search);
2957
+ urlDebug = urlParams.get("saasco-debug") === "true";
2958
+ urlDebugVerbose = urlParams.get("saasco-debug-verbose") === "true";
2959
+ } catch {
2960
+ }
2961
+ }
2962
+ const effectiveDebug = urlDebug || config.debug;
2963
+ const effectiveDebugVerbose = urlDebugVerbose || config.debugVerbose;
2964
+ return effectiveDebugVerbose ? 3 /* DEBUG */ : effectiveDebug ? 2 /* INFO */ : 0 /* ERROR */;
2965
+ }
2966
+
2967
+ // ../shared/src/lib/types/browserContext.ts
2968
+ import { z } from "zod";
2969
+ var BrowserContextSchema = z.object({
2970
+ $href: z.string(),
2971
+ $locale: z.string(),
2972
+ $location: z.string(),
2973
+ $pathname: z.string(),
2974
+ $referrer: z.string(),
2975
+ $referringDomain: z.string(),
2976
+ $screenDPI: z.number(),
2977
+ $screenHeight: z.number(),
2978
+ $screenWidth: z.number(),
2979
+ $title: z.string(),
2980
+ $userAgent: z.string(),
2981
+ $utmAdId: z.string().nullable(),
2982
+ $utmAdSource: z.string().nullable(),
2983
+ $utmCampaign: z.string().nullable(),
2984
+ $utmCampaignId: z.string().nullable(),
2985
+ $utmContent: z.string().nullable(),
2986
+ $utmCreativeFormat: z.string().nullable(),
2987
+ $utmId: z.string().nullable(),
2988
+ $utmMarketingTactic: z.string().nullable(),
2989
+ $utmMedium: z.string().nullable(),
2990
+ $utmSource: z.string().nullable(),
2991
+ $utmSourcePlatform: z.string().nullable(),
2992
+ $utmTerm: z.string().nullable()
2993
+ });
2994
+ export {
2995
+ BrowserContextSchema,
2996
+ IntegrationManager,
2997
+ Saasco,
2998
+ createFacebookPixelIntegration,
2999
+ createPinterestTagIntegration,
3000
+ createTikTokPixelIntegration,
3001
+ getBrowserContext,
3002
+ standardFacebookEvents,
3003
+ standardPinterestEvents,
3004
+ standardTikTokEvents,
3005
+ timezones
3006
+ };