shopkit-analytics 1.0.0
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/LICENSE +21 -0
- package/README.md +769 -0
- package/dist/adapters/index.d.mts +4 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +2405 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +23 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/affiliate/index.d.mts +138 -0
- package/dist/affiliate/index.d.ts +138 -0
- package/dist/affiliate/index.js +816 -0
- package/dist/affiliate/index.js.map +1 -0
- package/dist/affiliate/index.mjs +74 -0
- package/dist/affiliate/index.mjs.map +1 -0
- package/dist/affiliate-tracker-BgHwibPv.d.mts +144 -0
- package/dist/affiliate-tracker-BgHwibPv.d.ts +144 -0
- package/dist/chunk-3TQR5DOP.mjs +79 -0
- package/dist/chunk-3TQR5DOP.mjs.map +1 -0
- package/dist/chunk-4MZH5OLR.mjs +2375 -0
- package/dist/chunk-4MZH5OLR.mjs.map +1 -0
- package/dist/chunk-JVEGG6JV.mjs +213 -0
- package/dist/chunk-JVEGG6JV.mjs.map +1 -0
- package/dist/chunk-P4OJDCEZ.mjs +57 -0
- package/dist/chunk-P4OJDCEZ.mjs.map +1 -0
- package/dist/chunk-TNXTKEGS.mjs +758 -0
- package/dist/chunk-TNXTKEGS.mjs.map +1 -0
- package/dist/events/index.d.mts +112 -0
- package/dist/events/index.d.ts +112 -0
- package/dist/events/index.js +2131 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/index.mjs +30 -0
- package/dist/events/index.mjs.map +1 -0
- package/dist/index-BnNRgdUv.d.ts +676 -0
- package/dist/index-GODWc1s6.d.mts +676 -0
- package/dist/index.d.mts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +3269 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +190 -0
- package/dist/index.mjs.map +1 -0
- package/dist/subscriber-43gnCKWe.d.ts +80 -0
- package/dist/subscriber-IFZJU57V.mjs +8 -0
- package/dist/subscriber-IFZJU57V.mjs.map +1 -0
- package/dist/subscriber-sWesj_5p.d.mts +80 -0
- package/dist/types.d.mts +991 -0
- package/dist/types.d.ts +991 -0
- package/dist/types.js +102 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +8 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/affiliate/AffiliateTracker.tsx
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
// src/affiliate/constants.ts
|
|
7
|
+
var UTM_PARAMETERS = [
|
|
8
|
+
"utm_source",
|
|
9
|
+
"utm_medium",
|
|
10
|
+
"utm_campaign",
|
|
11
|
+
"utm_term",
|
|
12
|
+
"utm_content"
|
|
13
|
+
];
|
|
14
|
+
var CLICK_ID_PARAMETERS = [
|
|
15
|
+
"gclid",
|
|
16
|
+
// Google Ads
|
|
17
|
+
"fbclid",
|
|
18
|
+
// Facebook
|
|
19
|
+
"msclkid",
|
|
20
|
+
// Microsoft Ads
|
|
21
|
+
"ttclid",
|
|
22
|
+
// TikTok
|
|
23
|
+
"twclid",
|
|
24
|
+
// Twitter
|
|
25
|
+
"li_fat_id"
|
|
26
|
+
// LinkedIn
|
|
27
|
+
];
|
|
28
|
+
var AFFILIATE_PARAMETERS = [
|
|
29
|
+
"click_id",
|
|
30
|
+
"affiliate_id",
|
|
31
|
+
"ref",
|
|
32
|
+
"source",
|
|
33
|
+
"referrer"
|
|
34
|
+
];
|
|
35
|
+
var ALL_AFFILIATE_PARAMETERS = [
|
|
36
|
+
...UTM_PARAMETERS,
|
|
37
|
+
...CLICK_ID_PARAMETERS,
|
|
38
|
+
...AFFILIATE_PARAMETERS
|
|
39
|
+
];
|
|
40
|
+
var STORAGE_CONFIG = {
|
|
41
|
+
DEFAULT_KEY: "affiliateParams",
|
|
42
|
+
DEFAULT_TTL: 30 * 24 * 60 * 60 * 1e3
|
|
43
|
+
// 30 days in milliseconds
|
|
44
|
+
};
|
|
45
|
+
var ATTRIBUTION_MODELS = {
|
|
46
|
+
FIRST_TOUCH: "first-touch",
|
|
47
|
+
LAST_TOUCH: "last-touch"
|
|
48
|
+
};
|
|
49
|
+
var STORAGE_TYPES = {
|
|
50
|
+
SESSION: "sessionStorage",
|
|
51
|
+
LOCAL: "localStorage"
|
|
52
|
+
};
|
|
53
|
+
var EVENT_TYPES = {
|
|
54
|
+
CAPTURE: "capture",
|
|
55
|
+
CLEAR: "clear",
|
|
56
|
+
EXPIRE: "expire"
|
|
57
|
+
};
|
|
58
|
+
var ATTRIBUTION_CHANNELS = {
|
|
59
|
+
DIRECT: "direct",
|
|
60
|
+
PAID_SEARCH: "paid_search",
|
|
61
|
+
ORGANIC_SEARCH: "organic_search",
|
|
62
|
+
SOCIAL: "social",
|
|
63
|
+
EMAIL: "email",
|
|
64
|
+
AFFILIATE: "affiliate",
|
|
65
|
+
REFERRAL: "referral",
|
|
66
|
+
DISPLAY: "display"
|
|
67
|
+
};
|
|
68
|
+
var SEARCH_ENGINES = [
|
|
69
|
+
"google.com",
|
|
70
|
+
"bing.com",
|
|
71
|
+
"yahoo.com",
|
|
72
|
+
"duckduckgo.com",
|
|
73
|
+
"baidu.com",
|
|
74
|
+
"yandex.com",
|
|
75
|
+
"ask.com"
|
|
76
|
+
];
|
|
77
|
+
var SOCIAL_PLATFORMS = [
|
|
78
|
+
"facebook",
|
|
79
|
+
"instagram",
|
|
80
|
+
"twitter",
|
|
81
|
+
"linkedin",
|
|
82
|
+
"tiktok",
|
|
83
|
+
"youtube",
|
|
84
|
+
"pinterest",
|
|
85
|
+
"snapchat"
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
// src/affiliate/affiliate-tracker.ts
|
|
89
|
+
var DEFAULT_CONFIG = {
|
|
90
|
+
storageKey: STORAGE_CONFIG.DEFAULT_KEY,
|
|
91
|
+
storageType: STORAGE_TYPES.SESSION,
|
|
92
|
+
attribution: ATTRIBUTION_MODELS.LAST_TOUCH,
|
|
93
|
+
ttl: STORAGE_CONFIG.DEFAULT_TTL,
|
|
94
|
+
customParams: [],
|
|
95
|
+
enableReferrerCapture: true,
|
|
96
|
+
enableUserAgentCapture: false,
|
|
97
|
+
onCapture: () => {
|
|
98
|
+
},
|
|
99
|
+
onError: () => {
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var currentConfig = { ...DEFAULT_CONFIG };
|
|
103
|
+
var eventListeners = [];
|
|
104
|
+
function configureAffiliateTracker(config = {}) {
|
|
105
|
+
currentConfig = { ...DEFAULT_CONFIG, ...config };
|
|
106
|
+
}
|
|
107
|
+
function generateSessionId() {
|
|
108
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
109
|
+
}
|
|
110
|
+
function getStorage() {
|
|
111
|
+
if (typeof window === "undefined") return null;
|
|
112
|
+
try {
|
|
113
|
+
return currentConfig.storageType === "localStorage" ? window.localStorage : window.sessionStorage;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
currentConfig.onError(new Error(`Storage not available: ${error}`));
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function isExpired(timestamp) {
|
|
120
|
+
return Date.now() - timestamp > currentConfig.ttl;
|
|
121
|
+
}
|
|
122
|
+
function emitEvent(event) {
|
|
123
|
+
eventListeners.forEach((listener) => {
|
|
124
|
+
try {
|
|
125
|
+
listener(event);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
currentConfig.onError(new Error(`Event listener error: ${error}`));
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function addEventListener(listener) {
|
|
132
|
+
eventListeners.push(listener);
|
|
133
|
+
return () => {
|
|
134
|
+
const index = eventListeners.indexOf(listener);
|
|
135
|
+
if (index > -1) {
|
|
136
|
+
eventListeners.splice(index, 1);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function captureAffiliateParams(customConfig) {
|
|
141
|
+
if (typeof window === "undefined") return null;
|
|
142
|
+
const originalConfig = currentConfig;
|
|
143
|
+
if (customConfig) {
|
|
144
|
+
currentConfig = { ...currentConfig, ...customConfig };
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const storage = getStorage();
|
|
148
|
+
if (!storage) return null;
|
|
149
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
150
|
+
const allKeys = [
|
|
151
|
+
...ALL_AFFILIATE_PARAMETERS,
|
|
152
|
+
...currentConfig.customParams
|
|
153
|
+
];
|
|
154
|
+
const affiliateParams = {};
|
|
155
|
+
for (const key of allKeys) {
|
|
156
|
+
const value = urlParams.get(key);
|
|
157
|
+
if (value) {
|
|
158
|
+
affiliateParams[key] = value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (currentConfig.enableReferrerCapture && document.referrer) {
|
|
162
|
+
affiliateParams.referrer = document.referrer;
|
|
163
|
+
}
|
|
164
|
+
if (Object.keys(affiliateParams).length === 0) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const existingData = getAffiliateParams();
|
|
168
|
+
let shouldUpdate = true;
|
|
169
|
+
if (existingData && currentConfig.attribution === "first-touch") {
|
|
170
|
+
shouldUpdate = false;
|
|
171
|
+
}
|
|
172
|
+
if (shouldUpdate) {
|
|
173
|
+
const affiliateData = {
|
|
174
|
+
...affiliateParams,
|
|
175
|
+
timestamp: Date.now(),
|
|
176
|
+
sessionId: generateSessionId(),
|
|
177
|
+
attribution: currentConfig.attribution,
|
|
178
|
+
url: window.location.href,
|
|
179
|
+
...currentConfig.enableUserAgentCapture && {
|
|
180
|
+
userAgent: navigator.userAgent
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
storage.setItem(currentConfig.storageKey, JSON.stringify(affiliateData));
|
|
184
|
+
emitEvent({
|
|
185
|
+
type: "capture",
|
|
186
|
+
data: affiliateData,
|
|
187
|
+
timestamp: Date.now()
|
|
188
|
+
});
|
|
189
|
+
currentConfig.onCapture(affiliateData);
|
|
190
|
+
return affiliateData;
|
|
191
|
+
}
|
|
192
|
+
return existingData;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
195
|
+
currentConfig.onError(errorObj);
|
|
196
|
+
return null;
|
|
197
|
+
} finally {
|
|
198
|
+
if (customConfig) {
|
|
199
|
+
currentConfig = originalConfig;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function getAffiliateParams() {
|
|
204
|
+
if (typeof window === "undefined") return null;
|
|
205
|
+
try {
|
|
206
|
+
const storage = getStorage();
|
|
207
|
+
if (!storage) return null;
|
|
208
|
+
const stored = storage.getItem(currentConfig.storageKey);
|
|
209
|
+
if (!stored) return null;
|
|
210
|
+
const data = JSON.parse(stored);
|
|
211
|
+
if (isExpired(data.timestamp)) {
|
|
212
|
+
clearAffiliateParams();
|
|
213
|
+
emitEvent({
|
|
214
|
+
type: "expire",
|
|
215
|
+
data,
|
|
216
|
+
timestamp: Date.now()
|
|
217
|
+
});
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return data;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
223
|
+
currentConfig.onError(errorObj);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function clearAffiliateParams() {
|
|
228
|
+
if (typeof window === "undefined") return;
|
|
229
|
+
try {
|
|
230
|
+
const storage = getStorage();
|
|
231
|
+
if (!storage) return;
|
|
232
|
+
const existingData = getAffiliateParams();
|
|
233
|
+
storage.removeItem(currentConfig.storageKey);
|
|
234
|
+
emitEvent({
|
|
235
|
+
type: "clear",
|
|
236
|
+
data: existingData || void 0,
|
|
237
|
+
timestamp: Date.now()
|
|
238
|
+
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
241
|
+
currentConfig.onError(errorObj);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function getAffiliateParamsAsUrlString() {
|
|
245
|
+
const data = getAffiliateParams();
|
|
246
|
+
if (!data) return "";
|
|
247
|
+
const params = new URLSearchParams();
|
|
248
|
+
const affiliateKeys = [
|
|
249
|
+
...ALL_AFFILIATE_PARAMETERS,
|
|
250
|
+
...currentConfig.customParams
|
|
251
|
+
];
|
|
252
|
+
for (const key of affiliateKeys) {
|
|
253
|
+
const value = data[key];
|
|
254
|
+
if (typeof value === "string" && value) {
|
|
255
|
+
params.set(key, value);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return params.toString();
|
|
259
|
+
}
|
|
260
|
+
function appendAffiliateParams(url) {
|
|
261
|
+
const affiliateParams = getAffiliateParamsAsUrlString();
|
|
262
|
+
if (!affiliateParams) return url;
|
|
263
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
264
|
+
return `${url}${separator}${affiliateParams}`;
|
|
265
|
+
}
|
|
266
|
+
function hasAffiliateData() {
|
|
267
|
+
return getAffiliateParams() !== null;
|
|
268
|
+
}
|
|
269
|
+
function getAffiliateSource() {
|
|
270
|
+
const data = getAffiliateParams();
|
|
271
|
+
if (!data) return null;
|
|
272
|
+
if (data.utm_source) return data.utm_source;
|
|
273
|
+
if (data.referrer) {
|
|
274
|
+
try {
|
|
275
|
+
return new URL(data.referrer).hostname;
|
|
276
|
+
} catch {
|
|
277
|
+
return data.referrer;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
function getConfig() {
|
|
283
|
+
return { ...currentConfig };
|
|
284
|
+
}
|
|
285
|
+
function resetConfig() {
|
|
286
|
+
currentConfig = { ...DEFAULT_CONFIG };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/affiliate/AffiliateTracker.tsx
|
|
290
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
291
|
+
var AffiliateTracker = ({
|
|
292
|
+
config,
|
|
293
|
+
autoCapture = true,
|
|
294
|
+
children
|
|
295
|
+
}) => {
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
if (config) {
|
|
298
|
+
configureAffiliateTracker(config);
|
|
299
|
+
}
|
|
300
|
+
if (autoCapture) {
|
|
301
|
+
captureAffiliateParams();
|
|
302
|
+
}
|
|
303
|
+
}, [config, autoCapture]);
|
|
304
|
+
return children ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
|
|
305
|
+
};
|
|
306
|
+
var AffiliateTracker_default = AffiliateTracker;
|
|
307
|
+
|
|
308
|
+
// src/affiliate/hooks.ts
|
|
309
|
+
import { useState, useEffect as useEffect2, useCallback, useRef } from "react";
|
|
310
|
+
function useAffiliateTracker(config) {
|
|
311
|
+
const [affiliateParams, setAffiliateParams] = useState(
|
|
312
|
+
null
|
|
313
|
+
);
|
|
314
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
315
|
+
const [error, setError] = useState(null);
|
|
316
|
+
const configRef = useRef(config);
|
|
317
|
+
useEffect2(() => {
|
|
318
|
+
configRef.current = config;
|
|
319
|
+
}, [config]);
|
|
320
|
+
useEffect2(() => {
|
|
321
|
+
if (configRef.current) {
|
|
322
|
+
configureAffiliateTracker({
|
|
323
|
+
...configRef.current,
|
|
324
|
+
onError: (err) => {
|
|
325
|
+
setError(err);
|
|
326
|
+
configRef.current?.onError?.(err);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}, []);
|
|
331
|
+
const refreshParams = useCallback(() => {
|
|
332
|
+
setIsLoading(true);
|
|
333
|
+
setError(null);
|
|
334
|
+
try {
|
|
335
|
+
const params = getAffiliateParams();
|
|
336
|
+
setAffiliateParams(params);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
339
|
+
setError(error2);
|
|
340
|
+
} finally {
|
|
341
|
+
setIsLoading(false);
|
|
342
|
+
}
|
|
343
|
+
}, []);
|
|
344
|
+
const captureParams = useCallback(() => {
|
|
345
|
+
setError(null);
|
|
346
|
+
try {
|
|
347
|
+
const params = captureAffiliateParams(configRef.current);
|
|
348
|
+
setAffiliateParams(params);
|
|
349
|
+
} catch (err) {
|
|
350
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
351
|
+
setError(error2);
|
|
352
|
+
}
|
|
353
|
+
}, []);
|
|
354
|
+
const clearParams = useCallback(() => {
|
|
355
|
+
setError(null);
|
|
356
|
+
try {
|
|
357
|
+
clearAffiliateParams();
|
|
358
|
+
setAffiliateParams(null);
|
|
359
|
+
} catch (err) {
|
|
360
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
361
|
+
setError(error2);
|
|
362
|
+
}
|
|
363
|
+
}, []);
|
|
364
|
+
useEffect2(() => {
|
|
365
|
+
const unsubscribe = addEventListener((event) => {
|
|
366
|
+
switch (event.type) {
|
|
367
|
+
case "capture":
|
|
368
|
+
setAffiliateParams(event.data || null);
|
|
369
|
+
break;
|
|
370
|
+
case "clear":
|
|
371
|
+
case "expire":
|
|
372
|
+
setAffiliateParams(null);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
return unsubscribe;
|
|
377
|
+
}, []);
|
|
378
|
+
useEffect2(() => {
|
|
379
|
+
refreshParams();
|
|
380
|
+
}, [refreshParams]);
|
|
381
|
+
return {
|
|
382
|
+
affiliateParams,
|
|
383
|
+
isLoading,
|
|
384
|
+
error,
|
|
385
|
+
captureParams,
|
|
386
|
+
clearParams,
|
|
387
|
+
refreshParams
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function useHasAffiliateData() {
|
|
391
|
+
const [hasData, setHasData] = useState(false);
|
|
392
|
+
useEffect2(() => {
|
|
393
|
+
const checkData = () => {
|
|
394
|
+
setHasData(hasAffiliateData());
|
|
395
|
+
};
|
|
396
|
+
checkData();
|
|
397
|
+
const unsubscribe = addEventListener(() => {
|
|
398
|
+
checkData();
|
|
399
|
+
});
|
|
400
|
+
return unsubscribe;
|
|
401
|
+
}, []);
|
|
402
|
+
return hasData;
|
|
403
|
+
}
|
|
404
|
+
function useAffiliateSource() {
|
|
405
|
+
const [source, setSource] = useState(null);
|
|
406
|
+
useEffect2(() => {
|
|
407
|
+
const updateSource = () => {
|
|
408
|
+
setSource(getAffiliateSource());
|
|
409
|
+
};
|
|
410
|
+
updateSource();
|
|
411
|
+
const unsubscribe = addEventListener(() => {
|
|
412
|
+
updateSource();
|
|
413
|
+
});
|
|
414
|
+
return unsubscribe;
|
|
415
|
+
}, []);
|
|
416
|
+
return source;
|
|
417
|
+
}
|
|
418
|
+
function useAutoCapture(config) {
|
|
419
|
+
const hasRun = useRef(false);
|
|
420
|
+
useEffect2(() => {
|
|
421
|
+
if (hasRun.current) return;
|
|
422
|
+
hasRun.current = true;
|
|
423
|
+
if (config) {
|
|
424
|
+
configureAffiliateTracker(config);
|
|
425
|
+
}
|
|
426
|
+
captureAffiliateParams();
|
|
427
|
+
}, [config]);
|
|
428
|
+
}
|
|
429
|
+
function useAffiliateEvents(callback, eventTypes) {
|
|
430
|
+
const callbackRef = useRef(callback);
|
|
431
|
+
const eventTypesRef = useRef(eventTypes);
|
|
432
|
+
useEffect2(() => {
|
|
433
|
+
callbackRef.current = callback;
|
|
434
|
+
eventTypesRef.current = eventTypes;
|
|
435
|
+
});
|
|
436
|
+
useEffect2(() => {
|
|
437
|
+
const unsubscribe = addEventListener((event) => {
|
|
438
|
+
if (!eventTypesRef.current || eventTypesRef.current.includes(event.type)) {
|
|
439
|
+
callbackRef.current(event);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
return unsubscribe;
|
|
443
|
+
}, []);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/affiliate/utils.ts
|
|
447
|
+
var AffiliateAnalytics = class {
|
|
448
|
+
/**
|
|
449
|
+
* Send affiliate data to Google Analytics 4
|
|
450
|
+
*/
|
|
451
|
+
static sendToGA4(eventName = "affiliate_attribution", customParams) {
|
|
452
|
+
const affiliateData = getAffiliateParams();
|
|
453
|
+
if (!affiliateData || typeof window === "undefined") return;
|
|
454
|
+
if (typeof window.gtag === "function") {
|
|
455
|
+
const eventParams = {
|
|
456
|
+
utm_source: affiliateData.utm_source,
|
|
457
|
+
utm_medium: affiliateData.utm_medium,
|
|
458
|
+
utm_campaign: affiliateData.utm_campaign,
|
|
459
|
+
utm_term: affiliateData.utm_term,
|
|
460
|
+
utm_content: affiliateData.utm_content,
|
|
461
|
+
affiliate_id: affiliateData.affiliate_id,
|
|
462
|
+
attribution_type: affiliateData.attribution,
|
|
463
|
+
session_id: affiliateData.sessionId,
|
|
464
|
+
...customParams
|
|
465
|
+
};
|
|
466
|
+
Object.keys(eventParams).forEach((key) => {
|
|
467
|
+
if (eventParams[key] === void 0) {
|
|
468
|
+
delete eventParams[key];
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
window.gtag("event", eventName, eventParams);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Send affiliate data to Facebook Pixel
|
|
476
|
+
*/
|
|
477
|
+
static sendToFacebookPixel(eventName = "AffiliateAttribution", customParams) {
|
|
478
|
+
const affiliateData = getAffiliateParams();
|
|
479
|
+
if (!affiliateData || typeof window === "undefined") return;
|
|
480
|
+
if (typeof window.fbq === "function") {
|
|
481
|
+
const eventParams = {
|
|
482
|
+
utm_source: affiliateData.utm_source,
|
|
483
|
+
utm_medium: affiliateData.utm_medium,
|
|
484
|
+
utm_campaign: affiliateData.utm_campaign,
|
|
485
|
+
fbclid: affiliateData.fbclid,
|
|
486
|
+
...customParams
|
|
487
|
+
};
|
|
488
|
+
Object.keys(eventParams).forEach((key) => {
|
|
489
|
+
if (eventParams[key] === void 0) {
|
|
490
|
+
delete eventParams[key];
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
window.fbq("trackCustom", eventName, eventParams);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Send affiliate data to any custom analytics endpoint
|
|
498
|
+
*/
|
|
499
|
+
static async sendToCustomEndpoint(endpoint, options = {}) {
|
|
500
|
+
const affiliateData = getAffiliateParams();
|
|
501
|
+
if (!affiliateData) return;
|
|
502
|
+
const { method = "POST", headers = {}, customData = {} } = options;
|
|
503
|
+
try {
|
|
504
|
+
await fetch(endpoint, {
|
|
505
|
+
method,
|
|
506
|
+
headers: {
|
|
507
|
+
"Content-Type": "application/json",
|
|
508
|
+
...headers
|
|
509
|
+
},
|
|
510
|
+
body: JSON.stringify({
|
|
511
|
+
...affiliateData,
|
|
512
|
+
...customData
|
|
513
|
+
})
|
|
514
|
+
});
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error("Failed to send affiliate data to custom endpoint:", error);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
var AffiliateUrlUtils = class {
|
|
521
|
+
/**
|
|
522
|
+
* Create affiliate links with current parameters
|
|
523
|
+
*/
|
|
524
|
+
static createAffiliateLink(baseUrl, additionalParams) {
|
|
525
|
+
let url = appendAffiliateParams(baseUrl);
|
|
526
|
+
if (additionalParams) {
|
|
527
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
528
|
+
const params = new URLSearchParams(additionalParams);
|
|
529
|
+
url = `${url}${separator}${params.toString()}`;
|
|
530
|
+
}
|
|
531
|
+
return url;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Extract affiliate parameters from a URL
|
|
535
|
+
*/
|
|
536
|
+
static extractFromUrl(url) {
|
|
537
|
+
try {
|
|
538
|
+
const urlObj = new URL(url);
|
|
539
|
+
const params = {};
|
|
540
|
+
const affiliateKeys = ALL_AFFILIATE_PARAMETERS;
|
|
541
|
+
affiliateKeys.forEach((key) => {
|
|
542
|
+
const value = urlObj.searchParams.get(key);
|
|
543
|
+
if (value) {
|
|
544
|
+
params[key] = value;
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
return params;
|
|
548
|
+
} catch {
|
|
549
|
+
return {};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Clean URL by removing affiliate parameters
|
|
554
|
+
*/
|
|
555
|
+
static cleanUrl(url) {
|
|
556
|
+
try {
|
|
557
|
+
const urlObj = new URL(url);
|
|
558
|
+
const affiliateKeys = ALL_AFFILIATE_PARAMETERS;
|
|
559
|
+
affiliateKeys.forEach((key) => {
|
|
560
|
+
urlObj.searchParams.delete(key);
|
|
561
|
+
});
|
|
562
|
+
return urlObj.toString();
|
|
563
|
+
} catch {
|
|
564
|
+
return url;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
var AffiliateAttribution = class {
|
|
569
|
+
/**
|
|
570
|
+
* Determine the attribution channel based on affiliate data
|
|
571
|
+
*/
|
|
572
|
+
static getAttributionChannel(data) {
|
|
573
|
+
const affiliateData = data || getAffiliateParams();
|
|
574
|
+
if (!affiliateData) return ATTRIBUTION_CHANNELS.DIRECT;
|
|
575
|
+
if (affiliateData.gclid || affiliateData.utm_medium === "cpc" || affiliateData.utm_medium === "ppc") {
|
|
576
|
+
return ATTRIBUTION_CHANNELS.PAID_SEARCH;
|
|
577
|
+
}
|
|
578
|
+
if (affiliateData.fbclid || affiliateData.ttclid || affiliateData.twclid || affiliateData.li_fat_id || affiliateData.utm_source && SOCIAL_PLATFORMS.some(
|
|
579
|
+
(platform) => affiliateData.utm_source?.includes(platform)
|
|
580
|
+
)) {
|
|
581
|
+
return ATTRIBUTION_CHANNELS.SOCIAL;
|
|
582
|
+
}
|
|
583
|
+
if (affiliateData.utm_medium === "email" || affiliateData.utm_source === "email") {
|
|
584
|
+
return ATTRIBUTION_CHANNELS.EMAIL;
|
|
585
|
+
}
|
|
586
|
+
if (affiliateData.affiliate_id || affiliateData.utm_medium === "affiliate" || affiliateData.utm_medium === "referral") {
|
|
587
|
+
return ATTRIBUTION_CHANNELS.AFFILIATE;
|
|
588
|
+
}
|
|
589
|
+
if (affiliateData.utm_medium === "organic" || affiliateData.referrer && this.isSearchEngine(affiliateData.referrer)) {
|
|
590
|
+
return ATTRIBUTION_CHANNELS.ORGANIC_SEARCH;
|
|
591
|
+
}
|
|
592
|
+
if (affiliateData.utm_medium === "display" || affiliateData.utm_medium === "banner") {
|
|
593
|
+
return ATTRIBUTION_CHANNELS.DISPLAY;
|
|
594
|
+
}
|
|
595
|
+
if (affiliateData.referrer) {
|
|
596
|
+
return ATTRIBUTION_CHANNELS.REFERRAL;
|
|
597
|
+
}
|
|
598
|
+
return ATTRIBUTION_CHANNELS.DIRECT;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Check if a URL is from a search engine
|
|
602
|
+
*/
|
|
603
|
+
static isSearchEngine(url) {
|
|
604
|
+
const searchEngines = SEARCH_ENGINES;
|
|
605
|
+
try {
|
|
606
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
607
|
+
return searchEngines.some((engine) => hostname.includes(engine));
|
|
608
|
+
} catch {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Get attribution score (0-100) based on data quality
|
|
614
|
+
*/
|
|
615
|
+
static getAttributionScore(data) {
|
|
616
|
+
const affiliateData = data || getAffiliateParams();
|
|
617
|
+
if (!affiliateData) return 0;
|
|
618
|
+
let score = 0;
|
|
619
|
+
score += 20;
|
|
620
|
+
if (affiliateData.utm_source) score += 20;
|
|
621
|
+
if (affiliateData.utm_medium) score += 20;
|
|
622
|
+
if (affiliateData.utm_campaign) score += 15;
|
|
623
|
+
if (affiliateData.gclid || affiliateData.fbclid || affiliateData.msclkid)
|
|
624
|
+
score += 25;
|
|
625
|
+
if (affiliateData.referrer) score += 10;
|
|
626
|
+
if (affiliateData.utm_term) score += 5;
|
|
627
|
+
if (affiliateData.utm_content) score += 5;
|
|
628
|
+
return Math.min(score, 100);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
var AffiliateTestUtils = class {
|
|
632
|
+
/**
|
|
633
|
+
* Mock affiliate data for testing
|
|
634
|
+
*/
|
|
635
|
+
static mockAffiliateData(params = {}) {
|
|
636
|
+
return {
|
|
637
|
+
utm_source: "test_source",
|
|
638
|
+
utm_medium: "test_medium",
|
|
639
|
+
utm_campaign: "test_campaign",
|
|
640
|
+
timestamp: Date.now(),
|
|
641
|
+
sessionId: "test-session-id",
|
|
642
|
+
attribution: "last-touch",
|
|
643
|
+
url: "https://example.com?utm_source=test_source&utm_medium=test_medium",
|
|
644
|
+
...params
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Create test URL with affiliate parameters
|
|
649
|
+
*/
|
|
650
|
+
static createTestUrl(baseUrl = "https://example.com", params = {}) {
|
|
651
|
+
const defaultParams = {
|
|
652
|
+
utm_source: "test_source",
|
|
653
|
+
utm_medium: "test_medium",
|
|
654
|
+
utm_campaign: "test_campaign",
|
|
655
|
+
...params
|
|
656
|
+
};
|
|
657
|
+
const url = new URL(baseUrl);
|
|
658
|
+
Object.entries(defaultParams).forEach(([key, value]) => {
|
|
659
|
+
if (value) {
|
|
660
|
+
url.searchParams.set(key, value);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
return url.toString();
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Simulate URL navigation for testing
|
|
667
|
+
*/
|
|
668
|
+
static simulateNavigation(url) {
|
|
669
|
+
if (typeof window !== "undefined") {
|
|
670
|
+
Object.defineProperty(window, "location", {
|
|
671
|
+
value: {
|
|
672
|
+
...window.location,
|
|
673
|
+
search: new URL(url).search,
|
|
674
|
+
href: url
|
|
675
|
+
},
|
|
676
|
+
writable: true
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
var AffiliateValidation = class {
|
|
682
|
+
/**
|
|
683
|
+
* Validate affiliate parameter format
|
|
684
|
+
*/
|
|
685
|
+
static validateParams(params) {
|
|
686
|
+
const errors = [];
|
|
687
|
+
if (params.utm_source && params.utm_source.length > 100) {
|
|
688
|
+
errors.push("utm_source is too long (max 100 characters)");
|
|
689
|
+
}
|
|
690
|
+
if (params.utm_medium && params.utm_medium.length > 100) {
|
|
691
|
+
errors.push("utm_medium is too long (max 100 characters)");
|
|
692
|
+
}
|
|
693
|
+
if (params.utm_campaign && params.utm_campaign.length > 100) {
|
|
694
|
+
errors.push("utm_campaign is too long (max 100 characters)");
|
|
695
|
+
}
|
|
696
|
+
if (params.referrer) {
|
|
697
|
+
try {
|
|
698
|
+
new URL(params.referrer);
|
|
699
|
+
} catch {
|
|
700
|
+
errors.push("referrer is not a valid URL");
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
isValid: errors.length === 0,
|
|
705
|
+
errors
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Sanitize affiliate parameters
|
|
710
|
+
*/
|
|
711
|
+
static sanitizeParams(params) {
|
|
712
|
+
const sanitized = {};
|
|
713
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
714
|
+
if (typeof value === "string" && value.trim()) {
|
|
715
|
+
sanitized[key] = value.trim().replace(/[<>'"]/g, "").substring(0, 200);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
return sanitized;
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
export {
|
|
723
|
+
UTM_PARAMETERS,
|
|
724
|
+
CLICK_ID_PARAMETERS,
|
|
725
|
+
AFFILIATE_PARAMETERS,
|
|
726
|
+
ALL_AFFILIATE_PARAMETERS,
|
|
727
|
+
STORAGE_CONFIG,
|
|
728
|
+
ATTRIBUTION_MODELS,
|
|
729
|
+
STORAGE_TYPES,
|
|
730
|
+
EVENT_TYPES,
|
|
731
|
+
ATTRIBUTION_CHANNELS,
|
|
732
|
+
SEARCH_ENGINES,
|
|
733
|
+
SOCIAL_PLATFORMS,
|
|
734
|
+
DEFAULT_CONFIG,
|
|
735
|
+
configureAffiliateTracker,
|
|
736
|
+
addEventListener,
|
|
737
|
+
captureAffiliateParams,
|
|
738
|
+
getAffiliateParams,
|
|
739
|
+
clearAffiliateParams,
|
|
740
|
+
getAffiliateParamsAsUrlString,
|
|
741
|
+
appendAffiliateParams,
|
|
742
|
+
hasAffiliateData,
|
|
743
|
+
getAffiliateSource,
|
|
744
|
+
getConfig,
|
|
745
|
+
resetConfig,
|
|
746
|
+
AffiliateTracker_default,
|
|
747
|
+
useAffiliateTracker,
|
|
748
|
+
useHasAffiliateData,
|
|
749
|
+
useAffiliateSource,
|
|
750
|
+
useAutoCapture,
|
|
751
|
+
useAffiliateEvents,
|
|
752
|
+
AffiliateAnalytics,
|
|
753
|
+
AffiliateUrlUtils,
|
|
754
|
+
AffiliateAttribution,
|
|
755
|
+
AffiliateTestUtils,
|
|
756
|
+
AffiliateValidation
|
|
757
|
+
};
|
|
758
|
+
//# sourceMappingURL=chunk-TNXTKEGS.mjs.map
|