@windrun-huaiin/third-ui 14.1.1 → 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.
@@ -0,0 +1,2 @@
1
+ export declare function getDebugFingerprintOverride(): string | null;
2
+ export declare function setDebugFingerprintOverride(fingerprintId: string): void;
@@ -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, initializeAnonymousUser, } = useFingerprintContext();
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
- if (testFingerprintId) {
84
+ const debugFingerprintOverride = fingerprintDebug.getDebugFingerprintOverride();
85
+ if (debugFingerprintOverride) {
86
+ setActiveDebugFingerprintId(debugFingerprintOverride);
83
87
  return;
84
88
  }
85
- const defaultFingerprintId = buildDebugFingerprintId();
86
- setTestFingerprintId(defaultFingerprintId);
87
- }, [testFingerprintId]);
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('Running context parallel init x3...');
156
+ setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
147
157
  try {
148
158
  yield Promise.all([
149
- initializeAnonymousUser(),
150
- initializeAnonymousUser(),
151
- initializeAnonymousUser(),
159
+ initializeDebugAnonymousUser(debugFingerprintId),
160
+ initializeDebugAnonymousUser(debugFingerprintId),
161
+ initializeDebugAnonymousUser(debugFingerprintId),
152
162
  ]);
153
- setTestResult(`Context parallel init finished. Active fingerprint: ${fingerprintId || '--'}`);
163
+ setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
154
164
  }
155
165
  catch (testError) {
156
- setTestResult(`Context parallel init failed: ${formatErrorMessage(testError)}`);
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 = testFingerprintId.trim();
195
+ const normalizedFingerprintId = activeDebugFingerprintId;
164
196
  if (!normalizedFingerprintId) {
165
- setTestResult('Please input a valid test fingerprint id.');
197
+ setTestResult('Test fingerprint override is not ready yet.');
166
198
  return;
167
199
  }
168
200
  setIsRunningTest(true);
169
- setTestResult(`Running raw POST x3 with fingerprint: ${normalizedFingerprintId}`);
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
- `Raw POST x3 done.`,
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(`Raw POST test failed: ${formatErrorMessage(testError)}`);
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
- setTestFingerprintId(nextFingerprintId);
233
- setTestResult(`Generated test fingerprint: ${nextFingerprintId}`);
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: "Current 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: "Generate New" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsxRuntime.jsx("input", { value: testFingerprintId, onChange: (e) => setTestFingerprintId(e.target.value), 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", placeholder: "fp_test_dbg_20260322_xxx" }), 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.jsx("button", { type: "button", disabled: isRunningTest, onClick: applyTestFingerprintAndReload, "aria-label": "Apply 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.CheckCheck, { 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" }) })] }))] })] })] }))] }));
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 { setFingerprintId, createFingerprintHeaders } from './fingerprint-client.mjs';
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, initializeAnonymousUser, } = useFingerprintContext();
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
- if (testFingerprintId) {
82
+ const debugFingerprintOverride = getDebugFingerprintOverride();
83
+ if (debugFingerprintOverride) {
84
+ setActiveDebugFingerprintId(debugFingerprintOverride);
81
85
  return;
82
86
  }
83
- const defaultFingerprintId = buildDebugFingerprintId();
84
- setTestFingerprintId(defaultFingerprintId);
85
- }, [testFingerprintId]);
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('Running context parallel init x3...');
154
+ setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
145
155
  try {
146
156
  yield Promise.all([
147
- initializeAnonymousUser(),
148
- initializeAnonymousUser(),
149
- initializeAnonymousUser(),
157
+ initializeDebugAnonymousUser(debugFingerprintId),
158
+ initializeDebugAnonymousUser(debugFingerprintId),
159
+ initializeDebugAnonymousUser(debugFingerprintId),
150
160
  ]);
151
- setTestResult(`Context parallel init finished. Active fingerprint: ${fingerprintId || '--'}`);
161
+ setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
152
162
  }
153
163
  catch (testError) {
154
- setTestResult(`Context parallel init failed: ${formatErrorMessage(testError)}`);
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 = testFingerprintId.trim();
193
+ const normalizedFingerprintId = activeDebugFingerprintId;
162
194
  if (!normalizedFingerprintId) {
163
- setTestResult('Please input a valid test fingerprint id.');
195
+ setTestResult('Test fingerprint override is not ready yet.');
164
196
  return;
165
197
  }
166
198
  setIsRunningTest(true);
167
- setTestResult(`Running raw POST x3 with fingerprint: ${normalizedFingerprintId}`);
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
- `Raw POST x3 done.`,
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(`Raw POST test failed: ${formatErrorMessage(testError)}`);
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
- setTestFingerprintId(nextFingerprintId);
231
- setTestResult(`Generated test fingerprint: ${nextFingerprintId}`);
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: "Current Browser" }), jsx(CopyableText, { text: fingerprintId || '' })] }), jsxs("div", { className: "space-y-1", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Generate New" }), jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsx("input", { value: testFingerprintId, onChange: (e) => setTestFingerprintId(e.target.value), 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", placeholder: "fp_test_dbg_20260322_xxx" }), 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" }) }), jsx("button", { type: "button", disabled: isRunningTest, onClick: applyTestFingerprintAndReload, "aria-label": "Apply 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.CheckCheck, { 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" }) })] }))] })] })] }))] }));
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "14.1.1",
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.2",
90
- "@windrun-huaiin/lib": "^14.0.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, setFingerprintId } from './fingerprint-client';
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
- if (testFingerprintId) {
115
+ const debugFingerprintOverride = getDebugFingerprintOverride();
116
+ if (debugFingerprintOverride) {
117
+ setActiveDebugFingerprintId(debugFingerprintOverride);
112
118
  return;
113
119
  }
114
120
 
115
- const defaultFingerprintId = buildDebugFingerprintId();
116
- setTestFingerprintId(defaultFingerprintId);
117
- }, [testFingerprintId]);
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('Running context parallel init x3...');
195
+ setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
183
196
 
184
197
  try {
185
198
  await Promise.all([
186
- initializeAnonymousUser(),
187
- initializeAnonymousUser(),
188
- initializeAnonymousUser(),
199
+ initializeDebugAnonymousUser(debugFingerprintId),
200
+ initializeDebugAnonymousUser(debugFingerprintId),
201
+ initializeDebugAnonymousUser(debugFingerprintId),
189
202
  ]);
190
- setTestResult(`Context parallel init finished. Active fingerprint: ${fingerprintId || '--'}`);
203
+ setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
191
204
  } catch (testError) {
192
- setTestResult(`Context parallel init failed: ${formatErrorMessage(testError)}`);
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 = testFingerprintId.trim();
243
+ const normalizedFingerprintId = activeDebugFingerprintId;
200
244
  if (!normalizedFingerprintId) {
201
- setTestResult('Please input a valid test fingerprint id.');
245
+ setTestResult('Test fingerprint override is not ready yet.');
202
246
  return;
203
247
  }
204
248
 
205
249
  setIsRunningTest(true);
206
- setTestResult(`Running raw POST x3 with fingerprint: ${normalizedFingerprintId}`);
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
- `Raw POST x3 done.`,
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(`Raw POST test failed: ${formatErrorMessage(testError)}`);
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
- setTestFingerprintId(nextFingerprintId);
280
- setTestResult(`Generated test fingerprint: ${nextFingerprintId}`);
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">Current Browser</span>
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">Generate New</span>
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
- <input
452
- value={testFingerprintId}
453
- onChange={(e) => setTestFingerprintId(e.target.value)}
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,