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 +3043 -0
- package/dist/index.d.cts +535 -0
- package/dist/index.d.ts +535 -0
- package/dist/index.js +3006 -0
- package/dist/support.cjs +18301 -0
- package/dist/support.d.cts +183 -0
- package/dist/support.d.ts +183 -0
- package/dist/support.js +18303 -0
- package/package.json +54 -10
- package/README.md +0 -546
- package/index.cjs.d.ts +0 -1
- package/index.cjs.js +0 -2166
- package/index.esm.d.ts +0 -1
- package/index.esm.js +0 -2152
- package/src/index.d.ts +0 -5
- package/src/lib/analytics.d.ts +0 -134
- package/src/lib/getBrowserContext.d.ts +0 -2
- package/src/lib/integrations/facebook-pixel.d.ts +0 -34
- package/src/lib/integrations/index.d.ts +0 -4
- package/src/lib/integrations/integration-manager.d.ts +0 -119
- package/src/lib/integrations/pinterest-tag.d.ts +0 -37
- package/src/lib/integrations/tiktok-pixel.d.ts +0 -59
- package/src/lib/self-execute-analytics.d.ts +0 -6
- package/src/lib/timezones.d.ts +0 -3
- package/src/lib/tracking/index.d.ts +0 -1
- package/src/lib/tracking/types.d.ts +0 -73
- package/src/lib/utils/getIntegrationLoggerLevel.d.ts +0 -8
- package/src/lib/utils/index.d.ts +0 -3
- package/src/lib/utils/logger.d.ts +0 -30
- package/src/lib/utils/uuid.d.ts +0 -5
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
|
+
});
|