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