@windrun-huaiin/third-ui 14.4.1 → 14.4.3

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.
@@ -1,2 +1,5 @@
1
1
  export declare function getDebugFingerprintOverride(): string | null;
2
2
  export declare function setDebugFingerprintOverride(fingerprintId: string): void;
3
+ export declare function buildDebugFingerprintId(): string;
4
+ export declare function getOrCreateDebugFingerprintOverride(): string;
5
+ export declare function regenerateDebugFingerprintOverride(): string;
@@ -27,17 +27,39 @@ function setLocalStorageValue(key, value) {
27
27
  }
28
28
  function getDebugFingerprintOverride() {
29
29
  const value = getLocalStorageValue(fingerprintShared.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY);
30
- if (!value || !fingerprintShared.isValidFingerprintId(value)) {
30
+ if (!value || !fingerprintShared.isValidFingerprintId(value) || !fingerprintShared.isDebugFingerprintId(value)) {
31
31
  return null;
32
32
  }
33
33
  return value;
34
34
  }
35
35
  function setDebugFingerprintOverride(fingerprintId) {
36
- if (!fingerprintShared.isValidFingerprintId(fingerprintId)) {
36
+ if (!fingerprintShared.isValidFingerprintId(fingerprintId) || !fingerprintShared.isDebugFingerprintId(fingerprintId)) {
37
37
  throw new Error('Invalid fingerprint ID');
38
38
  }
39
39
  setLocalStorageValue(fingerprintShared.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, fingerprintId);
40
40
  }
41
+ function buildDebugFingerprintId() {
42
+ const timestamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
43
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
44
+ return `${fingerprintShared.FINGERPRINT_DEBUG_PREFIX}${timestamp}_${randomSuffix}`;
45
+ }
46
+ function getOrCreateDebugFingerprintOverride() {
47
+ const existing = getDebugFingerprintOverride();
48
+ if (existing) {
49
+ return existing;
50
+ }
51
+ const nextFingerprintId = buildDebugFingerprintId();
52
+ setDebugFingerprintOverride(nextFingerprintId);
53
+ return nextFingerprintId;
54
+ }
55
+ function regenerateDebugFingerprintOverride() {
56
+ const nextFingerprintId = buildDebugFingerprintId();
57
+ setDebugFingerprintOverride(nextFingerprintId);
58
+ return nextFingerprintId;
59
+ }
41
60
 
61
+ exports.buildDebugFingerprintId = buildDebugFingerprintId;
42
62
  exports.getDebugFingerprintOverride = getDebugFingerprintOverride;
63
+ exports.getOrCreateDebugFingerprintOverride = getOrCreateDebugFingerprintOverride;
64
+ exports.regenerateDebugFingerprintOverride = regenerateDebugFingerprintOverride;
43
65
  exports.setDebugFingerprintOverride = setDebugFingerprintOverride;
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { isValidFingerprintId, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY } from './fingerprint-shared.mjs';
2
+ import { isValidFingerprintId, isDebugFingerprintId, FINGERPRINT_DEBUG_PREFIX, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY } from './fingerprint-shared.mjs';
3
3
 
4
4
  function getLocalStorageValue(key) {
5
5
  if (typeof window === 'undefined') {
@@ -25,16 +25,35 @@ function setLocalStorageValue(key, value) {
25
25
  }
26
26
  function getDebugFingerprintOverride() {
27
27
  const value = getLocalStorageValue(FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY);
28
- if (!value || !isValidFingerprintId(value)) {
28
+ if (!value || !isValidFingerprintId(value) || !isDebugFingerprintId(value)) {
29
29
  return null;
30
30
  }
31
31
  return value;
32
32
  }
33
33
  function setDebugFingerprintOverride(fingerprintId) {
34
- if (!isValidFingerprintId(fingerprintId)) {
34
+ if (!isValidFingerprintId(fingerprintId) || !isDebugFingerprintId(fingerprintId)) {
35
35
  throw new Error('Invalid fingerprint ID');
36
36
  }
37
37
  setLocalStorageValue(FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, fingerprintId);
38
38
  }
39
+ function buildDebugFingerprintId() {
40
+ const timestamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
41
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
42
+ return `${FINGERPRINT_DEBUG_PREFIX}${timestamp}_${randomSuffix}`;
43
+ }
44
+ function getOrCreateDebugFingerprintOverride() {
45
+ const existing = getDebugFingerprintOverride();
46
+ if (existing) {
47
+ return existing;
48
+ }
49
+ const nextFingerprintId = buildDebugFingerprintId();
50
+ setDebugFingerprintOverride(nextFingerprintId);
51
+ return nextFingerprintId;
52
+ }
53
+ function regenerateDebugFingerprintOverride() {
54
+ const nextFingerprintId = buildDebugFingerprintId();
55
+ setDebugFingerprintOverride(nextFingerprintId);
56
+ return nextFingerprintId;
57
+ }
39
58
 
40
- export { getDebugFingerprintOverride, setDebugFingerprintOverride };
59
+ export { buildDebugFingerprintId, getDebugFingerprintOverride, getOrCreateDebugFingerprintOverride, regenerateDebugFingerprintOverride, setDebugFingerprintOverride };
@@ -81,15 +81,11 @@ function FingerprintStatus() {
81
81
  }
82
82
  }, [xUser]);
83
83
  React.useEffect(() => {
84
- const debugFingerprintOverride = fingerprintDebug.getDebugFingerprintOverride();
85
- if (debugFingerprintOverride) {
86
- setActiveDebugFingerprintId(debugFingerprintOverride);
84
+ if (panelMode !== 'test') {
87
85
  return;
88
86
  }
89
- const nextFingerprintId = buildDebugFingerprintId();
90
- fingerprintDebug.setDebugFingerprintOverride(nextFingerprintId);
91
- setActiveDebugFingerprintId(nextFingerprintId);
92
- }, []);
87
+ setActiveDebugFingerprintId((current) => current !== null && current !== void 0 ? current : fingerprintDebug.getOrCreateDebugFingerprintOverride());
88
+ }, [panelMode]);
93
89
  const creditBuckets = React.useMemo(() => {
94
90
  if (!xCredit)
95
91
  return [];
@@ -147,11 +143,12 @@ function FingerprintStatus() {
147
143
  const subStatus = subscriptionStatus.status;
148
144
  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);
149
145
  const runContextParallelInitTest = () => tslib_es6.__awaiter(this, void 0, void 0, function* () {
150
- const debugFingerprintId = activeDebugFingerprintId;
146
+ const debugFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : fingerprintDebug.getOrCreateDebugFingerprintOverride();
151
147
  if (!debugFingerprintId) {
152
148
  setTestResult('Test fingerprint override is not ready yet.');
153
149
  return;
154
150
  }
151
+ setActiveDebugFingerprintId(debugFingerprintId);
155
152
  setIsRunningTest(true);
156
153
  setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
157
154
  try {
@@ -192,11 +189,12 @@ function FingerprintStatus() {
192
189
  }
193
190
  });
194
191
  const runRawParallelPostTest = () => tslib_es6.__awaiter(this, void 0, void 0, function* () {
195
- const normalizedFingerprintId = activeDebugFingerprintId;
192
+ const normalizedFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : fingerprintDebug.getOrCreateDebugFingerprintOverride();
196
193
  if (!normalizedFingerprintId) {
197
194
  setTestResult('Test fingerprint override is not ready yet.');
198
195
  return;
199
196
  }
197
+ setActiveDebugFingerprintId(normalizedFingerprintId);
200
198
  setIsRunningTest(true);
201
199
  setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
202
200
  try {
@@ -245,8 +243,7 @@ function FingerprintStatus() {
245
243
  }
246
244
  });
247
245
  const regenerateTestFingerprint = () => {
248
- const nextFingerprintId = buildDebugFingerprintId();
249
- fingerprintDebug.setDebugFingerprintOverride(nextFingerprintId);
246
+ const nextFingerprintId = fingerprintDebug.regenerateDebugFingerprintOverride();
250
247
  setActiveDebugFingerprintId(nextFingerprintId);
251
248
  setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
252
249
  };
@@ -338,11 +335,6 @@ function StatusTag({ value }) {
338
335
  const badgeClass = colorMap[normalized] || defaultColor;
339
336
  return (jsxRuntime.jsx("span", { className: utils.cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
340
337
  }
341
- function buildDebugFingerprintId() {
342
- const timestamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
343
- const randomSuffix = Math.random().toString(36).slice(2, 8);
344
- return `fp_test_dbg_${timestamp}_${randomSuffix}`;
345
- }
346
338
  function formatErrorMessage(error) {
347
339
  if (error instanceof Error) {
348
340
  return error.message;
@@ -8,7 +8,7 @@ import { createContext, useContext, useState, useRef, useEffect, useMemo } from
8
8
  import { useFingerprint } from './use-fingerprint.mjs';
9
9
  import { CopyableText } from '@windrun-huaiin/base-ui/ui';
10
10
  import { createFingerprintHeaders } from './fingerprint-client.mjs';
11
- import { getDebugFingerprintOverride, setDebugFingerprintOverride } from './fingerprint-debug.mjs';
11
+ import { getOrCreateDebugFingerprintOverride, regenerateDebugFingerprintOverride } from './fingerprint-debug.mjs';
12
12
  import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
13
13
 
14
14
  const FingerprintContext = createContext(undefined);
@@ -79,15 +79,11 @@ function FingerprintStatus() {
79
79
  }
80
80
  }, [xUser]);
81
81
  useEffect(() => {
82
- const debugFingerprintOverride = getDebugFingerprintOverride();
83
- if (debugFingerprintOverride) {
84
- setActiveDebugFingerprintId(debugFingerprintOverride);
82
+ if (panelMode !== 'test') {
85
83
  return;
86
84
  }
87
- const nextFingerprintId = buildDebugFingerprintId();
88
- setDebugFingerprintOverride(nextFingerprintId);
89
- setActiveDebugFingerprintId(nextFingerprintId);
90
- }, []);
85
+ setActiveDebugFingerprintId((current) => current !== null && current !== void 0 ? current : getOrCreateDebugFingerprintOverride());
86
+ }, [panelMode]);
91
87
  const creditBuckets = useMemo(() => {
92
88
  if (!xCredit)
93
89
  return [];
@@ -145,11 +141,12 @@ function FingerprintStatus() {
145
141
  const subStatus = subscriptionStatus.status;
146
142
  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);
147
143
  const runContextParallelInitTest = () => __awaiter(this, void 0, void 0, function* () {
148
- const debugFingerprintId = activeDebugFingerprintId;
144
+ const debugFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : getOrCreateDebugFingerprintOverride();
149
145
  if (!debugFingerprintId) {
150
146
  setTestResult('Test fingerprint override is not ready yet.');
151
147
  return;
152
148
  }
149
+ setActiveDebugFingerprintId(debugFingerprintId);
153
150
  setIsRunningTest(true);
154
151
  setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
155
152
  try {
@@ -190,11 +187,12 @@ function FingerprintStatus() {
190
187
  }
191
188
  });
192
189
  const runRawParallelPostTest = () => __awaiter(this, void 0, void 0, function* () {
193
- const normalizedFingerprintId = activeDebugFingerprintId;
190
+ const normalizedFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : getOrCreateDebugFingerprintOverride();
194
191
  if (!normalizedFingerprintId) {
195
192
  setTestResult('Test fingerprint override is not ready yet.');
196
193
  return;
197
194
  }
195
+ setActiveDebugFingerprintId(normalizedFingerprintId);
198
196
  setIsRunningTest(true);
199
197
  setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
200
198
  try {
@@ -243,8 +241,7 @@ function FingerprintStatus() {
243
241
  }
244
242
  });
245
243
  const regenerateTestFingerprint = () => {
246
- const nextFingerprintId = buildDebugFingerprintId();
247
- setDebugFingerprintOverride(nextFingerprintId);
244
+ const nextFingerprintId = regenerateDebugFingerprintOverride();
248
245
  setActiveDebugFingerprintId(nextFingerprintId);
249
246
  setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
250
247
  };
@@ -336,11 +333,6 @@ function StatusTag({ value }) {
336
333
  const badgeClass = colorMap[normalized] || defaultColor;
337
334
  return (jsx("span", { className: cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
338
335
  }
339
- function buildDebugFingerprintId() {
340
- const timestamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
341
- const randomSuffix = Math.random().toString(36).slice(2, 8);
342
- return `fp_test_dbg_${timestamp}_${randomSuffix}`;
343
- }
344
336
  function formatErrorMessage(error) {
345
337
  if (error instanceof Error) {
346
338
  return error.message;
@@ -10,11 +10,13 @@ export declare const FINGERPRINT_SOURCE_REFER = "x-source-ref";
10
10
  export declare const FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = "__x_first_touch";
11
11
  export declare const FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = "__x_first_touch";
12
12
  export declare const FINGERPRINT_FIRST_TOUCH_HEADER = "x-first-touch";
13
+ export declare const FINGERPRINT_DEBUG_PREFIX = "fp_test_dbg_";
13
14
  /**
14
15
  * 验证fingerprint ID格式
15
16
  * 可以在客户端和服务端使用
16
17
  */
17
18
  export declare function isValidFingerprintId(fingerprintId: string): boolean;
19
+ export declare function isDebugFingerprintId(fingerprintId: string | null | undefined): boolean;
18
20
  export declare const FINGERPRINT_CONSTANTS: {
19
21
  readonly STORAGE_KEY: "__x_fingerprint_id";
20
22
  readonly DEBUG_OVERRIDE_STORAGE_KEY: "__x_fingerprint_debug_override";
@@ -23,4 +25,5 @@ export declare const FINGERPRINT_CONSTANTS: {
23
25
  readonly FIRST_TOUCH_STORAGE_KEY: "__x_first_touch";
24
26
  readonly FIRST_TOUCH_COOKIE_NAME: "__x_first_touch";
25
27
  readonly FIRST_TOUCH_HEADER: "x-first-touch";
28
+ readonly DEBUG_PREFIX: "fp_test_dbg_";
26
29
  };
@@ -13,6 +13,7 @@ const FINGERPRINT_SOURCE_REFER = 'x-source-ref';
13
13
  const FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = '__x_first_touch';
14
14
  const FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = '__x_first_touch';
15
15
  const FINGERPRINT_FIRST_TOUCH_HEADER = 'x-first-touch';
16
+ const FINGERPRINT_DEBUG_PREFIX = 'fp_test_dbg_';
16
17
  /**
17
18
  * 验证fingerprint ID格式
18
19
  * 可以在客户端和服务端使用
@@ -24,7 +25,14 @@ function isValidFingerprintId(fingerprintId) {
24
25
  // - fp_ + FingerprintJS visitorId (变长字符串)
25
26
  // - fp_fallback_ + 时间戳_随机字符串 (客户端降级方案)
26
27
  // - fp_server_ + 时间戳_随机字符串 (服务端降级)
27
- return /^fp(_fallback|_server)?_[a-zA-Z0-9_]+$/.test(fingerprintId);
28
+ // - fp_test_dbg_ + 时间戳_随机字符串 (调试并发测试)
29
+ return /^fp(_fallback|_server|_test_dbg)?_[a-zA-Z0-9_]+$/.test(fingerprintId);
30
+ }
31
+ function isDebugFingerprintId(fingerprintId) {
32
+ if (!fingerprintId) {
33
+ return false;
34
+ }
35
+ return fingerprintId.startsWith(FINGERPRINT_DEBUG_PREFIX);
28
36
  }
29
37
  // 常量导出
30
38
  const FINGERPRINT_CONSTANTS = {
@@ -35,15 +43,18 @@ const FINGERPRINT_CONSTANTS = {
35
43
  FIRST_TOUCH_STORAGE_KEY: FINGERPRINT_FIRST_TOUCH_STORAGE_KEY,
36
44
  FIRST_TOUCH_COOKIE_NAME: FINGERPRINT_FIRST_TOUCH_COOKIE_NAME,
37
45
  FIRST_TOUCH_HEADER: FINGERPRINT_FIRST_TOUCH_HEADER,
46
+ DEBUG_PREFIX: FINGERPRINT_DEBUG_PREFIX,
38
47
  };
39
48
 
40
49
  exports.FINGERPRINT_CONSTANTS = FINGERPRINT_CONSTANTS;
41
50
  exports.FINGERPRINT_COOKIE_NAME = FINGERPRINT_COOKIE_NAME;
42
51
  exports.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY;
52
+ exports.FINGERPRINT_DEBUG_PREFIX = FINGERPRINT_DEBUG_PREFIX;
43
53
  exports.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = FINGERPRINT_FIRST_TOUCH_COOKIE_NAME;
44
54
  exports.FINGERPRINT_FIRST_TOUCH_HEADER = FINGERPRINT_FIRST_TOUCH_HEADER;
45
55
  exports.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = FINGERPRINT_FIRST_TOUCH_STORAGE_KEY;
46
56
  exports.FINGERPRINT_HEADER_NAME = FINGERPRINT_HEADER_NAME;
47
57
  exports.FINGERPRINT_SOURCE_REFER = FINGERPRINT_SOURCE_REFER;
48
58
  exports.FINGERPRINT_STORAGE_KEY = FINGERPRINT_STORAGE_KEY;
59
+ exports.isDebugFingerprintId = isDebugFingerprintId;
49
60
  exports.isValidFingerprintId = isValidFingerprintId;
@@ -11,6 +11,7 @@ const FINGERPRINT_SOURCE_REFER = 'x-source-ref';
11
11
  const FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = '__x_first_touch';
12
12
  const FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = '__x_first_touch';
13
13
  const FINGERPRINT_FIRST_TOUCH_HEADER = 'x-first-touch';
14
+ const FINGERPRINT_DEBUG_PREFIX = 'fp_test_dbg_';
14
15
  /**
15
16
  * 验证fingerprint ID格式
16
17
  * 可以在客户端和服务端使用
@@ -22,7 +23,14 @@ function isValidFingerprintId(fingerprintId) {
22
23
  // - fp_ + FingerprintJS visitorId (变长字符串)
23
24
  // - fp_fallback_ + 时间戳_随机字符串 (客户端降级方案)
24
25
  // - fp_server_ + 时间戳_随机字符串 (服务端降级)
25
- return /^fp(_fallback|_server)?_[a-zA-Z0-9_]+$/.test(fingerprintId);
26
+ // - fp_test_dbg_ + 时间戳_随机字符串 (调试并发测试)
27
+ return /^fp(_fallback|_server|_test_dbg)?_[a-zA-Z0-9_]+$/.test(fingerprintId);
28
+ }
29
+ function isDebugFingerprintId(fingerprintId) {
30
+ if (!fingerprintId) {
31
+ return false;
32
+ }
33
+ return fingerprintId.startsWith(FINGERPRINT_DEBUG_PREFIX);
26
34
  }
27
35
  // 常量导出
28
36
  const FINGERPRINT_CONSTANTS = {
@@ -33,6 +41,7 @@ const FINGERPRINT_CONSTANTS = {
33
41
  FIRST_TOUCH_STORAGE_KEY: FINGERPRINT_FIRST_TOUCH_STORAGE_KEY,
34
42
  FIRST_TOUCH_COOKIE_NAME: FINGERPRINT_FIRST_TOUCH_COOKIE_NAME,
35
43
  FIRST_TOUCH_HEADER: FINGERPRINT_FIRST_TOUCH_HEADER,
44
+ DEBUG_PREFIX: FINGERPRINT_DEBUG_PREFIX,
36
45
  };
37
46
 
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 };
47
+ export { FINGERPRINT_CONSTANTS, FINGERPRINT_COOKIE_NAME, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, FINGERPRINT_DEBUG_PREFIX, FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_HEADER, FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER, FINGERPRINT_STORAGE_KEY, isDebugFingerprintId, isValidFingerprintId };
@@ -11,12 +11,14 @@ var fingerprintProvider = require('./fingerprint-provider.js');
11
11
  exports.FINGERPRINT_CONSTANTS = fingerprintShared.FINGERPRINT_CONSTANTS;
12
12
  exports.FINGERPRINT_COOKIE_NAME = fingerprintShared.FINGERPRINT_COOKIE_NAME;
13
13
  exports.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = fingerprintShared.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY;
14
+ exports.FINGERPRINT_DEBUG_PREFIX = fingerprintShared.FINGERPRINT_DEBUG_PREFIX;
14
15
  exports.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = fingerprintShared.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME;
15
16
  exports.FINGERPRINT_FIRST_TOUCH_HEADER = fingerprintShared.FINGERPRINT_FIRST_TOUCH_HEADER;
16
17
  exports.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = fingerprintShared.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY;
17
18
  exports.FINGERPRINT_HEADER_NAME = fingerprintShared.FINGERPRINT_HEADER_NAME;
18
19
  exports.FINGERPRINT_SOURCE_REFER = fingerprintShared.FINGERPRINT_SOURCE_REFER;
19
20
  exports.FINGERPRINT_STORAGE_KEY = fingerprintShared.FINGERPRINT_STORAGE_KEY;
21
+ exports.isDebugFingerprintId = fingerprintShared.isDebugFingerprintId;
20
22
  exports.isValidFingerprintId = fingerprintShared.isValidFingerprintId;
21
23
  exports.clearFingerprintId = fingerprintClient.clearFingerprintId;
22
24
  exports.createFingerprintFetch = fingerprintClient.createFingerprintFetch;
@@ -1,5 +1,5 @@
1
1
  "use client";
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';
2
+ export { FINGERPRINT_CONSTANTS, FINGERPRINT_COOKIE_NAME, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, FINGERPRINT_DEBUG_PREFIX, FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_HEADER, FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER, FINGERPRINT_STORAGE_KEY, isDebugFingerprintId, 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';
@@ -8,12 +8,14 @@ var fingerprintServer = require('./fingerprint-server.js');
8
8
  exports.FINGERPRINT_CONSTANTS = fingerprintShared.FINGERPRINT_CONSTANTS;
9
9
  exports.FINGERPRINT_COOKIE_NAME = fingerprintShared.FINGERPRINT_COOKIE_NAME;
10
10
  exports.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = fingerprintShared.FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY;
11
+ exports.FINGERPRINT_DEBUG_PREFIX = fingerprintShared.FINGERPRINT_DEBUG_PREFIX;
11
12
  exports.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = fingerprintShared.FINGERPRINT_FIRST_TOUCH_COOKIE_NAME;
12
13
  exports.FINGERPRINT_FIRST_TOUCH_HEADER = fingerprintShared.FINGERPRINT_FIRST_TOUCH_HEADER;
13
14
  exports.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = fingerprintShared.FINGERPRINT_FIRST_TOUCH_STORAGE_KEY;
14
15
  exports.FINGERPRINT_HEADER_NAME = fingerprintShared.FINGERPRINT_HEADER_NAME;
15
16
  exports.FINGERPRINT_SOURCE_REFER = fingerprintShared.FINGERPRINT_SOURCE_REFER;
16
17
  exports.FINGERPRINT_STORAGE_KEY = fingerprintShared.FINGERPRINT_STORAGE_KEY;
18
+ exports.isDebugFingerprintId = fingerprintShared.isDebugFingerprintId;
17
19
  exports.isValidFingerprintId = fingerprintShared.isValidFingerprintId;
18
20
  exports.extractFingerprintFromNextRequest = fingerprintServer.extractFingerprintFromNextRequest;
19
21
  exports.extractFingerprintFromNextStores = fingerprintServer.extractFingerprintFromNextStores;
@@ -1,2 +1,2 @@
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';
1
+ export { FINGERPRINT_CONSTANTS, FINGERPRINT_COOKIE_NAME, FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, FINGERPRINT_DEBUG_PREFIX, FINGERPRINT_FIRST_TOUCH_COOKIE_NAME, FINGERPRINT_FIRST_TOUCH_HEADER, FINGERPRINT_FIRST_TOUCH_STORAGE_KEY, FINGERPRINT_HEADER_NAME, FINGERPRINT_SOURCE_REFER, FINGERPRINT_STORAGE_KEY, isDebugFingerprintId, isValidFingerprintId } from './fingerprint-shared.mjs';
2
2
  export { extractFingerprintFromNextRequest, extractFingerprintFromNextStores, extractFingerprintId, generateServerFingerprintId } from './fingerprint-server.mjs';
@@ -99,8 +99,13 @@ function useFingerprint(config) {
99
99
  setXCredit(data.xCredit || null);
100
100
  setXSubscription(data.xSubscription || null);
101
101
  setIsInitialized(true);
102
- if (((_a = data.xUser) === null || _a === void 0 ? void 0 : _a.fingerprintId) && data.xUser.fingerprintId !== fingerprintId) {
103
- setFingerprintIdState(data.xUser.fingerprintId);
102
+ const canonicalFingerprintId = (_a = data.xUser) === null || _a === void 0 ? void 0 : _a.fingerprintId;
103
+ if (canonicalFingerprintId &&
104
+ fingerprintShared.isValidFingerprintId(canonicalFingerprintId) &&
105
+ !fingerprintShared.isDebugFingerprintId(canonicalFingerprintId) &&
106
+ canonicalFingerprintId !== fingerprintId) {
107
+ setFingerprintIdState(canonicalFingerprintId);
108
+ fingerprintClient.setFingerprintId(canonicalFingerprintId);
104
109
  }
105
110
  }
106
111
  else {
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
3
3
  import { useState, useRef, useCallback, useEffect } from 'react';
4
- import { getOrCreateFirstTouchData, getOrGenerateFingerprintId, createFingerprintHeaders } from './fingerprint-client.mjs';
5
- import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
4
+ import { getOrCreateFirstTouchData, getOrGenerateFingerprintId, createFingerprintHeaders, setFingerprintId } from './fingerprint-client.mjs';
5
+ import { FINGERPRINT_SOURCE_REFER, isValidFingerprintId, isDebugFingerprintId } from './fingerprint-shared.mjs';
6
6
 
7
7
  /**
8
8
  * Hook for managing fingerprint ID and anonymous user data
@@ -97,8 +97,13 @@ function useFingerprint(config) {
97
97
  setXCredit(data.xCredit || null);
98
98
  setXSubscription(data.xSubscription || null);
99
99
  setIsInitialized(true);
100
- if (((_a = data.xUser) === null || _a === void 0 ? void 0 : _a.fingerprintId) && data.xUser.fingerprintId !== fingerprintId) {
101
- setFingerprintIdState(data.xUser.fingerprintId);
100
+ const canonicalFingerprintId = (_a = data.xUser) === null || _a === void 0 ? void 0 : _a.fingerprintId;
101
+ if (canonicalFingerprintId &&
102
+ isValidFingerprintId(canonicalFingerprintId) &&
103
+ !isDebugFingerprintId(canonicalFingerprintId) &&
104
+ canonicalFingerprintId !== fingerprintId) {
105
+ setFingerprintIdState(canonicalFingerprintId);
106
+ setFingerprintId(canonicalFingerprintId);
102
107
  }
103
108
  }
104
109
  else {
@@ -45,6 +45,10 @@ const CLERK_ACTIVE_ANIMATION_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)';
45
45
  const CLERK_TEXT_GAP_FROM_PATH = 12;
46
46
  // Radius of numbered step badges rendered on top of the path centerline.
47
47
  const CLERK_STEP_BADGE_RADIUS = 7;
48
+ // Visual line offsets by grouped heading depth: 1/2, 3, 4, >4.
49
+ const CLERK_DEPTH_GROUP_LINE_OFFSETS = [6, 18, 30, 42];
50
+ // Max number of characters rendered for a TOC label before trimming with ellipsis.
51
+ const CLERK_MAX_LABEL_LENGTH = 44;
48
52
  function PortableClerkTOC({ toc, header, footer, title, emptyLabel = 'No headings', className, }) {
49
53
  return (jsxRuntime.jsxs(page.PageTOC, { className: className, children: [header, title !== null && title !== void 0 ? title : jsxRuntime.jsx(page.PageTOCTitle, {}), jsxRuntime.jsx(PortableClerkTOCScrollArea, { children: jsxRuntime.jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }) }), footer] }));
50
54
  }
@@ -124,16 +128,16 @@ function PortableClerkTOCItems(_a) {
124
128
  if (toc.length === 0) {
125
129
  return (jsxRuntime.jsx("div", { className: "rounded-lg border bg-fd-card p-3 text-xs text-fd-muted-foreground", children: emptyLabel }));
126
130
  }
127
- return (jsxRuntime.jsxs("div", Object.assign({ ref: mergeRefs(containerRef, ref), className: cn('relative flex flex-col', className) }, props, { children: [jsxRuntime.jsx(ClerkOutline, { path: outlinePath, items: layout.items, activePath: activePath, activeAnchors: activeAnchors, activeEndpoint: activeEndpoint }), metas.map((meta, i) => (jsxRuntime.jsx(PortableClerkTOCItem, { item: meta.item, isActive: activeAnchors.includes(meta.item.url.slice(1)), resolvedContent: meta.resolvedContent, itemPadding: meta.itemPadding, contentRef: (node) => {
131
+ return (jsxRuntime.jsxs("div", Object.assign({ ref: mergeRefs(containerRef, ref), className: cn('relative flex flex-col', className) }, props, { children: [jsxRuntime.jsx(ClerkOutline, { path: outlinePath, items: layout.items, activePath: activePath, activeAnchors: activeAnchors, activeEndpoint: activeEndpoint }), metas.map((meta, i) => (jsxRuntime.jsx(PortableClerkTOCItem, { item: meta.item, isActive: activeAnchors.includes(meta.item.url.slice(1)), resolvedContent: meta.resolvedContent, fullTitle: meta.fullTitle, itemPadding: meta.itemPadding, contentRef: (node) => {
128
132
  contentRefs.current[i] = node;
129
133
  }, ref: (node) => {
130
134
  itemRefs.current[i] = node;
131
135
  } }, meta.item.url)))] })));
132
136
  }
133
- function PortableClerkTOCItem({ item, isActive, resolvedContent, itemPadding, contentRef, ref, }) {
134
- return (jsxRuntime.jsx(Primitive__namespace.TOCItem, { ref: ref, href: item.url, "data-clerk-item": "", style: {
137
+ function PortableClerkTOCItem({ item, isActive, resolvedContent, fullTitle, itemPadding, contentRef, ref, }) {
138
+ return (jsxRuntime.jsx(Primitive__namespace.TOCItem, { ref: ref, href: item.url, "data-clerk-item": "", title: fullTitle !== null && fullTitle !== void 0 ? fullTitle : undefined, style: {
135
139
  paddingInlineStart: itemPadding,
136
- }, className: cn('prose group relative py-1.5 text-sm transition-colors wrap-anywhere first:pt-0 last:pb-0 hover:text-fd-accent-foreground', isActive ? lib.themeIconColor : 'text-fd-muted-foreground'), children: jsxRuntime.jsx("span", { ref: contentRef, className: "relative z-10", children: resolvedContent }) }));
140
+ }, className: cn('prose group relative py-1.5 text-sm transition-colors first:pt-0 last:pb-0 hover:text-fd-accent-foreground', isActive ? lib.themeIconColor : 'text-fd-muted-foreground'), children: jsxRuntime.jsx("span", { ref: contentRef, className: "relative z-10 block overflow-hidden text-ellipsis whitespace-nowrap", children: resolvedContent }) }));
137
141
  }
138
142
  function ClerkOutline({ path, items, activePath, activeAnchors, activeEndpoint, }) {
139
143
  const activeSet = new Set(activeAnchors);
@@ -176,19 +180,33 @@ function getItemOffset(depth) {
176
180
  return lineOffset + badgeRadius + CLERK_TEXT_GAP_FROM_PATH;
177
181
  }
178
182
  function getLineOffset(depth) {
179
- return depth >= 3 ? 18 : 6;
183
+ const group = getDepthGroup(depth);
184
+ return CLERK_DEPTH_GROUP_LINE_OFFSETS[group];
180
185
  }
181
186
  function getVisualLinePosition(depth) {
182
187
  return getLineOffset(depth);
183
188
  }
189
+ function getDepthGroup(depth) {
190
+ if (depth <= 2)
191
+ return 0;
192
+ if (depth === 3)
193
+ return 1;
194
+ if (depth === 4)
195
+ return 2;
196
+ return 3;
197
+ }
184
198
  function resolveClerkItem(item) {
185
199
  const isH3 = item.depth === 3;
186
200
  const rawTitle = typeof item.title === 'string' ? item.title : '';
187
201
  const { isStep, displayStep, content } = getStepInfoFromTitle(rawTitle);
188
202
  let stepNumber = isH3 && isStep ? String(displayStep) : null;
189
203
  let resolvedContent = item.title;
204
+ let fullTitle = rawTitle || null;
190
205
  if (isH3 && isStep) {
191
206
  resolvedContent = content !== null && content !== void 0 ? content : item.title;
207
+ if (typeof content === 'string') {
208
+ fullTitle = content;
209
+ }
192
210
  }
193
211
  if (isH3 && !stepNumber) {
194
212
  const urlNum = getDigitsFromUrl(item.url);
@@ -199,18 +217,30 @@ function resolveClerkItem(item) {
199
217
  const match = rawTitle.match(/^(\d+(?:\.\d+)*\.?)\s+(.+)$/);
200
218
  if (match === null || match === void 0 ? void 0 : match[2]) {
201
219
  resolvedContent = match[2];
220
+ fullTitle = match[2];
202
221
  }
203
222
  }
204
223
  }
205
224
  }
225
+ if (typeof resolvedContent === 'string') {
226
+ fullTitle = resolvedContent;
227
+ resolvedContent = truncateClerkLabel(resolvedContent);
228
+ }
206
229
  return {
207
230
  item,
208
231
  resolvedContent,
232
+ fullTitle,
209
233
  stepNumber,
210
234
  itemPadding: getItemOffset(item.depth),
211
235
  lineOffset: getVisualLinePosition(item.depth),
212
236
  };
213
237
  }
238
+ function truncateClerkLabel(value) {
239
+ const normalized = value.trim();
240
+ if (normalized.length <= CLERK_MAX_LABEL_LENGTH)
241
+ return normalized;
242
+ return `${normalized.slice(0, CLERK_MAX_LABEL_LENGTH).trimEnd()}...`;
243
+ }
214
244
  function buildOutlinePath(items) {
215
245
  if (items.length === 0)
216
246
  return '';
@@ -24,6 +24,10 @@ const CLERK_ACTIVE_ANIMATION_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)';
24
24
  const CLERK_TEXT_GAP_FROM_PATH = 12;
25
25
  // Radius of numbered step badges rendered on top of the path centerline.
26
26
  const CLERK_STEP_BADGE_RADIUS = 7;
27
+ // Visual line offsets by grouped heading depth: 1/2, 3, 4, >4.
28
+ const CLERK_DEPTH_GROUP_LINE_OFFSETS = [6, 18, 30, 42];
29
+ // Max number of characters rendered for a TOC label before trimming with ellipsis.
30
+ const CLERK_MAX_LABEL_LENGTH = 44;
27
31
  function PortableClerkTOC({ toc, header, footer, title, emptyLabel = 'No headings', className, }) {
28
32
  return (jsxs(PageTOC, { className: className, children: [header, title !== null && title !== void 0 ? title : jsx(PageTOCTitle, {}), jsx(PortableClerkTOCScrollArea, { children: jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }) }), footer] }));
29
33
  }
@@ -103,16 +107,16 @@ function PortableClerkTOCItems(_a) {
103
107
  if (toc.length === 0) {
104
108
  return (jsx("div", { className: "rounded-lg border bg-fd-card p-3 text-xs text-fd-muted-foreground", children: emptyLabel }));
105
109
  }
106
- return (jsxs("div", Object.assign({ ref: mergeRefs(containerRef, ref), className: cn('relative flex flex-col', className) }, props, { children: [jsx(ClerkOutline, { path: outlinePath, items: layout.items, activePath: activePath, activeAnchors: activeAnchors, activeEndpoint: activeEndpoint }), metas.map((meta, i) => (jsx(PortableClerkTOCItem, { item: meta.item, isActive: activeAnchors.includes(meta.item.url.slice(1)), resolvedContent: meta.resolvedContent, itemPadding: meta.itemPadding, contentRef: (node) => {
110
+ return (jsxs("div", Object.assign({ ref: mergeRefs(containerRef, ref), className: cn('relative flex flex-col', className) }, props, { children: [jsx(ClerkOutline, { path: outlinePath, items: layout.items, activePath: activePath, activeAnchors: activeAnchors, activeEndpoint: activeEndpoint }), metas.map((meta, i) => (jsx(PortableClerkTOCItem, { item: meta.item, isActive: activeAnchors.includes(meta.item.url.slice(1)), resolvedContent: meta.resolvedContent, fullTitle: meta.fullTitle, itemPadding: meta.itemPadding, contentRef: (node) => {
107
111
  contentRefs.current[i] = node;
108
112
  }, ref: (node) => {
109
113
  itemRefs.current[i] = node;
110
114
  } }, meta.item.url)))] })));
111
115
  }
112
- function PortableClerkTOCItem({ item, isActive, resolvedContent, itemPadding, contentRef, ref, }) {
113
- return (jsx(Primitive.TOCItem, { ref: ref, href: item.url, "data-clerk-item": "", style: {
116
+ function PortableClerkTOCItem({ item, isActive, resolvedContent, fullTitle, itemPadding, contentRef, ref, }) {
117
+ return (jsx(Primitive.TOCItem, { ref: ref, href: item.url, "data-clerk-item": "", title: fullTitle !== null && fullTitle !== void 0 ? fullTitle : undefined, style: {
114
118
  paddingInlineStart: itemPadding,
115
- }, className: cn('prose group relative py-1.5 text-sm transition-colors wrap-anywhere first:pt-0 last:pb-0 hover:text-fd-accent-foreground', isActive ? themeIconColor : 'text-fd-muted-foreground'), children: jsx("span", { ref: contentRef, className: "relative z-10", children: resolvedContent }) }));
119
+ }, className: cn('prose group relative py-1.5 text-sm transition-colors first:pt-0 last:pb-0 hover:text-fd-accent-foreground', isActive ? themeIconColor : 'text-fd-muted-foreground'), children: jsx("span", { ref: contentRef, className: "relative z-10 block overflow-hidden text-ellipsis whitespace-nowrap", children: resolvedContent }) }));
116
120
  }
117
121
  function ClerkOutline({ path, items, activePath, activeAnchors, activeEndpoint, }) {
118
122
  const activeSet = new Set(activeAnchors);
@@ -155,19 +159,33 @@ function getItemOffset(depth) {
155
159
  return lineOffset + badgeRadius + CLERK_TEXT_GAP_FROM_PATH;
156
160
  }
157
161
  function getLineOffset(depth) {
158
- return depth >= 3 ? 18 : 6;
162
+ const group = getDepthGroup(depth);
163
+ return CLERK_DEPTH_GROUP_LINE_OFFSETS[group];
159
164
  }
160
165
  function getVisualLinePosition(depth) {
161
166
  return getLineOffset(depth);
162
167
  }
168
+ function getDepthGroup(depth) {
169
+ if (depth <= 2)
170
+ return 0;
171
+ if (depth === 3)
172
+ return 1;
173
+ if (depth === 4)
174
+ return 2;
175
+ return 3;
176
+ }
163
177
  function resolveClerkItem(item) {
164
178
  const isH3 = item.depth === 3;
165
179
  const rawTitle = typeof item.title === 'string' ? item.title : '';
166
180
  const { isStep, displayStep, content } = getStepInfoFromTitle(rawTitle);
167
181
  let stepNumber = isH3 && isStep ? String(displayStep) : null;
168
182
  let resolvedContent = item.title;
183
+ let fullTitle = rawTitle || null;
169
184
  if (isH3 && isStep) {
170
185
  resolvedContent = content !== null && content !== void 0 ? content : item.title;
186
+ if (typeof content === 'string') {
187
+ fullTitle = content;
188
+ }
171
189
  }
172
190
  if (isH3 && !stepNumber) {
173
191
  const urlNum = getDigitsFromUrl(item.url);
@@ -178,18 +196,30 @@ function resolveClerkItem(item) {
178
196
  const match = rawTitle.match(/^(\d+(?:\.\d+)*\.?)\s+(.+)$/);
179
197
  if (match === null || match === void 0 ? void 0 : match[2]) {
180
198
  resolvedContent = match[2];
199
+ fullTitle = match[2];
181
200
  }
182
201
  }
183
202
  }
184
203
  }
204
+ if (typeof resolvedContent === 'string') {
205
+ fullTitle = resolvedContent;
206
+ resolvedContent = truncateClerkLabel(resolvedContent);
207
+ }
185
208
  return {
186
209
  item,
187
210
  resolvedContent,
211
+ fullTitle,
188
212
  stepNumber,
189
213
  itemPadding: getItemOffset(item.depth),
190
214
  lineOffset: getVisualLinePosition(item.depth),
191
215
  };
192
216
  }
217
+ function truncateClerkLabel(value) {
218
+ const normalized = value.trim();
219
+ if (normalized.length <= CLERK_MAX_LABEL_LENGTH)
220
+ return normalized;
221
+ return `${normalized.slice(0, CLERK_MAX_LABEL_LENGTH).trimEnd()}...`;
222
+ }
193
223
  function buildOutlinePath(items) {
194
224
  if (items.length === 0)
195
225
  return '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "14.4.1",
3
+ "version": "14.4.3",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -1,7 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import {
4
+ FINGERPRINT_DEBUG_PREFIX,
4
5
  FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY,
6
+ isDebugFingerprintId,
5
7
  isValidFingerprintId,
6
8
  } from './fingerprint-shared';
7
9
 
@@ -31,7 +33,7 @@ function setLocalStorageValue(key: string, value: string): void {
31
33
 
32
34
  export function getDebugFingerprintOverride(): string | null {
33
35
  const value = getLocalStorageValue(FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY);
34
- if (!value || !isValidFingerprintId(value)) {
36
+ if (!value || !isValidFingerprintId(value) || !isDebugFingerprintId(value)) {
35
37
  return null;
36
38
  }
37
39
 
@@ -39,9 +41,32 @@ export function getDebugFingerprintOverride(): string | null {
39
41
  }
40
42
 
41
43
  export function setDebugFingerprintOverride(fingerprintId: string): void {
42
- if (!isValidFingerprintId(fingerprintId)) {
44
+ if (!isValidFingerprintId(fingerprintId) || !isDebugFingerprintId(fingerprintId)) {
43
45
  throw new Error('Invalid fingerprint ID');
44
46
  }
45
47
 
46
48
  setLocalStorageValue(FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY, fingerprintId);
47
49
  }
50
+
51
+ export function buildDebugFingerprintId(): string {
52
+ const timestamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
53
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
54
+ return `${FINGERPRINT_DEBUG_PREFIX}${timestamp}_${randomSuffix}`;
55
+ }
56
+
57
+ export function getOrCreateDebugFingerprintOverride(): string {
58
+ const existing = getDebugFingerprintOverride();
59
+ if (existing) {
60
+ return existing;
61
+ }
62
+
63
+ const nextFingerprintId = buildDebugFingerprintId();
64
+ setDebugFingerprintOverride(nextFingerprintId);
65
+ return nextFingerprintId;
66
+ }
67
+
68
+ export function regenerateDebugFingerprintOverride(): string {
69
+ const nextFingerprintId = buildDebugFingerprintId();
70
+ setDebugFingerprintOverride(nextFingerprintId);
71
+ return nextFingerprintId;
72
+ }
@@ -9,8 +9,8 @@ import { useFingerprint } from './use-fingerprint';
9
9
  import { CopyableText } from '@windrun-huaiin/base-ui/ui';
10
10
  import { createFingerprintHeaders } from './fingerprint-client';
11
11
  import {
12
- getDebugFingerprintOverride,
13
- setDebugFingerprintOverride,
12
+ getOrCreateDebugFingerprintOverride,
13
+ regenerateDebugFingerprintOverride,
14
14
  } from './fingerprint-debug';
15
15
  import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared';
16
16
 
@@ -112,16 +112,12 @@ export function FingerprintStatus() {
112
112
  }, [xUser]);
113
113
 
114
114
  useEffect(() => {
115
- const debugFingerprintOverride = getDebugFingerprintOverride();
116
- if (debugFingerprintOverride) {
117
- setActiveDebugFingerprintId(debugFingerprintOverride);
115
+ if (panelMode !== 'test') {
118
116
  return;
119
117
  }
120
118
 
121
- const nextFingerprintId = buildDebugFingerprintId();
122
- setDebugFingerprintOverride(nextFingerprintId);
123
- setActiveDebugFingerprintId(nextFingerprintId);
124
- }, []);
119
+ setActiveDebugFingerprintId((current) => current ?? getOrCreateDebugFingerprintOverride());
120
+ }, [panelMode]);
125
121
 
126
122
  const creditBuckets = useMemo(() => {
127
123
  if (!xCredit) return [];
@@ -185,12 +181,13 @@ export function FingerprintStatus() {
185
181
  );
186
182
 
187
183
  const runContextParallelInitTest = async () => {
188
- const debugFingerprintId = activeDebugFingerprintId;
184
+ const debugFingerprintId = activeDebugFingerprintId ?? getOrCreateDebugFingerprintOverride();
189
185
  if (!debugFingerprintId) {
190
186
  setTestResult('Test fingerprint override is not ready yet.');
191
187
  return;
192
188
  }
193
189
 
190
+ setActiveDebugFingerprintId(debugFingerprintId);
194
191
  setIsRunningTest(true);
195
192
  setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
196
193
 
@@ -240,12 +237,13 @@ export function FingerprintStatus() {
240
237
  };
241
238
 
242
239
  const runRawParallelPostTest = async () => {
243
- const normalizedFingerprintId = activeDebugFingerprintId;
240
+ const normalizedFingerprintId = activeDebugFingerprintId ?? getOrCreateDebugFingerprintOverride();
244
241
  if (!normalizedFingerprintId) {
245
242
  setTestResult('Test fingerprint override is not ready yet.');
246
243
  return;
247
244
  }
248
245
 
246
+ setActiveDebugFingerprintId(normalizedFingerprintId);
249
247
  setIsRunningTest(true);
250
248
  setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
251
249
 
@@ -303,8 +301,7 @@ export function FingerprintStatus() {
303
301
  };
304
302
 
305
303
  const regenerateTestFingerprint = () => {
306
- const nextFingerprintId = buildDebugFingerprintId();
307
- setDebugFingerprintOverride(nextFingerprintId);
304
+ const nextFingerprintId = regenerateDebugFingerprintOverride();
308
305
  setActiveDebugFingerprintId(nextFingerprintId);
309
306
  setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
310
307
  };
@@ -680,12 +677,6 @@ function StatusTag({ value }: { value: string | undefined | null }) {
680
677
  );
681
678
  }
682
679
 
683
- function buildDebugFingerprintId() {
684
- const timestamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
685
- const randomSuffix = Math.random().toString(36).slice(2, 8);
686
- return `fp_test_dbg_${timestamp}_${randomSuffix}`;
687
- }
688
-
689
680
  function formatErrorMessage(error: unknown) {
690
681
  if (error instanceof Error) {
691
682
  return error.message;
@@ -12,6 +12,7 @@ export const FINGERPRINT_SOURCE_REFER = 'x-source-ref';
12
12
  export const FINGERPRINT_FIRST_TOUCH_STORAGE_KEY = '__x_first_touch';
13
13
  export const FINGERPRINT_FIRST_TOUCH_COOKIE_NAME = '__x_first_touch';
14
14
  export const FINGERPRINT_FIRST_TOUCH_HEADER = 'x-first-touch';
15
+ export const FINGERPRINT_DEBUG_PREFIX = 'fp_test_dbg_';
15
16
 
16
17
  /**
17
18
  * 验证fingerprint ID格式
@@ -23,7 +24,16 @@ export function isValidFingerprintId(fingerprintId: string): boolean {
23
24
  // - fp_ + FingerprintJS visitorId (变长字符串)
24
25
  // - fp_fallback_ + 时间戳_随机字符串 (客户端降级方案)
25
26
  // - fp_server_ + 时间戳_随机字符串 (服务端降级)
26
- return /^fp(_fallback|_server)?_[a-zA-Z0-9_]+$/.test(fingerprintId);
27
+ // - fp_test_dbg_ + 时间戳_随机字符串 (调试并发测试)
28
+ return /^fp(_fallback|_server|_test_dbg)?_[a-zA-Z0-9_]+$/.test(fingerprintId);
29
+ }
30
+
31
+ export function isDebugFingerprintId(fingerprintId: string | null | undefined): boolean {
32
+ if (!fingerprintId) {
33
+ return false;
34
+ }
35
+
36
+ return fingerprintId.startsWith(FINGERPRINT_DEBUG_PREFIX);
27
37
  }
28
38
 
29
39
  // 常量导出
@@ -35,4 +45,5 @@ export const FINGERPRINT_CONSTANTS = {
35
45
  FIRST_TOUCH_STORAGE_KEY: FINGERPRINT_FIRST_TOUCH_STORAGE_KEY,
36
46
  FIRST_TOUCH_COOKIE_NAME: FINGERPRINT_FIRST_TOUCH_COOKIE_NAME,
37
47
  FIRST_TOUCH_HEADER: FINGERPRINT_FIRST_TOUCH_HEADER,
48
+ DEBUG_PREFIX: FINGERPRINT_DEBUG_PREFIX,
38
49
  } as const;
@@ -4,7 +4,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
4
4
  import {
5
5
  createFingerprintHeaders,
6
6
  getOrCreateFirstTouchData,
7
- getOrGenerateFingerprintId
7
+ getOrGenerateFingerprintId,
8
+ setFingerprintId,
8
9
  } from './fingerprint-client';
9
10
  import type {
10
11
  FingerprintConfig,
@@ -13,7 +14,7 @@ import type {
13
14
  XSubscription,
14
15
  XUser
15
16
  } from './types';
16
- import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared'
17
+ import { FINGERPRINT_SOURCE_REFER, isDebugFingerprintId, isValidFingerprintId } from './fingerprint-shared'
17
18
 
18
19
  /**
19
20
  * Hook for managing fingerprint ID and anonymous user data
@@ -121,8 +122,15 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
121
122
  setXSubscription(data.xSubscription || null);
122
123
  setIsInitialized(true);
123
124
 
124
- if (data.xUser?.fingerprintId && data.xUser.fingerprintId !== fingerprintId) {
125
- setFingerprintIdState(data.xUser.fingerprintId);
125
+ const canonicalFingerprintId = data.xUser?.fingerprintId;
126
+ if (
127
+ canonicalFingerprintId &&
128
+ isValidFingerprintId(canonicalFingerprintId) &&
129
+ !isDebugFingerprintId(canonicalFingerprintId) &&
130
+ canonicalFingerprintId !== fingerprintId
131
+ ) {
132
+ setFingerprintIdState(canonicalFingerprintId);
133
+ setFingerprintId(canonicalFingerprintId);
126
134
  }
127
135
  } else {
128
136
  throw new Error(data.error || 'Unknown error occurred');
@@ -40,6 +40,7 @@ type PortableClerkTOCProps = {
40
40
  type ClerkItemMeta = {
41
41
  item: TOCItemType;
42
42
  resolvedContent: ReactNode;
43
+ fullTitle: string | null;
43
44
  stepNumber: string | null;
44
45
  itemPadding: number;
45
46
  lineOffset: number;
@@ -70,6 +71,10 @@ const CLERK_ACTIVE_ANIMATION_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)';
70
71
  const CLERK_TEXT_GAP_FROM_PATH = 12;
71
72
  // Radius of numbered step badges rendered on top of the path centerline.
72
73
  const CLERK_STEP_BADGE_RADIUS = 7;
74
+ // Visual line offsets by grouped heading depth: 1/2, 3, 4, >4.
75
+ const CLERK_DEPTH_GROUP_LINE_OFFSETS = [6, 18, 30, 42] as const;
76
+ // Max number of characters rendered for a TOC label before trimming with ellipsis.
77
+ const CLERK_MAX_LABEL_LENGTH = 44;
73
78
 
74
79
  export function PortableClerkTOC({
75
80
  toc,
@@ -250,6 +255,7 @@ export function PortableClerkTOCItems({
250
255
  item={meta.item}
251
256
  isActive={activeAnchors.includes(meta.item.url.slice(1))}
252
257
  resolvedContent={meta.resolvedContent}
258
+ fullTitle={meta.fullTitle}
253
259
  itemPadding={meta.itemPadding}
254
260
  contentRef={(node: HTMLSpanElement | null) => {
255
261
  contentRefs.current[i] = node;
@@ -267,6 +273,7 @@ function PortableClerkTOCItem({
267
273
  item,
268
274
  isActive,
269
275
  resolvedContent,
276
+ fullTitle,
270
277
  itemPadding,
271
278
  contentRef,
272
279
  ref,
@@ -274,6 +281,7 @@ function PortableClerkTOCItem({
274
281
  item: TOCItemType;
275
282
  isActive: boolean;
276
283
  resolvedContent: ReactNode;
284
+ fullTitle: string | null;
277
285
  itemPadding: number;
278
286
  contentRef?: ((node: HTMLSpanElement | null) => void) | null;
279
287
  ref?: ((node: HTMLAnchorElement | null) => void) | null;
@@ -283,15 +291,19 @@ function PortableClerkTOCItem({
283
291
  ref={ref}
284
292
  href={item.url}
285
293
  data-clerk-item=""
294
+ title={fullTitle ?? undefined}
286
295
  style={{
287
296
  paddingInlineStart: itemPadding,
288
297
  }}
289
298
  className={cn(
290
- 'prose group relative py-1.5 text-sm transition-colors wrap-anywhere first:pt-0 last:pb-0 hover:text-fd-accent-foreground',
299
+ 'prose group relative py-1.5 text-sm transition-colors first:pt-0 last:pb-0 hover:text-fd-accent-foreground',
291
300
  isActive ? themeIconColor : 'text-fd-muted-foreground',
292
301
  )}
293
302
  >
294
- <span ref={contentRef} className="relative z-10">
303
+ <span
304
+ ref={contentRef}
305
+ className="relative z-10 block overflow-hidden text-ellipsis whitespace-nowrap"
306
+ >
295
307
  {resolvedContent}
296
308
  </span>
297
309
  </Primitive.TOCItem>
@@ -453,22 +465,34 @@ function getItemOffset(depth: number): number {
453
465
  }
454
466
 
455
467
  function getLineOffset(depth: number): number {
456
- return depth >= 3 ? 18 : 6;
468
+ const group = getDepthGroup(depth);
469
+ return CLERK_DEPTH_GROUP_LINE_OFFSETS[group];
457
470
  }
458
471
 
459
472
  function getVisualLinePosition(depth: number): number {
460
473
  return getLineOffset(depth);
461
474
  }
462
475
 
476
+ function getDepthGroup(depth: number): number {
477
+ if (depth <= 2) return 0;
478
+ if (depth === 3) return 1;
479
+ if (depth === 4) return 2;
480
+ return 3;
481
+ }
482
+
463
483
  function resolveClerkItem(item: TOCItemType): ClerkItemMeta {
464
484
  const isH3 = item.depth === 3;
465
485
  const rawTitle = typeof item.title === 'string' ? item.title : '';
466
486
  const { isStep, displayStep, content } = getStepInfoFromTitle(rawTitle);
467
487
  let stepNumber: string | null = isH3 && isStep ? String(displayStep) : null;
468
488
  let resolvedContent: ReactNode = item.title;
489
+ let fullTitle: string | null = rawTitle || null;
469
490
 
470
491
  if (isH3 && isStep) {
471
492
  resolvedContent = content ?? item.title;
493
+ if (typeof content === 'string') {
494
+ fullTitle = content;
495
+ }
472
496
  }
473
497
 
474
498
  if (isH3 && !stepNumber) {
@@ -480,20 +504,34 @@ function resolveClerkItem(item: TOCItemType): ClerkItemMeta {
480
504
  const match = rawTitle.match(/^(\d+(?:\.\d+)*\.?)\s+(.+)$/);
481
505
  if (match?.[2]) {
482
506
  resolvedContent = match[2];
507
+ fullTitle = match[2];
483
508
  }
484
509
  }
485
510
  }
486
511
  }
487
512
 
513
+ if (typeof resolvedContent === 'string') {
514
+ fullTitle = resolvedContent;
515
+ resolvedContent = truncateClerkLabel(resolvedContent);
516
+ }
517
+
488
518
  return {
489
519
  item,
490
520
  resolvedContent,
521
+ fullTitle,
491
522
  stepNumber,
492
523
  itemPadding: getItemOffset(item.depth),
493
524
  lineOffset: getVisualLinePosition(item.depth),
494
525
  };
495
526
  }
496
527
 
528
+ function truncateClerkLabel(value: string): string {
529
+ const normalized = value.trim();
530
+ if (normalized.length <= CLERK_MAX_LABEL_LENGTH) return normalized;
531
+
532
+ return `${normalized.slice(0, CLERK_MAX_LABEL_LENGTH).trimEnd()}...`;
533
+ }
534
+
497
535
  function buildOutlinePath(items: ClerkItemMeasure[]): string {
498
536
  if (items.length === 0) return '';
499
537
 
@@ -3,6 +3,19 @@
3
3
  @apply top-20!;
4
4
  }
5
5
 
6
+ @media (min-width: 1280px) {
7
+ /* 给桌面端固定 TOC 留一点底部呼吸感,避免视觉上直接压到 footer */
8
+ #nd-toc {
9
+ bottom: 1.25rem !important;
10
+ }
11
+
12
+ /* 文档主区域补一小段底部缓冲,减少 footer 刚进入视口就与 TOC 相撞 */
13
+ #nd-page {
14
+ padding-bottom: 5rem !important;
15
+ min-height: calc(100dvh - 5rem) !important;
16
+ }
17
+ }
18
+
6
19
  /* 移动端文章目录下拉框的边距,建议移动端不要目录导航 96px */
7
20
  #nd-tocnav {
8
21
  @apply top-24!;
@@ -35,7 +48,7 @@ button[aria-label="Open Search"][data-search=""] {
35
48
 
36
49
  /* Custome Fuma Steps */
37
50
  .fd-step::before {
38
- @apply size-5 -start-2.5 rounded-full;
51
+ @apply size-5 -inset-s-2.5 rounded-full;
39
52
  background-color: #000;
40
53
  color: #fff;
41
54
  font-size: 0.75rem;
@@ -128,4 +141,4 @@ button[aria-label="Open Search"][data-search=""] {
128
141
  [data-rmiz-modal-img] {
129
142
  transition-duration: 0.01ms !important;
130
143
  }
131
- }
144
+ }