@windrun-huaiin/third-ui 14.0.1 → 14.0.2
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/clerk/fingerprint/fingerprint-client.d.ts +22 -0
- package/dist/clerk/fingerprint/fingerprint-client.js +190 -16
- package/dist/clerk/fingerprint/fingerprint-client.mjs +191 -18
- package/dist/clerk/fingerprint/fingerprint-shared.d.ts +6 -0
- package/dist/clerk/fingerprint/fingerprint-shared.js +9 -0
- package/dist/clerk/fingerprint/fingerprint-shared.mjs +7 -1
- package/dist/clerk/fingerprint/index.js +4 -0
- package/dist/clerk/fingerprint/index.mjs +2 -2
- package/dist/clerk/fingerprint/server.js +3 -0
- package/dist/clerk/fingerprint/server.mjs +1 -1
- package/dist/clerk/fingerprint/use-fingerprint.js +2 -0
- package/dist/clerk/fingerprint/use-fingerprint.mjs +3 -1
- package/dist/main/delayed-img.d.ts +7 -0
- package/dist/main/delayed-img.js +39 -0
- package/dist/main/delayed-img.mjs +37 -0
- package/dist/main/index.d.ts +1 -0
- package/dist/main/index.js +2 -0
- package/dist/main/index.mjs +1 -0
- package/package.json +1 -1
- package/src/clerk/fingerprint/fingerprint-client.ts +242 -18
- package/src/clerk/fingerprint/fingerprint-shared.ts +7 -1
- package/src/clerk/fingerprint/use-fingerprint.ts +4 -1
- package/src/main/delayed-img.tsx +88 -0
- package/src/main/index.ts +1 -0
|
@@ -3,6 +3,27 @@
|
|
|
3
3
|
* 客户端专用的指纹生成和管理逻辑
|
|
4
4
|
* 只能在浏览器环境中使用
|
|
5
5
|
*/
|
|
6
|
+
type FirstTouchData = {
|
|
7
|
+
landingUrl?: string;
|
|
8
|
+
landingPath?: string;
|
|
9
|
+
landingHost?: string;
|
|
10
|
+
externalReferrer?: string;
|
|
11
|
+
capturedAt?: string;
|
|
12
|
+
ref?: string;
|
|
13
|
+
utmSource?: string;
|
|
14
|
+
utmMedium?: string;
|
|
15
|
+
utmCampaign?: string;
|
|
16
|
+
utmTerm?: string;
|
|
17
|
+
utmContent?: string;
|
|
18
|
+
utmId?: string;
|
|
19
|
+
gclid?: string;
|
|
20
|
+
fbclid?: string;
|
|
21
|
+
msclkid?: string;
|
|
22
|
+
ttclid?: string;
|
|
23
|
+
twclid?: string;
|
|
24
|
+
liFatId?: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function getOrCreateFirstTouchData(): FirstTouchData | null;
|
|
6
27
|
/**
|
|
7
28
|
* 生成基于真实浏览器特征的fingerprint ID
|
|
8
29
|
* 使用 FingerprintJS 收集浏览器特征并生成唯一标识
|
|
@@ -37,3 +58,4 @@ export declare function useFingerprintHeaders(): () => Promise<Record<string, st
|
|
|
37
58
|
* Create a fetch wrapper that automatically includes fingerprint headers
|
|
38
59
|
*/
|
|
39
60
|
export declare function createFingerprintFetch(): (url: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
61
|
+
export {};
|
|
@@ -9,6 +9,8 @@ var fingerprintShared = require('./fingerprint-shared.js');
|
|
|
9
9
|
* 客户端专用的指纹生成和管理逻辑
|
|
10
10
|
* 只能在浏览器环境中使用
|
|
11
11
|
*/
|
|
12
|
+
const FIRST_TOUCH_MAX_LENGTH = 2048;
|
|
13
|
+
const FIRST_TOUCH_COOKIE_DAYS = 30;
|
|
12
14
|
/**
|
|
13
15
|
* 检查浏览器存储(localStorage 和 cookie)中的指纹 ID
|
|
14
16
|
* 返回有效的 ID 或 null
|
|
@@ -18,7 +20,7 @@ function checkStoredFingerprintId() {
|
|
|
18
20
|
return null;
|
|
19
21
|
}
|
|
20
22
|
// 优先检查 localStorage
|
|
21
|
-
const localStorageId =
|
|
23
|
+
const localStorageId = getLocalStorageValue(fingerprintShared.FINGERPRINT_STORAGE_KEY);
|
|
22
24
|
if (localStorageId && fingerprintShared.isValidFingerprintId(localStorageId)) {
|
|
23
25
|
return localStorageId;
|
|
24
26
|
}
|
|
@@ -26,11 +28,129 @@ function checkStoredFingerprintId() {
|
|
|
26
28
|
const cookieId = getCookieValue(fingerprintShared.FINGERPRINT_COOKIE_NAME);
|
|
27
29
|
if (cookieId && fingerprintShared.isValidFingerprintId(cookieId)) {
|
|
28
30
|
// 同步到 localStorage
|
|
29
|
-
|
|
31
|
+
setLocalStorageValue(fingerprintShared.FINGERPRINT_STORAGE_KEY, cookieId);
|
|
30
32
|
return cookieId;
|
|
31
33
|
}
|
|
32
34
|
return null;
|
|
33
35
|
}
|
|
36
|
+
function normalizeString(value, maxLength = FIRST_TOUCH_MAX_LENGTH) {
|
|
37
|
+
if (!value) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const trimmed = value.trim();
|
|
41
|
+
if (!trimmed) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return trimmed.length > maxLength ? trimmed.slice(0, maxLength) : trimmed;
|
|
45
|
+
}
|
|
46
|
+
function readFirstTouchFromStorage() {
|
|
47
|
+
if (typeof window === 'undefined') {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const localStorageValue = getLocalStorageValue(fingerprintShared.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY);
|
|
51
|
+
if (localStorageValue) {
|
|
52
|
+
const parsed = parseFirstTouchValue(localStorageValue);
|
|
53
|
+
if (parsed) {
|
|
54
|
+
syncFirstTouchStorage(parsed);
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const cookieValue = getCookieValue(fingerprintShared.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME);
|
|
59
|
+
if (cookieValue) {
|
|
60
|
+
const parsed = parseFirstTouchValue(cookieValue);
|
|
61
|
+
if (parsed) {
|
|
62
|
+
syncFirstTouchStorage(parsed);
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function parseFirstTouchValue(value) {
|
|
69
|
+
try {
|
|
70
|
+
const decoded = decodeURIComponent(value);
|
|
71
|
+
const parsed = JSON.parse(decoded);
|
|
72
|
+
return sanitizeFirstTouchData(parsed);
|
|
73
|
+
}
|
|
74
|
+
catch (_a) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function sanitizeFirstTouchData(data) {
|
|
79
|
+
if (!data) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const sanitized = {
|
|
83
|
+
landingUrl: normalizeString(data.landingUrl),
|
|
84
|
+
landingPath: normalizeString(data.landingPath, 512),
|
|
85
|
+
landingHost: normalizeString(data.landingHost, 255),
|
|
86
|
+
externalReferrer: normalizeString(data.externalReferrer),
|
|
87
|
+
capturedAt: normalizeString(data.capturedAt, 64),
|
|
88
|
+
ref: normalizeString(data.ref, 512),
|
|
89
|
+
utmSource: normalizeString(data.utmSource, 512),
|
|
90
|
+
utmMedium: normalizeString(data.utmMedium, 512),
|
|
91
|
+
utmCampaign: normalizeString(data.utmCampaign, 512),
|
|
92
|
+
utmTerm: normalizeString(data.utmTerm, 512),
|
|
93
|
+
utmContent: normalizeString(data.utmContent, 512),
|
|
94
|
+
utmId: normalizeString(data.utmId, 512),
|
|
95
|
+
gclid: normalizeString(data.gclid, 512),
|
|
96
|
+
fbclid: normalizeString(data.fbclid, 512),
|
|
97
|
+
msclkid: normalizeString(data.msclkid, 512),
|
|
98
|
+
ttclid: normalizeString(data.ttclid, 512),
|
|
99
|
+
twclid: normalizeString(data.twclid, 512),
|
|
100
|
+
liFatId: normalizeString(data.liFatId, 512),
|
|
101
|
+
};
|
|
102
|
+
return Object.values(sanitized).some(Boolean) ? sanitized : null;
|
|
103
|
+
}
|
|
104
|
+
function serializeFirstTouchData(data) {
|
|
105
|
+
return encodeURIComponent(JSON.stringify(data));
|
|
106
|
+
}
|
|
107
|
+
function syncFirstTouchStorage(data) {
|
|
108
|
+
if (typeof window === 'undefined') {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const serialized = serializeFirstTouchData(data);
|
|
112
|
+
setLocalStorageValue(fingerprintShared.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, serialized);
|
|
113
|
+
setCookie(fingerprintShared.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, serialized, FIRST_TOUCH_COOKIE_DAYS);
|
|
114
|
+
}
|
|
115
|
+
function buildFirstTouchData() {
|
|
116
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
117
|
+
if (typeof window === 'undefined') {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const url = new URL(window.location.href);
|
|
121
|
+
const data = sanitizeFirstTouchData({
|
|
122
|
+
landingUrl: url.toString(),
|
|
123
|
+
landingPath: url.pathname,
|
|
124
|
+
landingHost: url.host,
|
|
125
|
+
externalReferrer: document.referrer || undefined,
|
|
126
|
+
capturedAt: new Date().toISOString(),
|
|
127
|
+
ref: (_a = url.searchParams.get('ref')) !== null && _a !== void 0 ? _a : undefined,
|
|
128
|
+
utmSource: (_b = url.searchParams.get('utm_source')) !== null && _b !== void 0 ? _b : undefined,
|
|
129
|
+
utmMedium: (_c = url.searchParams.get('utm_medium')) !== null && _c !== void 0 ? _c : undefined,
|
|
130
|
+
utmCampaign: (_d = url.searchParams.get('utm_campaign')) !== null && _d !== void 0 ? _d : undefined,
|
|
131
|
+
utmTerm: (_e = url.searchParams.get('utm_term')) !== null && _e !== void 0 ? _e : undefined,
|
|
132
|
+
utmContent: (_f = url.searchParams.get('utm_content')) !== null && _f !== void 0 ? _f : undefined,
|
|
133
|
+
utmId: (_g = url.searchParams.get('utm_id')) !== null && _g !== void 0 ? _g : undefined,
|
|
134
|
+
gclid: (_h = url.searchParams.get('gclid')) !== null && _h !== void 0 ? _h : undefined,
|
|
135
|
+
fbclid: (_j = url.searchParams.get('fbclid')) !== null && _j !== void 0 ? _j : undefined,
|
|
136
|
+
msclkid: (_k = url.searchParams.get('msclkid')) !== null && _k !== void 0 ? _k : undefined,
|
|
137
|
+
ttclid: (_l = url.searchParams.get('ttclid')) !== null && _l !== void 0 ? _l : undefined,
|
|
138
|
+
twclid: (_m = url.searchParams.get('twclid')) !== null && _m !== void 0 ? _m : undefined,
|
|
139
|
+
liFatId: (_o = url.searchParams.get('li_fat_id')) !== null && _o !== void 0 ? _o : undefined,
|
|
140
|
+
});
|
|
141
|
+
return data;
|
|
142
|
+
}
|
|
143
|
+
function getOrCreateFirstTouchData() {
|
|
144
|
+
const existing = readFirstTouchFromStorage();
|
|
145
|
+
if (existing) {
|
|
146
|
+
return existing;
|
|
147
|
+
}
|
|
148
|
+
const created = buildFirstTouchData();
|
|
149
|
+
if (created) {
|
|
150
|
+
syncFirstTouchStorage(created);
|
|
151
|
+
}
|
|
152
|
+
return created;
|
|
153
|
+
}
|
|
34
154
|
/**
|
|
35
155
|
* 生成基于真实浏览器特征的fingerprint ID
|
|
36
156
|
* 使用 FingerprintJS 收集浏览器特征并生成唯一标识
|
|
@@ -52,7 +172,7 @@ function generateFingerprintId() {
|
|
|
52
172
|
const result = yield fp.get();
|
|
53
173
|
const fingerprintId = `fp_${result.visitorId}`;
|
|
54
174
|
// 存储到 localStorage 和 cookie
|
|
55
|
-
|
|
175
|
+
setLocalStorageValue(fingerprintShared.FINGERPRINT_STORAGE_KEY, fingerprintId);
|
|
56
176
|
setCookie(fingerprintShared.FINGERPRINT_COOKIE_NAME, fingerprintId, 365);
|
|
57
177
|
console.log('Generated new fingerprint ID:', fingerprintId);
|
|
58
178
|
return fingerprintId;
|
|
@@ -61,7 +181,7 @@ function generateFingerprintId() {
|
|
|
61
181
|
console.warn('Failed to generate fingerprint with FingerprintJS:', error);
|
|
62
182
|
// 降级方案:生成基于时间戳和随机数的 ID
|
|
63
183
|
const fallbackId = `fp_fallback_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
64
|
-
|
|
184
|
+
setLocalStorageValue(fingerprintShared.FINGERPRINT_STORAGE_KEY, fallbackId);
|
|
65
185
|
setCookie(fingerprintShared.FINGERPRINT_COOKIE_NAME, fallbackId, 365);
|
|
66
186
|
console.log('Generated fallback fingerprint ID:', fallbackId);
|
|
67
187
|
return fallbackId;
|
|
@@ -84,7 +204,7 @@ function setFingerprintId(fingerprintId) {
|
|
|
84
204
|
if (!fingerprintShared.isValidFingerprintId(fingerprintId)) {
|
|
85
205
|
throw new Error('Invalid fingerprint ID');
|
|
86
206
|
}
|
|
87
|
-
|
|
207
|
+
setLocalStorageValue(fingerprintShared.FINGERPRINT_STORAGE_KEY, fingerprintId);
|
|
88
208
|
setCookie(fingerprintShared.FINGERPRINT_COOKIE_NAME, fingerprintId, 365);
|
|
89
209
|
}
|
|
90
210
|
/**
|
|
@@ -94,7 +214,7 @@ function clearFingerprintId() {
|
|
|
94
214
|
if (typeof window === 'undefined') {
|
|
95
215
|
throw new Error('clearFingerprintId can only be used in browser environment');
|
|
96
216
|
}
|
|
97
|
-
|
|
217
|
+
removeLocalStorageValue(fingerprintShared.FINGERPRINT_STORAGE_KEY);
|
|
98
218
|
deleteCookie(fingerprintShared.FINGERPRINT_COOKIE_NAME);
|
|
99
219
|
}
|
|
100
220
|
/**
|
|
@@ -117,9 +237,14 @@ function getOrGenerateFingerprintId() {
|
|
|
117
237
|
function createFingerprintHeaders() {
|
|
118
238
|
return tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
119
239
|
const fingerprintId = yield getOrGenerateFingerprintId();
|
|
120
|
-
|
|
240
|
+
const headers = {
|
|
121
241
|
[fingerprintShared.FINGERPRINT_HEADER_NAME]: fingerprintId,
|
|
122
242
|
};
|
|
243
|
+
const firstTouch = getOrCreateFirstTouchData();
|
|
244
|
+
if (firstTouch) {
|
|
245
|
+
headers[fingerprintShared.FINGERPRINT_FIRST_TOUCH_HEADER] = serializeFirstTouchData(firstTouch);
|
|
246
|
+
}
|
|
247
|
+
return headers;
|
|
123
248
|
});
|
|
124
249
|
}
|
|
125
250
|
/**
|
|
@@ -144,26 +269,74 @@ function getCookieValue(name) {
|
|
|
144
269
|
if (typeof document === 'undefined') {
|
|
145
270
|
return null;
|
|
146
271
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
272
|
+
try {
|
|
273
|
+
const value = `; ${document.cookie}`;
|
|
274
|
+
const parts = value.split(`; ${name}=`);
|
|
275
|
+
if (parts.length === 2) {
|
|
276
|
+
return ((_a = parts.pop()) === null || _a === void 0 ? void 0 : _a.split(';').shift()) || null;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
catch (_b) {
|
|
281
|
+
return null;
|
|
151
282
|
}
|
|
152
|
-
return null;
|
|
153
283
|
}
|
|
154
284
|
function setCookie(name, value, days) {
|
|
155
285
|
if (typeof document === 'undefined') {
|
|
156
286
|
return;
|
|
157
287
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
288
|
+
try {
|
|
289
|
+
const expires = new Date();
|
|
290
|
+
expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
|
|
291
|
+
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
|
|
292
|
+
}
|
|
293
|
+
catch (_a) {
|
|
294
|
+
// Ignore storage failures so attribution never blocks page flow.
|
|
295
|
+
}
|
|
161
296
|
}
|
|
162
297
|
function deleteCookie(name) {
|
|
163
298
|
if (typeof document === 'undefined') {
|
|
164
299
|
return;
|
|
165
300
|
}
|
|
166
|
-
|
|
301
|
+
try {
|
|
302
|
+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
|
|
303
|
+
}
|
|
304
|
+
catch (_a) {
|
|
305
|
+
// Ignore storage failures so attribution never blocks page flow.
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function getLocalStorageValue(key) {
|
|
309
|
+
if (typeof window === 'undefined') {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
return window.localStorage.getItem(key);
|
|
314
|
+
}
|
|
315
|
+
catch (_a) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function setLocalStorageValue(key, value) {
|
|
320
|
+
if (typeof window === 'undefined') {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
window.localStorage.setItem(key, value);
|
|
325
|
+
}
|
|
326
|
+
catch (_a) {
|
|
327
|
+
// Ignore storage failures so attribution never blocks page flow.
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function removeLocalStorageValue(key) {
|
|
331
|
+
if (typeof window === 'undefined') {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
window.localStorage.removeItem(key);
|
|
336
|
+
}
|
|
337
|
+
catch (_a) {
|
|
338
|
+
// Ignore storage failures so attribution never blocks page flow.
|
|
339
|
+
}
|
|
167
340
|
}
|
|
168
341
|
|
|
169
342
|
exports.clearFingerprintId = clearFingerprintId;
|
|
@@ -171,6 +344,7 @@ exports.createFingerprintFetch = createFingerprintFetch;
|
|
|
171
344
|
exports.createFingerprintHeaders = createFingerprintHeaders;
|
|
172
345
|
exports.generateFingerprintId = generateFingerprintId;
|
|
173
346
|
exports.getFingerprintId = getFingerprintId;
|
|
347
|
+
exports.getOrCreateFirstTouchData = getOrCreateFirstTouchData;
|
|
174
348
|
exports.getOrGenerateFingerprintId = getOrGenerateFingerprintId;
|
|
175
349
|
exports.setFingerprintId = setFingerprintId;
|
|
176
350
|
exports.useFingerprintHeaders = useFingerprintHeaders;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
|
|
2
2
|
import index from '../../node_modules/.pnpm/@fingerprintjs_fingerprintjs@5.1.0/node_modules/@fingerprintjs/fingerprintjs/dist/fp.esm.mjs';
|
|
3
|
-
import { isValidFingerprintId, FINGERPRINT_STORAGE_KEY, FINGERPRINT_COOKIE_NAME, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
|
|
3
|
+
import { isValidFingerprintId, FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, FINGERPRINT_STORAGE_KEY, FINGERPRINT_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_HEADER, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Fingerprint Client Utilities
|
|
7
7
|
* 客户端专用的指纹生成和管理逻辑
|
|
8
8
|
* 只能在浏览器环境中使用
|
|
9
9
|
*/
|
|
10
|
+
const FIRST_TOUCH_MAX_LENGTH = 2048;
|
|
11
|
+
const FIRST_TOUCH_COOKIE_DAYS = 30;
|
|
10
12
|
/**
|
|
11
13
|
* 检查浏览器存储(localStorage 和 cookie)中的指纹 ID
|
|
12
14
|
* 返回有效的 ID 或 null
|
|
@@ -16,7 +18,7 @@ function checkStoredFingerprintId() {
|
|
|
16
18
|
return null;
|
|
17
19
|
}
|
|
18
20
|
// 优先检查 localStorage
|
|
19
|
-
const localStorageId =
|
|
21
|
+
const localStorageId = getLocalStorageValue(FINGERPRINT_STORAGE_KEY);
|
|
20
22
|
if (localStorageId && isValidFingerprintId(localStorageId)) {
|
|
21
23
|
return localStorageId;
|
|
22
24
|
}
|
|
@@ -24,11 +26,129 @@ function checkStoredFingerprintId() {
|
|
|
24
26
|
const cookieId = getCookieValue(FINGERPRINT_COOKIE_NAME);
|
|
25
27
|
if (cookieId && isValidFingerprintId(cookieId)) {
|
|
26
28
|
// 同步到 localStorage
|
|
27
|
-
|
|
29
|
+
setLocalStorageValue(FINGERPRINT_STORAGE_KEY, cookieId);
|
|
28
30
|
return cookieId;
|
|
29
31
|
}
|
|
30
32
|
return null;
|
|
31
33
|
}
|
|
34
|
+
function normalizeString(value, maxLength = FIRST_TOUCH_MAX_LENGTH) {
|
|
35
|
+
if (!value) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
if (!trimmed) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return trimmed.length > maxLength ? trimmed.slice(0, maxLength) : trimmed;
|
|
43
|
+
}
|
|
44
|
+
function readFirstTouchFromStorage() {
|
|
45
|
+
if (typeof window === 'undefined') {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const localStorageValue = getLocalStorageValue(FINGERPRINT_FIRST_TOUCH_STORAGE_KEY);
|
|
49
|
+
if (localStorageValue) {
|
|
50
|
+
const parsed = parseFirstTouchValue(localStorageValue);
|
|
51
|
+
if (parsed) {
|
|
52
|
+
syncFirstTouchStorage(parsed);
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const cookieValue = getCookieValue(FINGERPRINT_FIRST_TOUCH_COOKIE_NAME);
|
|
57
|
+
if (cookieValue) {
|
|
58
|
+
const parsed = parseFirstTouchValue(cookieValue);
|
|
59
|
+
if (parsed) {
|
|
60
|
+
syncFirstTouchStorage(parsed);
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function parseFirstTouchValue(value) {
|
|
67
|
+
try {
|
|
68
|
+
const decoded = decodeURIComponent(value);
|
|
69
|
+
const parsed = JSON.parse(decoded);
|
|
70
|
+
return sanitizeFirstTouchData(parsed);
|
|
71
|
+
}
|
|
72
|
+
catch (_a) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function sanitizeFirstTouchData(data) {
|
|
77
|
+
if (!data) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const sanitized = {
|
|
81
|
+
landingUrl: normalizeString(data.landingUrl),
|
|
82
|
+
landingPath: normalizeString(data.landingPath, 512),
|
|
83
|
+
landingHost: normalizeString(data.landingHost, 255),
|
|
84
|
+
externalReferrer: normalizeString(data.externalReferrer),
|
|
85
|
+
capturedAt: normalizeString(data.capturedAt, 64),
|
|
86
|
+
ref: normalizeString(data.ref, 512),
|
|
87
|
+
utmSource: normalizeString(data.utmSource, 512),
|
|
88
|
+
utmMedium: normalizeString(data.utmMedium, 512),
|
|
89
|
+
utmCampaign: normalizeString(data.utmCampaign, 512),
|
|
90
|
+
utmTerm: normalizeString(data.utmTerm, 512),
|
|
91
|
+
utmContent: normalizeString(data.utmContent, 512),
|
|
92
|
+
utmId: normalizeString(data.utmId, 512),
|
|
93
|
+
gclid: normalizeString(data.gclid, 512),
|
|
94
|
+
fbclid: normalizeString(data.fbclid, 512),
|
|
95
|
+
msclkid: normalizeString(data.msclkid, 512),
|
|
96
|
+
ttclid: normalizeString(data.ttclid, 512),
|
|
97
|
+
twclid: normalizeString(data.twclid, 512),
|
|
98
|
+
liFatId: normalizeString(data.liFatId, 512),
|
|
99
|
+
};
|
|
100
|
+
return Object.values(sanitized).some(Boolean) ? sanitized : null;
|
|
101
|
+
}
|
|
102
|
+
function serializeFirstTouchData(data) {
|
|
103
|
+
return encodeURIComponent(JSON.stringify(data));
|
|
104
|
+
}
|
|
105
|
+
function syncFirstTouchStorage(data) {
|
|
106
|
+
if (typeof window === 'undefined') {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const serialized = serializeFirstTouchData(data);
|
|
110
|
+
setLocalStorageValue(FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, serialized);
|
|
111
|
+
setCookie(FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, serialized, FIRST_TOUCH_COOKIE_DAYS);
|
|
112
|
+
}
|
|
113
|
+
function buildFirstTouchData() {
|
|
114
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
115
|
+
if (typeof window === 'undefined') {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const url = new URL(window.location.href);
|
|
119
|
+
const data = sanitizeFirstTouchData({
|
|
120
|
+
landingUrl: url.toString(),
|
|
121
|
+
landingPath: url.pathname,
|
|
122
|
+
landingHost: url.host,
|
|
123
|
+
externalReferrer: document.referrer || undefined,
|
|
124
|
+
capturedAt: new Date().toISOString(),
|
|
125
|
+
ref: (_a = url.searchParams.get('ref')) !== null && _a !== void 0 ? _a : undefined,
|
|
126
|
+
utmSource: (_b = url.searchParams.get('utm_source')) !== null && _b !== void 0 ? _b : undefined,
|
|
127
|
+
utmMedium: (_c = url.searchParams.get('utm_medium')) !== null && _c !== void 0 ? _c : undefined,
|
|
128
|
+
utmCampaign: (_d = url.searchParams.get('utm_campaign')) !== null && _d !== void 0 ? _d : undefined,
|
|
129
|
+
utmTerm: (_e = url.searchParams.get('utm_term')) !== null && _e !== void 0 ? _e : undefined,
|
|
130
|
+
utmContent: (_f = url.searchParams.get('utm_content')) !== null && _f !== void 0 ? _f : undefined,
|
|
131
|
+
utmId: (_g = url.searchParams.get('utm_id')) !== null && _g !== void 0 ? _g : undefined,
|
|
132
|
+
gclid: (_h = url.searchParams.get('gclid')) !== null && _h !== void 0 ? _h : undefined,
|
|
133
|
+
fbclid: (_j = url.searchParams.get('fbclid')) !== null && _j !== void 0 ? _j : undefined,
|
|
134
|
+
msclkid: (_k = url.searchParams.get('msclkid')) !== null && _k !== void 0 ? _k : undefined,
|
|
135
|
+
ttclid: (_l = url.searchParams.get('ttclid')) !== null && _l !== void 0 ? _l : undefined,
|
|
136
|
+
twclid: (_m = url.searchParams.get('twclid')) !== null && _m !== void 0 ? _m : undefined,
|
|
137
|
+
liFatId: (_o = url.searchParams.get('li_fat_id')) !== null && _o !== void 0 ? _o : undefined,
|
|
138
|
+
});
|
|
139
|
+
return data;
|
|
140
|
+
}
|
|
141
|
+
function getOrCreateFirstTouchData() {
|
|
142
|
+
const existing = readFirstTouchFromStorage();
|
|
143
|
+
if (existing) {
|
|
144
|
+
return existing;
|
|
145
|
+
}
|
|
146
|
+
const created = buildFirstTouchData();
|
|
147
|
+
if (created) {
|
|
148
|
+
syncFirstTouchStorage(created);
|
|
149
|
+
}
|
|
150
|
+
return created;
|
|
151
|
+
}
|
|
32
152
|
/**
|
|
33
153
|
* 生成基于真实浏览器特征的fingerprint ID
|
|
34
154
|
* 使用 FingerprintJS 收集浏览器特征并生成唯一标识
|
|
@@ -50,7 +170,7 @@ function generateFingerprintId() {
|
|
|
50
170
|
const result = yield fp.get();
|
|
51
171
|
const fingerprintId = `fp_${result.visitorId}`;
|
|
52
172
|
// 存储到 localStorage 和 cookie
|
|
53
|
-
|
|
173
|
+
setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fingerprintId);
|
|
54
174
|
setCookie(FINGERPRINT_COOKIE_NAME, fingerprintId, 365);
|
|
55
175
|
console.log('Generated new fingerprint ID:', fingerprintId);
|
|
56
176
|
return fingerprintId;
|
|
@@ -59,7 +179,7 @@ function generateFingerprintId() {
|
|
|
59
179
|
console.warn('Failed to generate fingerprint with FingerprintJS:', error);
|
|
60
180
|
// 降级方案:生成基于时间戳和随机数的 ID
|
|
61
181
|
const fallbackId = `fp_fallback_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
62
|
-
|
|
182
|
+
setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fallbackId);
|
|
63
183
|
setCookie(FINGERPRINT_COOKIE_NAME, fallbackId, 365);
|
|
64
184
|
console.log('Generated fallback fingerprint ID:', fallbackId);
|
|
65
185
|
return fallbackId;
|
|
@@ -82,7 +202,7 @@ function setFingerprintId(fingerprintId) {
|
|
|
82
202
|
if (!isValidFingerprintId(fingerprintId)) {
|
|
83
203
|
throw new Error('Invalid fingerprint ID');
|
|
84
204
|
}
|
|
85
|
-
|
|
205
|
+
setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fingerprintId);
|
|
86
206
|
setCookie(FINGERPRINT_COOKIE_NAME, fingerprintId, 365);
|
|
87
207
|
}
|
|
88
208
|
/**
|
|
@@ -92,7 +212,7 @@ function clearFingerprintId() {
|
|
|
92
212
|
if (typeof window === 'undefined') {
|
|
93
213
|
throw new Error('clearFingerprintId can only be used in browser environment');
|
|
94
214
|
}
|
|
95
|
-
|
|
215
|
+
removeLocalStorageValue(FINGERPRINT_STORAGE_KEY);
|
|
96
216
|
deleteCookie(FINGERPRINT_COOKIE_NAME);
|
|
97
217
|
}
|
|
98
218
|
/**
|
|
@@ -115,9 +235,14 @@ function getOrGenerateFingerprintId() {
|
|
|
115
235
|
function createFingerprintHeaders() {
|
|
116
236
|
return __awaiter(this, void 0, void 0, function* () {
|
|
117
237
|
const fingerprintId = yield getOrGenerateFingerprintId();
|
|
118
|
-
|
|
238
|
+
const headers = {
|
|
119
239
|
[FINGERPRINT_HEADER_NAME]: fingerprintId,
|
|
120
240
|
};
|
|
241
|
+
const firstTouch = getOrCreateFirstTouchData();
|
|
242
|
+
if (firstTouch) {
|
|
243
|
+
headers[FINGERPRINT_FIRST_TOUCH_HEADER] = serializeFirstTouchData(firstTouch);
|
|
244
|
+
}
|
|
245
|
+
return headers;
|
|
121
246
|
});
|
|
122
247
|
}
|
|
123
248
|
/**
|
|
@@ -142,26 +267,74 @@ function getCookieValue(name) {
|
|
|
142
267
|
if (typeof document === 'undefined') {
|
|
143
268
|
return null;
|
|
144
269
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
270
|
+
try {
|
|
271
|
+
const value = `; ${document.cookie}`;
|
|
272
|
+
const parts = value.split(`; ${name}=`);
|
|
273
|
+
if (parts.length === 2) {
|
|
274
|
+
return ((_a = parts.pop()) === null || _a === void 0 ? void 0 : _a.split(';').shift()) || null;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
catch (_b) {
|
|
279
|
+
return null;
|
|
149
280
|
}
|
|
150
|
-
return null;
|
|
151
281
|
}
|
|
152
282
|
function setCookie(name, value, days) {
|
|
153
283
|
if (typeof document === 'undefined') {
|
|
154
284
|
return;
|
|
155
285
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
286
|
+
try {
|
|
287
|
+
const expires = new Date();
|
|
288
|
+
expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
|
|
289
|
+
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
|
|
290
|
+
}
|
|
291
|
+
catch (_a) {
|
|
292
|
+
// Ignore storage failures so attribution never blocks page flow.
|
|
293
|
+
}
|
|
159
294
|
}
|
|
160
295
|
function deleteCookie(name) {
|
|
161
296
|
if (typeof document === 'undefined') {
|
|
162
297
|
return;
|
|
163
298
|
}
|
|
164
|
-
|
|
299
|
+
try {
|
|
300
|
+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
|
|
301
|
+
}
|
|
302
|
+
catch (_a) {
|
|
303
|
+
// Ignore storage failures so attribution never blocks page flow.
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function getLocalStorageValue(key) {
|
|
307
|
+
if (typeof window === 'undefined') {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
return window.localStorage.getItem(key);
|
|
312
|
+
}
|
|
313
|
+
catch (_a) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function setLocalStorageValue(key, value) {
|
|
318
|
+
if (typeof window === 'undefined') {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
window.localStorage.setItem(key, value);
|
|
323
|
+
}
|
|
324
|
+
catch (_a) {
|
|
325
|
+
// Ignore storage failures so attribution never blocks page flow.
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function removeLocalStorageValue(key) {
|
|
329
|
+
if (typeof window === 'undefined') {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
window.localStorage.removeItem(key);
|
|
334
|
+
}
|
|
335
|
+
catch (_a) {
|
|
336
|
+
// Ignore storage failures so attribution never blocks page flow.
|
|
337
|
+
}
|
|
165
338
|
}
|
|
166
339
|
|
|
167
|
-
export { clearFingerprintId, createFingerprintFetch, createFingerprintHeaders, generateFingerprintId, getFingerprintId, getOrGenerateFingerprintId, setFingerprintId, useFingerprintHeaders };
|
|
340
|
+
export { clearFingerprintId, createFingerprintFetch, createFingerprintHeaders, generateFingerprintId, getFingerprintId, getOrCreateFirstTouchData, getOrGenerateFingerprintId, setFingerprintId, useFingerprintHeaders };
|
|
@@ -6,6 +6,9 @@ export declare const FINGERPRINT_STORAGE_KEY = "__x_fingerprint_id";
|
|
|
6
6
|
export declare const FINGERPRINT_HEADER_NAME = "x-fingerprint-id-v8";
|
|
7
7
|
export declare const FINGERPRINT_COOKIE_NAME = "__x_fingerprint_id";
|
|
8
8
|
export declare const FINGERPRINT_SOURCE_REFER = "x-source-ref";
|
|
9
|
+
export declare const FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = "__x_first_touch";
|
|
10
|
+
export declare const FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = "__x_first_touch";
|
|
11
|
+
export declare const FINGERPRINT_FIRST_TOUCH_HEADER = "x-first-touch";
|
|
9
12
|
/**
|
|
10
13
|
* 验证fingerprint ID格式
|
|
11
14
|
* 可以在客户端和服务端使用
|
|
@@ -15,4 +18,7 @@ export declare const FINGERPRINT_CONSTANTS: {
|
|
|
15
18
|
readonly STORAGE_KEY: "__x_fingerprint_id";
|
|
16
19
|
readonly HEADER_NAME: "x-fingerprint-id-v8";
|
|
17
20
|
readonly COOKIE_NAME: "__x_fingerprint_id";
|
|
21
|
+
readonly FIRST_TOUCH_STORAGE_KEY: "__x_first_touch";
|
|
22
|
+
readonly FIRST_TOUCH_COOKIE_NAME: "__x_first_touch";
|
|
23
|
+
readonly FIRST_TOUCH_HEADER: "x-first-touch";
|
|
18
24
|
};
|
|
@@ -9,6 +9,9 @@ const FINGERPRINT_STORAGE_KEY = '__x_fingerprint_id';
|
|
|
9
9
|
const FINGERPRINT_HEADER_NAME = 'x-fingerprint-id-v8';
|
|
10
10
|
const FINGERPRINT_COOKIE_NAME = '__x_fingerprint_id';
|
|
11
11
|
const FINGERPRINT_SOURCE_REFER = 'x-source-ref';
|
|
12
|
+
const FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = '__x_first_touch';
|
|
13
|
+
const FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = '__x_first_touch';
|
|
14
|
+
const FINGERPRINT_FIRST_TOUCH_HEADER = 'x-first-touch';
|
|
12
15
|
/**
|
|
13
16
|
* 验证fingerprint ID格式
|
|
14
17
|
* 可以在客户端和服务端使用
|
|
@@ -27,10 +30,16 @@ const FINGERPRINT_CONSTANTS = {
|
|
|
27
30
|
STORAGE_KEY: FINGERPRINT_STORAGE_KEY,
|
|
28
31
|
HEADER_NAME: FINGERPRINT_HEADER_NAME,
|
|
29
32
|
COOKIE_NAME: FINGERPRINT_COOKIE_NAME,
|
|
33
|
+
FIRST_TOUCH_STORAGE_KEY: FINGERPRINT_FIRST_TOUCH_STORAGE_KEY,
|
|
34
|
+
FIRST_TOUCH_COOKIE_NAME: FINGERPRINT_FIRST_TOUCH_COOKIE_NAME,
|
|
35
|
+
FIRST_TOUCH_HEADER: FINGERPRINT_FIRST_TOUCH_HEADER,
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
exports.FINGERPRINT_CONSTANTS = FINGERPRINT_CONSTANTS;
|
|
33
39
|
exports.FINGERPRINT_COOKIE_NAME = FINGERPRINT_COOKIE_NAME;
|
|
40
|
+
exports.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = FINGERPRINT_FIRST_TOUCH_COOKIE_NAME;
|
|
41
|
+
exports.FINGERPRINT_FIRST_TOUCH_HEADER = FINGERPRINT_FIRST_TOUCH_HEADER;
|
|
42
|
+
exports.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = FINGERPRINT_FIRST_TOUCH_STORAGE_KEY;
|
|
34
43
|
exports.FINGERPRINT_HEADER_NAME = FINGERPRINT_HEADER_NAME;
|
|
35
44
|
exports.FINGERPRINT_SOURCE_REFER = FINGERPRINT_SOURCE_REFER;
|
|
36
45
|
exports.FINGERPRINT_STORAGE_KEY = FINGERPRINT_STORAGE_KEY;
|
|
@@ -7,6 +7,9 @@ const FINGERPRINT_STORAGE_KEY = '__x_fingerprint_id';
|
|
|
7
7
|
const FINGERPRINT_HEADER_NAME = 'x-fingerprint-id-v8';
|
|
8
8
|
const FINGERPRINT_COOKIE_NAME = '__x_fingerprint_id';
|
|
9
9
|
const FINGERPRINT_SOURCE_REFER = 'x-source-ref';
|
|
10
|
+
const FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = '__x_first_touch';
|
|
11
|
+
const FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = '__x_first_touch';
|
|
12
|
+
const FINGERPRINT_FIRST_TOUCH_HEADER = 'x-first-touch';
|
|
10
13
|
/**
|
|
11
14
|
* 验证fingerprint ID格式
|
|
12
15
|
* 可以在客户端和服务端使用
|
|
@@ -25,6 +28,9 @@ const FINGERPRINT_CONSTANTS = {
|
|
|
25
28
|
STORAGE_KEY: FINGERPRINT_STORAGE_KEY,
|
|
26
29
|
HEADER_NAME: FINGERPRINT_HEADER_NAME,
|
|
27
30
|
COOKIE_NAME: FINGERPRINT_COOKIE_NAME,
|
|
31
|
+
FIRST_TOUCH_STORAGE_KEY: FINGERPRINT_FIRST_TOUCH_STORAGE_KEY,
|
|
32
|
+
FIRST_TOUCH_COOKIE_NAME: FINGERPRINT_FIRST_TOUCH_COOKIE_NAME,
|
|
33
|
+
FIRST_TOUCH_HEADER: FINGERPRINT_FIRST_TOUCH_HEADER,
|
|
28
34
|
};
|
|
29
35
|
|
|
30
|
-
export { FINGERPRINT_CONSTANTS, FINGERPRINT_COOKIE_NAME, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER, FINGERPRINT_STORAGE_KEY, isValidFingerprintId };
|
|
36
|
+
export { FINGERPRINT_CONSTANTS, FINGERPRINT_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_HEADER, FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER, FINGERPRINT_STORAGE_KEY, isValidFingerprintId };
|