@windrun-huaiin/third-ui 14.1.0 → 14.2.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/dist/clerk/fingerprint/fingerprint-debug.d.ts +2 -0
- package/dist/clerk/fingerprint/fingerprint-debug.js +43 -0
- package/dist/clerk/fingerprint/fingerprint-debug.mjs +40 -0
- package/dist/clerk/fingerprint/fingerprint-provider.js +53 -35
- package/dist/clerk/fingerprint/fingerprint-provider.mjs +54 -36
- package/dist/clerk/fingerprint/fingerprint-shared.d.ts +2 -0
- package/dist/clerk/fingerprint/fingerprint-shared.js +3 -0
- package/dist/clerk/fingerprint/fingerprint-shared.mjs +3 -1
- package/dist/clerk/fingerprint/index.js +1 -0
- package/dist/clerk/fingerprint/index.mjs +1 -1
- package/dist/clerk/fingerprint/server.js +1 -0
- package/dist/clerk/fingerprint/server.mjs +1 -1
- package/dist/main/delayed-img.js +3 -4
- package/dist/main/delayed-img.mjs +3 -4
- package/package.json +3 -3
- package/src/clerk/fingerprint/fingerprint-debug.ts +47 -0
- package/src/clerk/fingerprint/fingerprint-provider.tsx +70 -53
- package/src/clerk/fingerprint/fingerprint-shared.ts +2 -0
- package/src/main/delayed-img.tsx +2 -7
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var fingerprintShared = require('./fingerprint-shared.js');
|
|
5
|
+
|
|
6
|
+
function getLocalStorageValue(key) {
|
|
7
|
+
if (typeof window === 'undefined') {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return window.localStorage.getItem(key);
|
|
12
|
+
}
|
|
13
|
+
catch (_a) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function setLocalStorageValue(key, value) {
|
|
18
|
+
if (typeof window === 'undefined') {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
window.localStorage.setItem(key, value);
|
|
23
|
+
}
|
|
24
|
+
catch (_a) {
|
|
25
|
+
// Ignore storage failures in debug helpers.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function getDebugFingerprintOverride() {
|
|
29
|
+
const value = getLocalStorageValue(fingerprintShared.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY);
|
|
30
|
+
if (!value || !fingerprintShared.isValidFingerprintId(value)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function setDebugFingerprintOverride(fingerprintId) {
|
|
36
|
+
if (!fingerprintShared.isValidFingerprintId(fingerprintId)) {
|
|
37
|
+
throw new Error('Invalid fingerprint ID');
|
|
38
|
+
}
|
|
39
|
+
setLocalStorageValue(fingerprintShared.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, fingerprintId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.getDebugFingerprintOverride = getDebugFingerprintOverride;
|
|
43
|
+
exports.setDebugFingerprintOverride = setDebugFingerprintOverride;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { isValidFingerprintId, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY } from './fingerprint-shared.mjs';
|
|
3
|
+
|
|
4
|
+
function getLocalStorageValue(key) {
|
|
5
|
+
if (typeof window === 'undefined') {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
return window.localStorage.getItem(key);
|
|
10
|
+
}
|
|
11
|
+
catch (_a) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function setLocalStorageValue(key, value) {
|
|
16
|
+
if (typeof window === 'undefined') {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
window.localStorage.setItem(key, value);
|
|
21
|
+
}
|
|
22
|
+
catch (_a) {
|
|
23
|
+
// Ignore storage failures in debug helpers.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getDebugFingerprintOverride() {
|
|
27
|
+
const value = getLocalStorageValue(FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY);
|
|
28
|
+
if (!value || !isValidFingerprintId(value)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function setDebugFingerprintOverride(fingerprintId) {
|
|
34
|
+
if (!isValidFingerprintId(fingerprintId)) {
|
|
35
|
+
throw new Error('Invalid fingerprint ID');
|
|
36
|
+
}
|
|
37
|
+
setLocalStorageValue(FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, fingerprintId);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { getDebugFingerprintOverride, setDebugFingerprintOverride };
|
|
@@ -10,6 +10,7 @@ var React = require('react');
|
|
|
10
10
|
var useFingerprint = require('./use-fingerprint.js');
|
|
11
11
|
var ui = require('@windrun-huaiin/base-ui/ui');
|
|
12
12
|
var fingerprintClient = require('./fingerprint-client.js');
|
|
13
|
+
var fingerprintDebug = require('./fingerprint-debug.js');
|
|
13
14
|
var fingerprintShared = require('./fingerprint-shared.js');
|
|
14
15
|
|
|
15
16
|
const FingerprintContext = React.createContext(undefined);
|
|
@@ -52,13 +53,14 @@ function withFingerprint(Component, config) {
|
|
|
52
53
|
* 组件:显示用户状态和积分信息(用于调试)
|
|
53
54
|
*/
|
|
54
55
|
function FingerprintStatus() {
|
|
55
|
-
const { fingerprintId, xUser, xCredit, xSubscription, error, clearError,
|
|
56
|
+
const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, } = useFingerprintContext();
|
|
56
57
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
57
58
|
const [panelMode, setPanelMode] = React.useState('info');
|
|
58
|
-
const [testFingerprintId, setTestFingerprintId] = React.useState('');
|
|
59
59
|
const [testResult, setTestResult] = React.useState('');
|
|
60
60
|
const [isRunningTest, setIsRunningTest] = React.useState(false);
|
|
61
61
|
const modalRef = React.useRef(null);
|
|
62
|
+
const isInitializingDebugAnonymousUserRef = React.useRef(false);
|
|
63
|
+
const [activeDebugFingerprintId, setActiveDebugFingerprintId] = React.useState(null);
|
|
62
64
|
const handleToggle = () => setIsOpen(prev => !prev);
|
|
63
65
|
const handleBackdropClick = (e) => {
|
|
64
66
|
if (e.target === e.currentTarget)
|
|
@@ -79,12 +81,15 @@ function FingerprintStatus() {
|
|
|
79
81
|
}
|
|
80
82
|
}, [xUser]);
|
|
81
83
|
React.useEffect(() => {
|
|
82
|
-
|
|
84
|
+
const debugFingerprintOverride = fingerprintDebug.getDebugFingerprintOverride();
|
|
85
|
+
if (debugFingerprintOverride) {
|
|
86
|
+
setActiveDebugFingerprintId(debugFingerprintOverride);
|
|
83
87
|
return;
|
|
84
88
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
const nextFingerprintId = buildDebugFingerprintId();
|
|
90
|
+
fingerprintDebug.setDebugFingerprintOverride(nextFingerprintId);
|
|
91
|
+
setActiveDebugFingerprintId(nextFingerprintId);
|
|
92
|
+
}, []);
|
|
88
93
|
const creditBuckets = React.useMemo(() => {
|
|
89
94
|
if (!xCredit)
|
|
90
95
|
return [];
|
|
@@ -142,31 +147,58 @@ function FingerprintStatus() {
|
|
|
142
147
|
const subStatus = subscriptionStatus.status;
|
|
143
148
|
const themedGhostButtonClass = utils.cn('border-slate-200 bg-white/90 hover:bg-slate-50 dark:border-white/10 dark:bg-slate-950/80 dark:hover:bg-slate-900', 'hover:border-current', lib.themeIconColor);
|
|
144
149
|
const runContextParallelInitTest = () => tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
const debugFingerprintId = activeDebugFingerprintId;
|
|
151
|
+
if (!debugFingerprintId) {
|
|
152
|
+
setTestResult('Test fingerprint override is not ready yet.');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
145
155
|
setIsRunningTest(true);
|
|
146
|
-
setTestResult(
|
|
156
|
+
setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
|
|
147
157
|
try {
|
|
148
158
|
yield Promise.all([
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
160
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
161
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
152
162
|
]);
|
|
153
|
-
setTestResult(`
|
|
163
|
+
setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
|
|
154
164
|
}
|
|
155
165
|
catch (testError) {
|
|
156
|
-
setTestResult(`
|
|
166
|
+
setTestResult(`Frontend Prevention Test failed: ${formatErrorMessage(testError)}`);
|
|
157
167
|
}
|
|
158
168
|
finally {
|
|
159
169
|
setIsRunningTest(false);
|
|
160
170
|
}
|
|
161
171
|
});
|
|
172
|
+
const initializeDebugAnonymousUser = (debugFingerprintId) => tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
173
|
+
if (isInitializingDebugAnonymousUserRef.current) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
isInitializingDebugAnonymousUserRef.current = true;
|
|
178
|
+
const fingerprintHeaders = yield fingerprintClient.createFingerprintHeaders();
|
|
179
|
+
const response = yield fetch('/api/user/anonymous/init', {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
headers: Object.assign(Object.assign({ 'Content-Type': 'application/json', [fingerprintShared.FINGERPRINT_SOURCE_REFER]: document.referrer || '' }, fingerprintHeaders), { 'x-fingerprint-id-v8': debugFingerprintId }),
|
|
182
|
+
body: JSON.stringify({ fingerprintId: debugFingerprintId }),
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const errorData = yield response.json().catch(() => ({}));
|
|
186
|
+
throw new Error(errorData.error || 'Failed to initialize anonymous user');
|
|
187
|
+
}
|
|
188
|
+
yield response.json().catch(() => ({}));
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
isInitializingDebugAnonymousUserRef.current = false;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
162
194
|
const runRawParallelPostTest = () => tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
163
|
-
const normalizedFingerprintId =
|
|
195
|
+
const normalizedFingerprintId = activeDebugFingerprintId;
|
|
164
196
|
if (!normalizedFingerprintId) {
|
|
165
|
-
setTestResult('
|
|
197
|
+
setTestResult('Test fingerprint override is not ready yet.');
|
|
166
198
|
return;
|
|
167
199
|
}
|
|
168
200
|
setIsRunningTest(true);
|
|
169
|
-
setTestResult(`Running
|
|
201
|
+
setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
|
|
170
202
|
try {
|
|
171
203
|
const fingerprintHeaders = yield fingerprintClient.createFingerprintHeaders();
|
|
172
204
|
const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
|
|
@@ -197,7 +229,7 @@ function FingerprintStatus() {
|
|
|
197
229
|
.filter((payload) => !payload.ok)
|
|
198
230
|
.map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
|
|
199
231
|
setTestResult([
|
|
200
|
-
`
|
|
232
|
+
`Backend Idempotency Test done.`,
|
|
201
233
|
`created=${createdUserIds.length}`,
|
|
202
234
|
`reused=${reusedUserIds.length}`,
|
|
203
235
|
`failed=${failedStatuses.length}`,
|
|
@@ -206,31 +238,17 @@ function FingerprintStatus() {
|
|
|
206
238
|
].filter(Boolean).join('\n'));
|
|
207
239
|
}
|
|
208
240
|
catch (testError) {
|
|
209
|
-
setTestResult(`
|
|
241
|
+
setTestResult(`Backend Idempotency Test failed: ${formatErrorMessage(testError)}`);
|
|
210
242
|
}
|
|
211
243
|
finally {
|
|
212
244
|
setIsRunningTest(false);
|
|
213
245
|
}
|
|
214
246
|
});
|
|
215
|
-
const applyTestFingerprintAndReload = () => {
|
|
216
|
-
const normalizedFingerprintId = testFingerprintId.trim();
|
|
217
|
-
if (!normalizedFingerprintId) {
|
|
218
|
-
setTestResult('Please input a valid test fingerprint id before applying.');
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
try {
|
|
222
|
-
fingerprintClient.setFingerprintId(normalizedFingerprintId);
|
|
223
|
-
setTestResult(`Applied test fingerprint and reloading: ${normalizedFingerprintId}`);
|
|
224
|
-
window.location.reload();
|
|
225
|
-
}
|
|
226
|
-
catch (testError) {
|
|
227
|
-
setTestResult(`Apply test fingerprint failed: ${formatErrorMessage(testError)}`);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
247
|
const regenerateTestFingerprint = () => {
|
|
231
248
|
const nextFingerprintId = buildDebugFingerprintId();
|
|
232
|
-
|
|
233
|
-
|
|
249
|
+
fingerprintDebug.setDebugFingerprintOverride(nextFingerprintId);
|
|
250
|
+
setActiveDebugFingerprintId(nextFingerprintId);
|
|
251
|
+
setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
|
|
234
252
|
};
|
|
235
253
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isOpen && (jsxRuntime.jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: utils.cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsxRuntime.jsx(server.globalLucideIcons.Lightbulb, { className: "size-6 text-white" }) })), isOpen && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxRuntime.jsxs("div", { ref: modalRef, className: utils.cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsxRuntime.jsx("header", { className: "mb-4", children: jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxRuntime.jsxs("div", { className: utils.cn("flex items-center gap-2 text-base font-bold tracking-wider", lib.themeIconColor), children: [jsxRuntime.jsx(server.globalLucideIcons.ShieldUser, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: utils.cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
|
|
236
254
|
? utils.cn('border-transparent text-white', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass)
|
|
@@ -254,7 +272,7 @@ function FingerprintStatus() {
|
|
|
254
272
|
{ label: 'SubID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
|
|
255
273
|
{ label: 'OrderID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
|
|
256
274
|
{ label: 'PriceID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
|
|
257
|
-
] })] })) : (jsxRuntime.jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(server.globalLucideIcons.DatabaseZap, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsxRuntime.jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxRuntime.jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "
|
|
275
|
+
] })] })) : (jsxRuntime.jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(server.globalLucideIcons.DatabaseZap, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsxRuntime.jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxRuntime.jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Real Browser" }), jsxRuntime.jsx(ui.CopyableText, { text: fingerprintId || '' })] }), jsxRuntime.jsxs("div", { className: "space-y-1", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Test Override" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsxRuntime.jsx("div", { className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsxRuntime.jsx(ui.CopyableText, { text: activeDebugFingerprintId || '' }) }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": "Generate new test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsxRuntime.jsx(server.globalLucideIcons.RefreshCcw, { className: "size-4" }) })] })] })] }), jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: utils.cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: "Frontend Prevention Test" }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: utils.cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass), children: "Backend Idempotency Test" })] }), jsxRuntime.jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsxRuntime.jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || 'No test executed yet.' }) })] })), error && (jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [jsxRuntime.jsx(server.globalLucideIcons.X, { className: "mt-0.5 size-4 shrink-0" }), jsxRuntime.jsx("span", { children: error })] }), jsxRuntime.jsx("button", { type: "button", "aria-label": "Dismiss error", onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsxRuntime.jsx(server.globalLucideIcons.X, { className: "size-4" }) })] }))] })] })] }))] }));
|
|
258
276
|
}
|
|
259
277
|
/* ==================== 新增辅助组件 ==================== */
|
|
260
278
|
// 标题行:左侧图标+标题,右侧信息(右对齐)
|
|
@@ -7,7 +7,8 @@ import { cn } from '@windrun-huaiin/lib/utils';
|
|
|
7
7
|
import { createContext, useContext, useState, useRef, useEffect, useMemo } from 'react';
|
|
8
8
|
import { useFingerprint } from './use-fingerprint.mjs';
|
|
9
9
|
import { CopyableText } from '@windrun-huaiin/base-ui/ui';
|
|
10
|
-
import {
|
|
10
|
+
import { createFingerprintHeaders } from './fingerprint-client.mjs';
|
|
11
|
+
import { getDebugFingerprintOverride, setDebugFingerprintOverride } from './fingerprint-debug.mjs';
|
|
11
12
|
import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
|
|
12
13
|
|
|
13
14
|
const FingerprintContext = createContext(undefined);
|
|
@@ -50,13 +51,14 @@ function withFingerprint(Component, config) {
|
|
|
50
51
|
* 组件:显示用户状态和积分信息(用于调试)
|
|
51
52
|
*/
|
|
52
53
|
function FingerprintStatus() {
|
|
53
|
-
const { fingerprintId, xUser, xCredit, xSubscription, error, clearError,
|
|
54
|
+
const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, } = useFingerprintContext();
|
|
54
55
|
const [isOpen, setIsOpen] = useState(false);
|
|
55
56
|
const [panelMode, setPanelMode] = useState('info');
|
|
56
|
-
const [testFingerprintId, setTestFingerprintId] = useState('');
|
|
57
57
|
const [testResult, setTestResult] = useState('');
|
|
58
58
|
const [isRunningTest, setIsRunningTest] = useState(false);
|
|
59
59
|
const modalRef = useRef(null);
|
|
60
|
+
const isInitializingDebugAnonymousUserRef = useRef(false);
|
|
61
|
+
const [activeDebugFingerprintId, setActiveDebugFingerprintId] = useState(null);
|
|
60
62
|
const handleToggle = () => setIsOpen(prev => !prev);
|
|
61
63
|
const handleBackdropClick = (e) => {
|
|
62
64
|
if (e.target === e.currentTarget)
|
|
@@ -77,12 +79,15 @@ function FingerprintStatus() {
|
|
|
77
79
|
}
|
|
78
80
|
}, [xUser]);
|
|
79
81
|
useEffect(() => {
|
|
80
|
-
|
|
82
|
+
const debugFingerprintOverride = getDebugFingerprintOverride();
|
|
83
|
+
if (debugFingerprintOverride) {
|
|
84
|
+
setActiveDebugFingerprintId(debugFingerprintOverride);
|
|
81
85
|
return;
|
|
82
86
|
}
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
const nextFingerprintId = buildDebugFingerprintId();
|
|
88
|
+
setDebugFingerprintOverride(nextFingerprintId);
|
|
89
|
+
setActiveDebugFingerprintId(nextFingerprintId);
|
|
90
|
+
}, []);
|
|
86
91
|
const creditBuckets = useMemo(() => {
|
|
87
92
|
if (!xCredit)
|
|
88
93
|
return [];
|
|
@@ -140,31 +145,58 @@ function FingerprintStatus() {
|
|
|
140
145
|
const subStatus = subscriptionStatus.status;
|
|
141
146
|
const themedGhostButtonClass = cn('border-slate-200 bg-white/90 hover:bg-slate-50 dark:border-white/10 dark:bg-slate-950/80 dark:hover:bg-slate-900', 'hover:border-current', themeIconColor);
|
|
142
147
|
const runContextParallelInitTest = () => __awaiter(this, void 0, void 0, function* () {
|
|
148
|
+
const debugFingerprintId = activeDebugFingerprintId;
|
|
149
|
+
if (!debugFingerprintId) {
|
|
150
|
+
setTestResult('Test fingerprint override is not ready yet.');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
143
153
|
setIsRunningTest(true);
|
|
144
|
-
setTestResult(
|
|
154
|
+
setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
|
|
145
155
|
try {
|
|
146
156
|
yield Promise.all([
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
157
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
158
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
159
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
150
160
|
]);
|
|
151
|
-
setTestResult(`
|
|
161
|
+
setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
|
|
152
162
|
}
|
|
153
163
|
catch (testError) {
|
|
154
|
-
setTestResult(`
|
|
164
|
+
setTestResult(`Frontend Prevention Test failed: ${formatErrorMessage(testError)}`);
|
|
155
165
|
}
|
|
156
166
|
finally {
|
|
157
167
|
setIsRunningTest(false);
|
|
158
168
|
}
|
|
159
169
|
});
|
|
170
|
+
const initializeDebugAnonymousUser = (debugFingerprintId) => __awaiter(this, void 0, void 0, function* () {
|
|
171
|
+
if (isInitializingDebugAnonymousUserRef.current) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
isInitializingDebugAnonymousUserRef.current = true;
|
|
176
|
+
const fingerprintHeaders = yield createFingerprintHeaders();
|
|
177
|
+
const response = yield fetch('/api/user/anonymous/init', {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: Object.assign(Object.assign({ 'Content-Type': 'application/json', [FINGERPRINT_SOURCE_REFER]: document.referrer || '' }, fingerprintHeaders), { 'x-fingerprint-id-v8': debugFingerprintId }),
|
|
180
|
+
body: JSON.stringify({ fingerprintId: debugFingerprintId }),
|
|
181
|
+
});
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
const errorData = yield response.json().catch(() => ({}));
|
|
184
|
+
throw new Error(errorData.error || 'Failed to initialize anonymous user');
|
|
185
|
+
}
|
|
186
|
+
yield response.json().catch(() => ({}));
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
isInitializingDebugAnonymousUserRef.current = false;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
160
192
|
const runRawParallelPostTest = () => __awaiter(this, void 0, void 0, function* () {
|
|
161
|
-
const normalizedFingerprintId =
|
|
193
|
+
const normalizedFingerprintId = activeDebugFingerprintId;
|
|
162
194
|
if (!normalizedFingerprintId) {
|
|
163
|
-
setTestResult('
|
|
195
|
+
setTestResult('Test fingerprint override is not ready yet.');
|
|
164
196
|
return;
|
|
165
197
|
}
|
|
166
198
|
setIsRunningTest(true);
|
|
167
|
-
setTestResult(`Running
|
|
199
|
+
setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
|
|
168
200
|
try {
|
|
169
201
|
const fingerprintHeaders = yield createFingerprintHeaders();
|
|
170
202
|
const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
|
|
@@ -195,7 +227,7 @@ function FingerprintStatus() {
|
|
|
195
227
|
.filter((payload) => !payload.ok)
|
|
196
228
|
.map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
|
|
197
229
|
setTestResult([
|
|
198
|
-
`
|
|
230
|
+
`Backend Idempotency Test done.`,
|
|
199
231
|
`created=${createdUserIds.length}`,
|
|
200
232
|
`reused=${reusedUserIds.length}`,
|
|
201
233
|
`failed=${failedStatuses.length}`,
|
|
@@ -204,31 +236,17 @@ function FingerprintStatus() {
|
|
|
204
236
|
].filter(Boolean).join('\n'));
|
|
205
237
|
}
|
|
206
238
|
catch (testError) {
|
|
207
|
-
setTestResult(`
|
|
239
|
+
setTestResult(`Backend Idempotency Test failed: ${formatErrorMessage(testError)}`);
|
|
208
240
|
}
|
|
209
241
|
finally {
|
|
210
242
|
setIsRunningTest(false);
|
|
211
243
|
}
|
|
212
244
|
});
|
|
213
|
-
const applyTestFingerprintAndReload = () => {
|
|
214
|
-
const normalizedFingerprintId = testFingerprintId.trim();
|
|
215
|
-
if (!normalizedFingerprintId) {
|
|
216
|
-
setTestResult('Please input a valid test fingerprint id before applying.');
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
setFingerprintId(normalizedFingerprintId);
|
|
221
|
-
setTestResult(`Applied test fingerprint and reloading: ${normalizedFingerprintId}`);
|
|
222
|
-
window.location.reload();
|
|
223
|
-
}
|
|
224
|
-
catch (testError) {
|
|
225
|
-
setTestResult(`Apply test fingerprint failed: ${formatErrorMessage(testError)}`);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
245
|
const regenerateTestFingerprint = () => {
|
|
229
246
|
const nextFingerprintId = buildDebugFingerprintId();
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
setDebugFingerprintOverride(nextFingerprintId);
|
|
248
|
+
setActiveDebugFingerprintId(nextFingerprintId);
|
|
249
|
+
setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
|
|
232
250
|
};
|
|
233
251
|
return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', themeButtonGradientClass, themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsx(globalLucideIcons.Lightbulb, { className: "size-6 text-white" }) })), isOpen && (jsxs(Fragment, { children: [jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxs("div", { ref: modalRef, className: cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsx("header", { className: "mb-4", children: jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxs("div", { className: cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor), children: [jsx(globalLucideIcons.ShieldUser, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
|
|
234
252
|
? cn('border-transparent text-white', themeButtonGradientClass, themeButtonGradientHoverClass)
|
|
@@ -252,7 +270,7 @@ function FingerprintStatus() {
|
|
|
252
270
|
{ label: 'SubID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
|
|
253
271
|
{ label: 'OrderID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
|
|
254
272
|
{ label: 'PriceID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
|
|
255
|
-
] })] })) : (jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsx(PanelHeader, { icon: jsx(globalLucideIcons.DatabaseZap, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "
|
|
273
|
+
] })] })) : (jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsx(PanelHeader, { icon: jsx(globalLucideIcons.DatabaseZap, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Real Browser" }), jsx(CopyableText, { text: fingerprintId || '' })] }), jsxs("div", { className: "space-y-1", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Test Override" }), jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsx("div", { className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsx(CopyableText, { text: activeDebugFingerprintId || '' }) }), jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": "Generate new test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsx(globalLucideIcons.RefreshCcw, { className: "size-4" }) })] })] })] }), jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: "Frontend Prevention Test" }), jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', themeButtonGradientClass, themeButtonGradientHoverClass), children: "Backend Idempotency Test" })] }), jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || 'No test executed yet.' }) })] })), error && (jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxs("div", { className: "flex items-start gap-2", children: [jsx(globalLucideIcons.X, { className: "mt-0.5 size-4 shrink-0" }), jsx("span", { children: error })] }), jsx("button", { type: "button", "aria-label": "Dismiss error", onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsx(globalLucideIcons.X, { className: "size-4" }) })] }))] })] })] }))] }));
|
|
256
274
|
}
|
|
257
275
|
/* ==================== 新增辅助组件 ==================== */
|
|
258
276
|
// 标题行:左侧图标+标题,右侧信息(右对齐)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 客户端和服务端共享的常量、类型和验证逻辑
|
|
4
4
|
*/
|
|
5
5
|
export declare const FINGERPRINT_STORAGE_KEY = "__x_fingerprint_id";
|
|
6
|
+
export declare const FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = "__x_fingerprint_debug_override";
|
|
6
7
|
export declare const FINGERPRINT_HEADER_NAME = "x-fingerprint-id-v8";
|
|
7
8
|
export declare const FINGERPRINT_COOKIE_NAME = "__x_fingerprint_id";
|
|
8
9
|
export declare const FINGERPRINT_SOURCE_REFER = "x-source-ref";
|
|
@@ -16,6 +17,7 @@ export declare const FINGERPRINT_FIRST_TOUCH_HEADER = "x-first-touch";
|
|
|
16
17
|
export declare function isValidFingerprintId(fingerprintId: string): boolean;
|
|
17
18
|
export declare const FINGERPRINT_CONSTANTS: {
|
|
18
19
|
readonly STORAGE_KEY: "__x_fingerprint_id";
|
|
20
|
+
readonly DEBUG_OVERRIDE_STORAGE_KEY: "__x_fingerprint_debug_override";
|
|
19
21
|
readonly HEADER_NAME: "x-fingerprint-id-v8";
|
|
20
22
|
readonly COOKIE_NAME: "__x_fingerprint_id";
|
|
21
23
|
readonly FIRST_TOUCH_STORAGE_KEY: "__x_first_touch";
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
// Fingerprint ID的存储键和header名
|
|
8
8
|
const FINGERPRINT_STORAGE_KEY = '__x_fingerprint_id';
|
|
9
|
+
const FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = '__x_fingerprint_debug_override';
|
|
9
10
|
const FINGERPRINT_HEADER_NAME = 'x-fingerprint-id-v8';
|
|
10
11
|
const FINGERPRINT_COOKIE_NAME = '__x_fingerprint_id';
|
|
11
12
|
const FINGERPRINT_SOURCE_REFER = 'x-source-ref';
|
|
@@ -28,6 +29,7 @@ function isValidFingerprintId(fingerprintId) {
|
|
|
28
29
|
// 常量导出
|
|
29
30
|
const FINGERPRINT_CONSTANTS = {
|
|
30
31
|
STORAGE_KEY: FINGERPRINT_STORAGE_KEY,
|
|
32
|
+
DEBUG_OVERRIDE_STORAGE_KEY: FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY,
|
|
31
33
|
HEADER_NAME: FINGERPRINT_HEADER_NAME,
|
|
32
34
|
COOKIE_NAME: FINGERPRINT_COOKIE_NAME,
|
|
33
35
|
FIRST_TOUCH_STORAGE_KEY: FINGERPRINT_FIRST_TOUCH_STORAGE_KEY,
|
|
@@ -37,6 +39,7 @@ const FINGERPRINT_CONSTANTS = {
|
|
|
37
39
|
|
|
38
40
|
exports.FINGERPRINT_CONSTANTS = FINGERPRINT_CONSTANTS;
|
|
39
41
|
exports.FINGERPRINT_COOKIE_NAME = FINGERPRINT_COOKIE_NAME;
|
|
42
|
+
exports.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY;
|
|
40
43
|
exports.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = FINGERPRINT_FIRST_TOUCH_COOKIE_NAME;
|
|
41
44
|
exports.FINGERPRINT_FIRST_TOUCH_HEADER = FINGERPRINT_FIRST_TOUCH_HEADER;
|
|
42
45
|
exports.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = FINGERPRINT_FIRST_TOUCH_STORAGE_KEY;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
// Fingerprint ID的存储键和header名
|
|
6
6
|
const FINGERPRINT_STORAGE_KEY = '__x_fingerprint_id';
|
|
7
|
+
const FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = '__x_fingerprint_debug_override';
|
|
7
8
|
const FINGERPRINT_HEADER_NAME = 'x-fingerprint-id-v8';
|
|
8
9
|
const FINGERPRINT_COOKIE_NAME = '__x_fingerprint_id';
|
|
9
10
|
const FINGERPRINT_SOURCE_REFER = 'x-source-ref';
|
|
@@ -26,6 +27,7 @@ function isValidFingerprintId(fingerprintId) {
|
|
|
26
27
|
// 常量导出
|
|
27
28
|
const FINGERPRINT_CONSTANTS = {
|
|
28
29
|
STORAGE_KEY: FINGERPRINT_STORAGE_KEY,
|
|
30
|
+
DEBUG_OVERRIDE_STORAGE_KEY: FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY,
|
|
29
31
|
HEADER_NAME: FINGERPRINT_HEADER_NAME,
|
|
30
32
|
COOKIE_NAME: FINGERPRINT_COOKIE_NAME,
|
|
31
33
|
FIRST_TOUCH_STORAGE_KEY: FINGERPRINT_FIRST_TOUCH_STORAGE_KEY,
|
|
@@ -33,4 +35,4 @@ const FINGERPRINT_CONSTANTS = {
|
|
|
33
35
|
FIRST_TOUCH_HEADER: FINGERPRINT_FIRST_TOUCH_HEADER,
|
|
34
36
|
};
|
|
35
37
|
|
|
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 };
|
|
38
|
+
export { FINGERPRINT_CONSTANTS, FINGERPRINT_COOKIE_NAME, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_HEADER, FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER, FINGERPRINT_STORAGE_KEY, isValidFingerprintId };
|
|
@@ -10,6 +10,7 @@ var fingerprintProvider = require('./fingerprint-provider.js');
|
|
|
10
10
|
|
|
11
11
|
exports.FINGERPRINT_CONSTANTS = fingerprintShared.FINGERPRINT_CONSTANTS;
|
|
12
12
|
exports.FINGERPRINT_COOKIE_NAME = fingerprintShared.FINGERPRINT_COOKIE_NAME;
|
|
13
|
+
exports.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = fingerprintShared.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY;
|
|
13
14
|
exports.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = fingerprintShared.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME;
|
|
14
15
|
exports.FINGERPRINT_FIRST_TOUCH_HEADER = fingerprintShared.FINGERPRINT_FIRST_TOUCH_HEADER;
|
|
15
16
|
exports.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = fingerprintShared.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
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 } from './fingerprint-shared.mjs';
|
|
2
|
+
export { FINGERPRINT_CONSTANTS, FINGERPRINT_COOKIE_NAME, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_HEADER, FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER, FINGERPRINT_STORAGE_KEY, isValidFingerprintId } from './fingerprint-shared.mjs';
|
|
3
3
|
export { clearFingerprintId, createFingerprintFetch, createFingerprintHeaders, generateFingerprintId, getFingerprintId, getOrCreateFirstTouchData, getOrGenerateFingerprintId, setFingerprintId, useFingerprintHeaders } from './fingerprint-client.mjs';
|
|
4
4
|
export { useFingerprint } from './use-fingerprint.mjs';
|
|
5
5
|
export { FingerprintProvider, FingerprintStatus, useFingerprintContext, useFingerprintContextSafe, withFingerprint } from './fingerprint-provider.mjs';
|
|
@@ -7,6 +7,7 @@ var fingerprintServer = require('./fingerprint-server.js');
|
|
|
7
7
|
|
|
8
8
|
exports.FINGERPRINT_CONSTANTS = fingerprintShared.FINGERPRINT_CONSTANTS;
|
|
9
9
|
exports.FINGERPRINT_COOKIE_NAME = fingerprintShared.FINGERPRINT_COOKIE_NAME;
|
|
10
|
+
exports.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = fingerprintShared.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY;
|
|
10
11
|
exports.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = fingerprintShared.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME;
|
|
11
12
|
exports.FINGERPRINT_FIRST_TOUCH_HEADER = fingerprintShared.FINGERPRINT_FIRST_TOUCH_HEADER;
|
|
12
13
|
exports.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = fingerprintShared.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
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 } from './fingerprint-shared.mjs';
|
|
1
|
+
export { FINGERPRINT_CONSTANTS, FINGERPRINT_COOKIE_NAME, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_HEADER, FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER, FINGERPRINT_STORAGE_KEY, isValidFingerprintId } from './fingerprint-shared.mjs';
|
|
2
2
|
export { extractFingerprintFromNextRequest, extractFingerprintFromNextStores, extractFingerprintId, generateServerFingerprintId } from './fingerprint-server.mjs';
|
package/dist/main/delayed-img.js
CHANGED
|
@@ -8,10 +8,9 @@ var React = require('react');
|
|
|
8
8
|
var lib = require('@windrun-huaiin/base-ui/lib');
|
|
9
9
|
var utils = require('@windrun-huaiin/lib/utils');
|
|
10
10
|
|
|
11
|
-
var _a
|
|
12
|
-
const ENV_DELAY_ENABLED = process.env.NEXT_PUBLIC_DELAYED_IMG_ENABLED === "true"
|
|
13
|
-
|
|
14
|
-
const rawDelaySeconds = (_b = (_a = process.env.NEXT_PUBLIC_DELAYED_IMG_SECONDS) !== null && _a !== void 0 ? _a : process.env.NEXT_PUBLIC_DELAY_REVEAL_SECONDS) !== null && _b !== void 0 ? _b : "0";
|
|
11
|
+
var _a;
|
|
12
|
+
const ENV_DELAY_ENABLED = process.env.NEXT_PUBLIC_DELAYED_IMG_ENABLED === "true";
|
|
13
|
+
const rawDelaySeconds = (_a = process.env.NEXT_PUBLIC_DELAYED_IMG_SECONDS) !== null && _a !== void 0 ? _a : "0";
|
|
15
14
|
const parsedDelaySeconds = Number(rawDelaySeconds);
|
|
16
15
|
const ENV_DELAY_MS = Number.isFinite(parsedDelaySeconds) && parsedDelaySeconds > 0
|
|
17
16
|
? parsedDelaySeconds * 1000
|
|
@@ -6,10 +6,9 @@ import { useState, useEffect } from 'react';
|
|
|
6
6
|
import { themeViaColor, themeBgColor } from '@windrun-huaiin/base-ui/lib';
|
|
7
7
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
8
8
|
|
|
9
|
-
var _a
|
|
10
|
-
const ENV_DELAY_ENABLED = process.env.NEXT_PUBLIC_DELAYED_IMG_ENABLED === "true"
|
|
11
|
-
|
|
12
|
-
const rawDelaySeconds = (_b = (_a = process.env.NEXT_PUBLIC_DELAYED_IMG_SECONDS) !== null && _a !== void 0 ? _a : process.env.NEXT_PUBLIC_DELAY_REVEAL_SECONDS) !== null && _b !== void 0 ? _b : "0";
|
|
9
|
+
var _a;
|
|
10
|
+
const ENV_DELAY_ENABLED = process.env.NEXT_PUBLIC_DELAYED_IMG_ENABLED === "true";
|
|
11
|
+
const rawDelaySeconds = (_a = process.env.NEXT_PUBLIC_DELAYED_IMG_SECONDS) !== null && _a !== void 0 ? _a : "0";
|
|
13
12
|
const parsedDelaySeconds = Number(rawDelaySeconds);
|
|
14
13
|
const ENV_DELAY_MS = Number.isFinite(parsedDelaySeconds) && parsedDelaySeconds > 0
|
|
15
14
|
? parsedDelaySeconds * 1000
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.2.0",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"react-medium-image-zoom": "^5.4.1",
|
|
87
87
|
"swiper": "^12.1.2",
|
|
88
88
|
"zod": "^4.3.6",
|
|
89
|
-
"@windrun-huaiin/base-ui": "^14.0.
|
|
90
|
-
"@windrun-huaiin/lib": "^14.0.
|
|
89
|
+
"@windrun-huaiin/base-ui": "^14.0.3",
|
|
90
|
+
"@windrun-huaiin/lib": "^14.0.1"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"clsx": "^2.1.1",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY,
|
|
5
|
+
isValidFingerprintId,
|
|
6
|
+
} from './fingerprint-shared';
|
|
7
|
+
|
|
8
|
+
function getLocalStorageValue(key: string): string | null {
|
|
9
|
+
if (typeof window === 'undefined') {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
return window.localStorage.getItem(key);
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setLocalStorageValue(key: string, value: string): void {
|
|
21
|
+
if (typeof window === 'undefined') {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
window.localStorage.setItem(key, value);
|
|
27
|
+
} catch {
|
|
28
|
+
// Ignore storage failures in debug helpers.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getDebugFingerprintOverride(): string | null {
|
|
33
|
+
const value = getLocalStorageValue(FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY);
|
|
34
|
+
if (!value || !isValidFingerprintId(value)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function setDebugFingerprintOverride(fingerprintId: string): void {
|
|
42
|
+
if (!isValidFingerprintId(fingerprintId)) {
|
|
43
|
+
throw new Error('Invalid fingerprint ID');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setLocalStorageValue(FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, fingerprintId);
|
|
47
|
+
}
|
|
@@ -7,7 +7,11 @@ import React, { createContext, useContext, useEffect, useMemo, useRef, useState
|
|
|
7
7
|
import type { FingerprintContextType, FingerprintProviderProps } from './types';
|
|
8
8
|
import { useFingerprint } from './use-fingerprint';
|
|
9
9
|
import { CopyableText } from '@windrun-huaiin/base-ui/ui';
|
|
10
|
-
import { createFingerprintHeaders
|
|
10
|
+
import { createFingerprintHeaders } from './fingerprint-client';
|
|
11
|
+
import {
|
|
12
|
+
getDebugFingerprintOverride,
|
|
13
|
+
setDebugFingerprintOverride,
|
|
14
|
+
} from './fingerprint-debug';
|
|
11
15
|
import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared';
|
|
12
16
|
|
|
13
17
|
const FingerprintContext = createContext<FingerprintContextType | undefined>(undefined);
|
|
@@ -77,15 +81,15 @@ export function FingerprintStatus() {
|
|
|
77
81
|
xSubscription,
|
|
78
82
|
error,
|
|
79
83
|
clearError,
|
|
80
|
-
initializeAnonymousUser,
|
|
81
84
|
} = useFingerprintContext();
|
|
82
85
|
|
|
83
86
|
const [isOpen, setIsOpen] = useState(false);
|
|
84
87
|
const [panelMode, setPanelMode] = useState<'info' | 'test'>('info');
|
|
85
|
-
const [testFingerprintId, setTestFingerprintId] = useState('');
|
|
86
88
|
const [testResult, setTestResult] = useState<string>('');
|
|
87
89
|
const [isRunningTest, setIsRunningTest] = useState(false);
|
|
88
90
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
91
|
+
const isInitializingDebugAnonymousUserRef = useRef(false);
|
|
92
|
+
const [activeDebugFingerprintId, setActiveDebugFingerprintId] = useState<string | null>(null);
|
|
89
93
|
|
|
90
94
|
const handleToggle = () => setIsOpen(prev => !prev);
|
|
91
95
|
|
|
@@ -108,13 +112,16 @@ export function FingerprintStatus() {
|
|
|
108
112
|
}, [xUser]);
|
|
109
113
|
|
|
110
114
|
useEffect(() => {
|
|
111
|
-
|
|
115
|
+
const debugFingerprintOverride = getDebugFingerprintOverride();
|
|
116
|
+
if (debugFingerprintOverride) {
|
|
117
|
+
setActiveDebugFingerprintId(debugFingerprintOverride);
|
|
112
118
|
return;
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
const nextFingerprintId = buildDebugFingerprintId();
|
|
122
|
+
setDebugFingerprintOverride(nextFingerprintId);
|
|
123
|
+
setActiveDebugFingerprintId(nextFingerprintId);
|
|
124
|
+
}, []);
|
|
118
125
|
|
|
119
126
|
const creditBuckets = useMemo(() => {
|
|
120
127
|
if (!xCredit) return [];
|
|
@@ -178,32 +185,69 @@ export function FingerprintStatus() {
|
|
|
178
185
|
);
|
|
179
186
|
|
|
180
187
|
const runContextParallelInitTest = async () => {
|
|
188
|
+
const debugFingerprintId = activeDebugFingerprintId;
|
|
189
|
+
if (!debugFingerprintId) {
|
|
190
|
+
setTestResult('Test fingerprint override is not ready yet.');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
181
194
|
setIsRunningTest(true);
|
|
182
|
-
setTestResult(
|
|
195
|
+
setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
|
|
183
196
|
|
|
184
197
|
try {
|
|
185
198
|
await Promise.all([
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
199
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
200
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
201
|
+
initializeDebugAnonymousUser(debugFingerprintId),
|
|
189
202
|
]);
|
|
190
|
-
setTestResult(`
|
|
203
|
+
setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
|
|
191
204
|
} catch (testError) {
|
|
192
|
-
setTestResult(`
|
|
205
|
+
setTestResult(`Frontend Prevention Test failed: ${formatErrorMessage(testError)}`);
|
|
193
206
|
} finally {
|
|
194
207
|
setIsRunningTest(false);
|
|
195
208
|
}
|
|
196
209
|
};
|
|
197
210
|
|
|
211
|
+
const initializeDebugAnonymousUser = async (debugFingerprintId: string) => {
|
|
212
|
+
if (isInitializingDebugAnonymousUserRef.current) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
isInitializingDebugAnonymousUserRef.current = true;
|
|
218
|
+
|
|
219
|
+
const fingerprintHeaders = await createFingerprintHeaders();
|
|
220
|
+
const response = await fetch('/api/user/anonymous/init', {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
headers: {
|
|
223
|
+
'Content-Type': 'application/json',
|
|
224
|
+
[FINGERPRINT_SOURCE_REFER]: document.referrer || '',
|
|
225
|
+
...fingerprintHeaders,
|
|
226
|
+
'x-fingerprint-id-v8': debugFingerprintId,
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify({ fingerprintId: debugFingerprintId }),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
const errorData = await response.json().catch(() => ({}));
|
|
233
|
+
throw new Error(errorData.error || 'Failed to initialize anonymous user');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await response.json().catch(() => ({}));
|
|
237
|
+
} finally {
|
|
238
|
+
isInitializingDebugAnonymousUserRef.current = false;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
198
242
|
const runRawParallelPostTest = async () => {
|
|
199
|
-
const normalizedFingerprintId =
|
|
243
|
+
const normalizedFingerprintId = activeDebugFingerprintId;
|
|
200
244
|
if (!normalizedFingerprintId) {
|
|
201
|
-
setTestResult('
|
|
245
|
+
setTestResult('Test fingerprint override is not ready yet.');
|
|
202
246
|
return;
|
|
203
247
|
}
|
|
204
248
|
|
|
205
249
|
setIsRunningTest(true);
|
|
206
|
-
setTestResult(`Running
|
|
250
|
+
setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
|
|
207
251
|
|
|
208
252
|
try {
|
|
209
253
|
const fingerprintHeaders = await createFingerprintHeaders();
|
|
@@ -243,7 +287,7 @@ export function FingerprintStatus() {
|
|
|
243
287
|
|
|
244
288
|
setTestResult(
|
|
245
289
|
[
|
|
246
|
-
`
|
|
290
|
+
`Backend Idempotency Test done.`,
|
|
247
291
|
`created=${createdUserIds.length}`,
|
|
248
292
|
`reused=${reusedUserIds.length}`,
|
|
249
293
|
`failed=${failedStatuses.length}`,
|
|
@@ -252,32 +296,17 @@ export function FingerprintStatus() {
|
|
|
252
296
|
].filter(Boolean).join('\n')
|
|
253
297
|
);
|
|
254
298
|
} catch (testError) {
|
|
255
|
-
setTestResult(`
|
|
299
|
+
setTestResult(`Backend Idempotency Test failed: ${formatErrorMessage(testError)}`);
|
|
256
300
|
} finally {
|
|
257
301
|
setIsRunningTest(false);
|
|
258
302
|
}
|
|
259
303
|
};
|
|
260
304
|
|
|
261
|
-
const applyTestFingerprintAndReload = () => {
|
|
262
|
-
const normalizedFingerprintId = testFingerprintId.trim();
|
|
263
|
-
if (!normalizedFingerprintId) {
|
|
264
|
-
setTestResult('Please input a valid test fingerprint id before applying.');
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
setFingerprintId(normalizedFingerprintId);
|
|
270
|
-
setTestResult(`Applied test fingerprint and reloading: ${normalizedFingerprintId}`);
|
|
271
|
-
window.location.reload();
|
|
272
|
-
} catch (testError) {
|
|
273
|
-
setTestResult(`Apply test fingerprint failed: ${formatErrorMessage(testError)}`);
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
|
|
277
305
|
const regenerateTestFingerprint = () => {
|
|
278
306
|
const nextFingerprintId = buildDebugFingerprintId();
|
|
279
|
-
|
|
280
|
-
|
|
307
|
+
setDebugFingerprintOverride(nextFingerprintId);
|
|
308
|
+
setActiveDebugFingerprintId(nextFingerprintId);
|
|
309
|
+
setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
|
|
281
310
|
};
|
|
282
311
|
|
|
283
312
|
return (
|
|
@@ -442,18 +471,15 @@ export function FingerprintStatus() {
|
|
|
442
471
|
|
|
443
472
|
<div className="space-y-2 text-xs text-slate-500 dark:text-slate-300">
|
|
444
473
|
<div className="flex items-center justify-between gap-3">
|
|
445
|
-
<span className="text-slate-400 dark:text-slate-500">
|
|
474
|
+
<span className="text-slate-400 dark:text-slate-500">Real Browser</span>
|
|
446
475
|
<CopyableText text={fingerprintId || ''} />
|
|
447
476
|
</div>
|
|
448
477
|
<div className="space-y-1">
|
|
449
|
-
<span className="text-slate-400 dark:text-slate-500">
|
|
478
|
+
<span className="text-slate-400 dark:text-slate-500">Test Override</span>
|
|
450
479
|
<div className="flex items-center gap-2 py-1">
|
|
451
|
-
<
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
className="min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 outline-none transition focus:border-slate-400 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100"
|
|
455
|
-
placeholder="fp_test_dbg_20260322_xxx"
|
|
456
|
-
/>
|
|
480
|
+
<div className="min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100">
|
|
481
|
+
<CopyableText text={activeDebugFingerprintId || ''} />
|
|
482
|
+
</div>
|
|
457
483
|
<button
|
|
458
484
|
type="button"
|
|
459
485
|
disabled={isRunningTest}
|
|
@@ -463,15 +489,6 @@ export function FingerprintStatus() {
|
|
|
463
489
|
>
|
|
464
490
|
<icons.RefreshCcw className="size-4" />
|
|
465
491
|
</button>
|
|
466
|
-
<button
|
|
467
|
-
type="button"
|
|
468
|
-
disabled={isRunningTest}
|
|
469
|
-
onClick={applyTestFingerprintAndReload}
|
|
470
|
-
aria-label="Apply test fingerprint"
|
|
471
|
-
className="inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900"
|
|
472
|
-
>
|
|
473
|
-
<icons.CheckCheck className="size-4" />
|
|
474
|
-
</button>
|
|
475
492
|
</div>
|
|
476
493
|
</div>
|
|
477
494
|
</div>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// Fingerprint ID的存储键和header名
|
|
7
7
|
export const FINGERPRINT_STORAGE_KEY = '__x_fingerprint_id';
|
|
8
|
+
export const FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = '__x_fingerprint_debug_override';
|
|
8
9
|
export const FINGERPRINT_HEADER_NAME = 'x-fingerprint-id-v8';
|
|
9
10
|
export const FINGERPRINT_COOKIE_NAME = '__x_fingerprint_id';
|
|
10
11
|
export const FINGERPRINT_SOURCE_REFER = 'x-source-ref';
|
|
@@ -28,6 +29,7 @@ export function isValidFingerprintId(fingerprintId: string): boolean {
|
|
|
28
29
|
// 常量导出
|
|
29
30
|
export const FINGERPRINT_CONSTANTS = {
|
|
30
31
|
STORAGE_KEY: FINGERPRINT_STORAGE_KEY,
|
|
32
|
+
DEBUG_OVERRIDE_STORAGE_KEY: FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY,
|
|
31
33
|
HEADER_NAME: FINGERPRINT_HEADER_NAME,
|
|
32
34
|
COOKIE_NAME: FINGERPRINT_COOKIE_NAME,
|
|
33
35
|
FIRST_TOUCH_STORAGE_KEY: FINGERPRINT_FIRST_TOUCH_STORAGE_KEY,
|
package/src/main/delayed-img.tsx
CHANGED
|
@@ -10,14 +10,9 @@ interface DelayedImgProps extends ImageProps {
|
|
|
10
10
|
placeholderClassName?: string
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const ENV_DELAY_ENABLED =
|
|
14
|
-
process.env.NEXT_PUBLIC_DELAYED_IMG_ENABLED === "true" ||
|
|
15
|
-
process.env.NEXT_PUBLIC_DELAY_REVEAL_ENABLED === "true"
|
|
13
|
+
const ENV_DELAY_ENABLED = process.env.NEXT_PUBLIC_DELAYED_IMG_ENABLED === "true"
|
|
16
14
|
|
|
17
|
-
const rawDelaySeconds =
|
|
18
|
-
process.env.NEXT_PUBLIC_DELAYED_IMG_SECONDS ??
|
|
19
|
-
process.env.NEXT_PUBLIC_DELAY_REVEAL_SECONDS ??
|
|
20
|
-
"0"
|
|
15
|
+
const rawDelaySeconds = process.env.NEXT_PUBLIC_DELAYED_IMG_SECONDS ?? "0"
|
|
21
16
|
|
|
22
17
|
const parsedDelaySeconds = Number(rawDelaySeconds)
|
|
23
18
|
const ENV_DELAY_MS = Number.isFinite(parsedDelaySeconds) && parsedDelaySeconds > 0
|