@wenbin_wb/dsh-bridge 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/client.js CHANGED
@@ -68,6 +68,25 @@ var BRIDGE_ENDPOINTS = {
68
68
  };
69
69
 
70
70
  // client/index.js
71
+ if (typeof window !== "undefined") {
72
+ if (!window.crypto) {
73
+ window.crypto = {};
74
+ }
75
+ if (!window.crypto.randomUUID) {
76
+ window.crypto.randomUUID = function() {
77
+ if (typeof window.crypto.getRandomValues === "function") {
78
+ return ("10000000-1000-4000-8000" + -1e11).replace(/[018]/g, function(c) {
79
+ return (c ^ window.crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16);
80
+ });
81
+ }
82
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
83
+ var r = Math.random() * 16 | 0;
84
+ var v = c === "x" ? r : r & 3 | 8;
85
+ return v.toString(16);
86
+ });
87
+ };
88
+ }
89
+ }
71
90
  var GITHUB_URL = "https://github.com/wenbin-wb/dsh-bridge";
72
91
  var RELEASES_URL = "https://github.com/wenbin-wb/dsh-bridge/releases";
73
92
  var ISSUES_URL = "https://github.com/wenbin-wb/dsh-bridge/issues/new";
@@ -346,13 +365,17 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
346
365
  }
347
366
  }, [initUrl, initToken]);
348
367
  const dirty = serverUrl !== (initUrl ?? "") || accessToken !== (initToken ?? "");
368
+ const [saveErr, setSaveErr] = React.useState(null);
349
369
  const handleSave = React.useCallback(async () => {
350
370
  setSaving(true);
351
371
  setSaveSuccess(false);
372
+ setSaveErr(null);
352
373
  try {
353
374
  await onSave(serverUrl, accessToken);
354
375
  setSaveSuccess(true);
355
376
  setTimeout(() => setSaveSuccess(false), 2500);
377
+ } catch (e) {
378
+ setSaveErr(e.message || "\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25");
356
379
  } finally {
357
380
  setSaving(false);
358
381
  }
@@ -360,10 +383,12 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
360
383
  const handleUrlChange = React.useCallback((e) => {
361
384
  setServerUrl(e.target.value);
362
385
  setSaveSuccess(false);
386
+ setSaveErr(null);
363
387
  }, []);
364
388
  const handleTokenChange = React.useCallback((e) => {
365
389
  setAccessToken(e.target.value);
366
390
  setSaveSuccess(false);
391
+ setSaveErr(null);
367
392
  }, []);
368
393
  return React.createElement(
369
394
  "div",
@@ -393,6 +418,7 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
393
418
  },
394
419
  disabled: saving
395
420
  }),
421
+ saveErr && React.createElement("div", { style: s.err }, `\u274C ${saveErr}`),
396
422
  React.createElement(
397
423
  "div",
398
424
  { style: { ...s.muted, fontSize: 11 } },
@@ -477,43 +503,55 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
477
503
  }
478
504
  }, [auth]);
479
505
  const handleToggleEnabled = async () => {
506
+ const prev = enabled;
480
507
  const next = !enabled;
481
508
  setEnabled(next);
482
509
  try {
483
- await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { enabled: next });
510
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { enabled: next });
511
+ if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
484
512
  setTopMsg({ ok: true, text: next ? "\u2713 \u8BBF\u95EE\u5B89\u5168\u8BA4\u8BC1\u5DF2\u5F00\u542F\uFF08\u73B0\u6709\u767B\u5F55\u6001\u5DF2\u5237\u65B0\uFF09" : "\u2713 \u8BBF\u95EE\u5B89\u5168\u8BA4\u8BC1\u5DF2\u5173\u95ED" });
485
513
  onUpdate?.();
486
514
  } catch (e) {
515
+ setEnabled(prev);
487
516
  setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
488
517
  }
489
518
  };
490
519
  const handleChangeMode = async (m) => {
520
+ const prev = mode;
491
521
  setMode(m);
492
522
  try {
493
- await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { mode: m });
523
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { mode: m });
524
+ if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
494
525
  setTopMsg({ ok: true, text: "\u2713 \u5916\u90E8\u9A8C\u8BC1\u6A21\u5F0F\u5DF2\u5207\u6362\uFF0C\u5DF2\u5237\u65B0\u5168\u57DF\u767B\u5F55\u6001" });
495
526
  onUpdate?.();
496
527
  } catch (e) {
528
+ setMode(prev);
497
529
  setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
498
530
  }
499
531
  };
500
532
  const handleChangeScope = async (sc) => {
533
+ const prev = scope;
501
534
  setScope(sc);
502
535
  try {
503
- await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { scope: sc });
536
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { scope: sc });
537
+ if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
504
538
  setTopMsg({ ok: true, text: "\u2713 \u9632\u62A4\u751F\u6548\u8303\u56F4\u5DF2\u66F4\u65B0" });
505
539
  onUpdate?.();
506
540
  } catch (e) {
541
+ setScope(prev);
507
542
  setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
508
543
  }
509
544
  };
510
545
  const handleChangeAdminPolicy = async (pol) => {
546
+ const prev = adminPolicy;
511
547
  setAdminPolicy(pol);
512
548
  try {
513
- await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminPolicy: pol });
549
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminPolicy: pol });
550
+ if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
514
551
  setTopMsg({ ok: true, text: "\u2713 \u8FDC\u7A0B\u7BA1\u7406\u9632\u7BE1\u6539\u7B56\u7565\u5DF2\u66F4\u65B0" });
515
552
  onUpdate?.();
516
553
  } catch (e) {
554
+ setAdminPolicy(prev);
517
555
  setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
518
556
  }
519
557
  };
@@ -560,7 +598,8 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
560
598
  const handleRegenerateToken = async () => {
561
599
  if (!confirm("\u91CD\u7F6E\u540E\uFF0C\u4E4B\u524D\u5305\u542B\u65E7 Token \u7684\u4E8C\u7EF4\u7801\u548C\u5206\u4EAB\u94FE\u63A5\u5C06\u7ACB\u5373\u5931\u6548\u3002\u662F\u5426\u786E\u8BA4\u91CD\u7F6E\uFF1F")) return;
562
600
  try {
563
- await rpcCall(BRIDGE_ENDPOINTS.authRegenerateToken, {});
601
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authRegenerateToken, {});
602
+ if (!res?.ok) throw new Error(res?.error?.message || "\u91CD\u7F6E\u5931\u8D25");
564
603
  setTopMsg({ ok: true, text: "\u2713 \u5B89\u5168 Token \u5DF2\u91CD\u7F6E\uFF0C\u4E8C\u7EF4\u7801\u4E0E\u4E13\u5C5E\u94FE\u63A5\u5DF2\u5237\u65B0" });
565
604
  onUpdate?.();
566
605
  } catch (e) {
@@ -975,15 +1014,23 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
975
1014
  const connected2 = platform?.status === "connected" || platform?.status === "starting" || platform?.status === "reconnecting";
976
1015
  onStatusChange?.(connected2);
977
1016
  }, [platform?.status, onStatusChange]);
1017
+ const loadInFlightRef = React.useRef(false);
1018
+ const seqRef = React.useRef(0);
978
1019
  const load = React.useCallback(async (quiet = false) => {
1020
+ if (loadInFlightRef.current) return;
1021
+ loadInFlightRef.current = true;
1022
+ const currentSeq = ++seqRef.current;
979
1023
  try {
980
1024
  const r = await rpcCall(BRIDGE_ENDPOINTS.listPlatforms, {});
1025
+ if (currentSeq !== seqRef.current) return;
981
1026
  if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
982
1027
  const allPlatforms = r.value ?? {};
983
1028
  setPlatform(allPlatforms[platformId] ?? null);
984
1029
  if (!quiet) setErr(null);
985
1030
  } catch (e) {
986
- if (!quiet) setErr(e.message);
1031
+ if (currentSeq === seqRef.current && !quiet) setErr(e.message);
1032
+ } finally {
1033
+ loadInFlightRef.current = false;
987
1034
  }
988
1035
  }, [rpcCall, platformId]);
989
1036
  React.useEffect(() => {
@@ -1014,9 +1061,20 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1014
1061
  const id = newId.trim();
1015
1062
  if (!id) return;
1016
1063
  const list = [...platform?.allowFrom ?? [], id];
1017
- await act(BRIDGE_ENDPOINTS.platformSetAllowFrom, { allowFrom: list });
1018
- setNewId("");
1019
- }, [act, newId, platform?.allowFrom]);
1064
+ setBusy(true);
1065
+ try {
1066
+ const r = await rpcCall(BRIDGE_ENDPOINTS.platformSetAllowFrom, { platformId, allowFrom: list });
1067
+ if (!r?.ok) throw new Error(r?.error?.message ?? "\u6DFB\u52A0\u767D\u540D\u5355\u5931\u8D25");
1068
+ setPlatform(r.value);
1069
+ setNewId("");
1070
+ setErr(null);
1071
+ await load(true);
1072
+ } catch (e) {
1073
+ setErr(e.message);
1074
+ } finally {
1075
+ setBusy(false);
1076
+ }
1077
+ }, [rpcCall, platformId, newId, platform?.allowFrom, load]);
1020
1078
  const removeAllow = React.useCallback(async (id) => {
1021
1079
  const list = (platform?.allowFrom ?? []).filter((x) => x !== id);
1022
1080
  await act(BRIDGE_ENDPOINTS.platformSetAllowFrom, { allowFrom: list });
@@ -1062,7 +1120,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1062
1120
  React.createElement("div", { style: { ...s.muted, marginTop: 6 } }, "\u52A0\u8F7D\u4E2D\u2026")
1063
1121
  );
1064
1122
  }
1065
- const connected = platform?.status === "connected" || platform?.status === "starting";
1123
+ const connected = platform?.status === "connected" || platform?.status === "starting" || platform?.status === "reconnecting";
1066
1124
  const login = platform?.login ?? {};
1067
1125
  const showQr = login.phase === "qr" || login.phase === "scaned";
1068
1126
  const statusLabel = platform?.status === "connected" ? "\u5DF2\u8FDE\u63A5" : platform?.status === "starting" ? "\u8FDE\u63A5\u4E2D\u2026" : platform?.status === "reconnecting" ? "\u91CD\u8FDE\u4E2D\u2026" : platform?.status === "paused" ? "\u6682\u505C\uFF08\u4F1A\u8BDD\u8FC7\u671F\uFF09" : platform?.status === "error" ? "\u9519\u8BEF" : "\u672A\u8FDE\u63A5";
@@ -1785,11 +1843,65 @@ function BridgePanel({ rpcCall }) {
1785
1843
  const [platforms, setPlatforms] = React.useState(null);
1786
1844
  const [selectedPlatform, setSelectedPlatform] = React.useState("wechat");
1787
1845
  const isLocalhost = typeof window === "undefined" || (!window.location.hostname || window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost" || window.location.hostname === "::1" || window.location.hostname === "" || window.location.protocol === "file:" || window.location.protocol === "vscode-webview:" || window.location.protocol === "app:" || window.location.hostname.endsWith(".local"));
1846
+ const [adminToken, setAdminToken] = React.useState("");
1788
1847
  const [adminUnlocked, setAdminUnlocked] = React.useState(false);
1789
1848
  const [unlockPassword, setUnlockPassword] = React.useState("");
1790
1849
  const [unlockErr, setUnlockErr] = React.useState(null);
1791
1850
  const [unlocking, setUnlocking] = React.useState(false);
1792
1851
  const [showForgotGuide, setShowForgotGuide] = React.useState(false);
1852
+ const [showUnlockModal, setShowUnlockModal] = React.useState(false);
1853
+ const fetchLoopbackToken = React.useCallback(async () => {
1854
+ if (!isLocalhost) return null;
1855
+ const currentPort = typeof window !== "undefined" ? window.location.port || (window.location.protocol === "https:" ? "443" : "80") : "3082";
1856
+ const proxyPort = status?.proxy?.port || 3082;
1857
+ const candidateUrls = [
1858
+ "/__dsh_bridge__/loopback-token",
1859
+ `http://127.0.0.1:${proxyPort}/__dsh_bridge__/loopback-token`,
1860
+ `http://localhost:${proxyPort}/__dsh_bridge__/loopback-token`,
1861
+ "http://127.0.0.1:3082/__dsh_bridge__/loopback-token"
1862
+ ];
1863
+ const uniqueUrls = [...new Set(candidateUrls)];
1864
+ for (const url of uniqueUrls) {
1865
+ try {
1866
+ const res = await fetch(url, { method: "POST" });
1867
+ if (res.ok) {
1868
+ const data = await res.json();
1869
+ if (data?.ok && data.adminToken) {
1870
+ setAdminToken(data.adminToken);
1871
+ setAdminUnlocked(true);
1872
+ return data.adminToken;
1873
+ }
1874
+ }
1875
+ } catch {
1876
+ }
1877
+ }
1878
+ return null;
1879
+ }, [isLocalhost, status?.proxy?.port]);
1880
+ React.useEffect(() => {
1881
+ if (isLocalhost && !adminUnlocked) {
1882
+ fetchLoopbackToken();
1883
+ }
1884
+ }, [isLocalhost, adminUnlocked, fetchLoopbackToken]);
1885
+ const authRpcCall = React.useCallback(async (endpoint, payload = {}, signal) => {
1886
+ let token = adminToken;
1887
+ if (isLocalhost && !token) {
1888
+ token = await fetchLoopbackToken();
1889
+ }
1890
+ const enriched = {
1891
+ ...payload,
1892
+ ...token ? { adminToken: token } : {},
1893
+ ...isLocalhost ? { isLocalhost: true } : {}
1894
+ };
1895
+ const res = await rpcCall(endpoint, enriched, signal);
1896
+ if (res?.ok === false) {
1897
+ const msg = res?.error?.message || "";
1898
+ if (msg.includes("\u7BA1\u7406\u5458\u6743\u9650") || msg.includes("\u7BA1\u7406\u5BC6\u7801\u89E3\u9501")) {
1899
+ setUnlockErr(msg);
1900
+ setShowUnlockModal(true);
1901
+ }
1902
+ }
1903
+ return res;
1904
+ }, [rpcCall, adminToken, isLocalhost, fetchLoopbackToken]);
1793
1905
  const handleUnlockAdmin = React.useCallback(async (e) => {
1794
1906
  e?.preventDefault?.();
1795
1907
  setUnlocking(true);
@@ -1797,8 +1909,11 @@ function BridgePanel({ rpcCall }) {
1797
1909
  try {
1798
1910
  const res = await rpcCall(BRIDGE_ENDPOINTS.authAdminUnlock, { password: unlockPassword });
1799
1911
  if (res?.ok) {
1912
+ setAdminToken(res.value?.adminToken || "");
1800
1913
  setAdminUnlocked(true);
1801
1914
  setUnlockPassword("");
1915
+ setShowUnlockModal(false);
1916
+ setErr(null);
1802
1917
  } else {
1803
1918
  setUnlockErr(res?.error?.message || "\u7BA1\u7406\u5458\u5BC6\u7801\u9519\u8BEF");
1804
1919
  }
@@ -1808,25 +1923,50 @@ function BridgePanel({ rpcCall }) {
1808
1923
  setUnlocking(false);
1809
1924
  }
1810
1925
  }, [rpcCall, unlockPassword]);
1926
+ const handleLockAdmin = React.useCallback(async () => {
1927
+ try {
1928
+ if (adminToken) {
1929
+ await rpcCall(BRIDGE_ENDPOINTS.authAdminLock, { adminToken });
1930
+ }
1931
+ } catch {
1932
+ }
1933
+ setAdminToken("");
1934
+ setAdminUnlocked(false);
1935
+ }, [rpcCall, adminToken]);
1936
+ const loadInFlightRef = React.useRef(false);
1937
+ const loadSeqRef = React.useRef(0);
1811
1938
  const load = React.useCallback(async (quiet = false) => {
1939
+ if (loadInFlightRef.current) return;
1940
+ loadInFlightRef.current = true;
1941
+ const currentSeq = ++loadSeqRef.current;
1812
1942
  try {
1813
- const r = await rpcCall(BRIDGE_ENDPOINTS.getStatus, {});
1943
+ const r = await authRpcCall(BRIDGE_ENDPOINTS.getStatus, {});
1944
+ if (currentSeq !== loadSeqRef.current) return;
1814
1945
  if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
1815
1946
  setStatus(r.value);
1816
1947
  if (!quiet) setErr(null);
1817
1948
  } catch (e) {
1818
- setErr(e.message);
1949
+ if (currentSeq === loadSeqRef.current) setErr(e.message);
1950
+ } finally {
1951
+ loadInFlightRef.current = false;
1819
1952
  }
1820
- }, [rpcCall]);
1953
+ }, [authRpcCall]);
1954
+ const pollPlatformsSeqRef = React.useRef(0);
1821
1955
  React.useEffect(() => {
1822
1956
  let alive = true;
1957
+ let inFlight = false;
1823
1958
  const poll = async () => {
1959
+ if (inFlight || !alive) return;
1960
+ inFlight = true;
1961
+ const currentSeq = ++pollPlatformsSeqRef.current;
1824
1962
  try {
1825
- const r = await rpcCall(BRIDGE_ENDPOINTS.listPlatforms, {});
1826
- if (alive && r?.ok) {
1963
+ const r = await authRpcCall(BRIDGE_ENDPOINTS.listPlatforms, {});
1964
+ if (alive && currentSeq === pollPlatformsSeqRef.current && r?.ok) {
1827
1965
  setPlatforms(r.value ?? {});
1828
1966
  }
1829
1967
  } catch {
1968
+ } finally {
1969
+ inFlight = false;
1830
1970
  }
1831
1971
  };
1832
1972
  poll();
@@ -1835,7 +1975,7 @@ function BridgePanel({ rpcCall }) {
1835
1975
  alive = false;
1836
1976
  clearInterval(t);
1837
1977
  };
1838
- }, [rpcCall]);
1978
+ }, [authRpcCall]);
1839
1979
  React.useEffect(() => {
1840
1980
  load();
1841
1981
  const t = setInterval(() => load(true), 3e3);
@@ -1843,14 +1983,14 @@ function BridgePanel({ rpcCall }) {
1843
1983
  }, [load]);
1844
1984
  const act = React.useCallback(async (endpoint, payload) => {
1845
1985
  try {
1846
- const r = await rpcCall(endpoint, payload ?? {});
1986
+ const r = await authRpcCall(endpoint, payload ?? {});
1847
1987
  if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
1848
1988
  setStatus(r.value);
1849
1989
  setErr(null);
1850
1990
  } catch (e) {
1851
1991
  setErr(e.message);
1852
1992
  }
1853
- }, [rpcCall]);
1993
+ }, [authRpcCall]);
1854
1994
  const onStartCloudflared = React.useCallback(() => act(BRIDGE_ENDPOINTS.startCloudflared), [act]);
1855
1995
  const onStopCloudflared = React.useCallback(() => act(BRIDGE_ENDPOINTS.stopCloudflared), [act]);
1856
1996
  const onResetCloudflared = React.useCallback(
@@ -1935,7 +2075,7 @@ function BridgePanel({ rpcCall }) {
1935
2075
  } else if (activeTab === "security") {
1936
2076
  tabContent = React.createElement(AccessAuthCard, {
1937
2077
  auth: status?.auth,
1938
- rpcCall,
2078
+ rpcCall: authRpcCall,
1939
2079
  onUpdate: () => load(true)
1940
2080
  });
1941
2081
  } else if (activeTab === "im") {
@@ -1998,12 +2138,13 @@ function BridgePanel({ rpcCall }) {
1998
2138
  );
1999
2139
  })
2000
2140
  ),
2001
- // 显示选中的平台卡片
2141
+ // 显示选中的平台卡片(带有 key 保证切换时重置表单状态)
2002
2142
  selectedPlatform && platforms?.[selectedPlatform] && React.createElement(PlatformCard, {
2143
+ key: selectedPlatform,
2003
2144
  platformId: selectedPlatform,
2004
2145
  platformName: IM_PLATFORMS.find((p) => p.id === selectedPlatform)?.label ?? selectedPlatform,
2005
2146
  platformDesc: IM_PLATFORMS.find((p) => p.id === selectedPlatform)?.desc ?? "",
2006
- rpcCall,
2147
+ rpcCall: authRpcCall,
2007
2148
  onStatusChange: () => {
2008
2149
  }
2009
2150
  // 状态变化已由 listPlatforms 轮询处理,不需要回调
@@ -2129,12 +2270,38 @@ function BridgePanel({ rpcCall }) {
2129
2270
  )
2130
2271
  );
2131
2272
  }
2273
+ const isInterceptionErr = err && (err.includes("\u7BA1\u7406\u5458\u6743\u9650") || err.includes("\u7BA1\u7406\u5BC6\u7801\u89E3\u9501"));
2132
2274
  return React.createElement(
2133
2275
  "div",
2134
- { style: { maxWidth: 620 } },
2135
- err && React.createElement("div", {
2136
- style: { ...s.card, background: "var(--dsw-alias-state-error-bg,#fef2f2)", color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 13, marginBottom: 16 }
2137
- }, err),
2276
+ { style: { maxWidth: 620, position: "relative" } },
2277
+ // 错误横幅(如果是权限拦截,直接提供醒目的输入密码解锁按钮)
2278
+ err && React.createElement(
2279
+ "div",
2280
+ {
2281
+ style: {
2282
+ ...s.card,
2283
+ background: "var(--dsw-alias-state-error-bg,#fef2f2)",
2284
+ color: "var(--dsw-alias-state-error-primary,#dc2626)",
2285
+ fontSize: 13,
2286
+ marginBottom: 16,
2287
+ display: "flex",
2288
+ alignItems: "center",
2289
+ justifyContent: "space-between",
2290
+ flexWrap: "wrap",
2291
+ gap: 10
2292
+ }
2293
+ },
2294
+ React.createElement("span", { style: { flex: "1 1 auto" } }, err),
2295
+ isInterceptionErr && React.createElement("button", {
2296
+ type: "button",
2297
+ style: { ...s.btnPri, background: "#dc2626", color: "#ffffff", height: 26, fontSize: 12, padding: "0 10px", flexShrink: 0 },
2298
+ onClick: () => {
2299
+ setUnlockErr(err);
2300
+ setShowUnlockModal(true);
2301
+ }
2302
+ }, "\u{1F511} \u7ACB\u5373\u8F93\u5165\u7BA1\u7406\u5BC6\u7801\u89E3\u9501")
2303
+ ),
2304
+ // 管理员解锁状态提示条
2138
2305
  !isLocalhost && adminUnlocked && React.createElement(
2139
2306
  "div",
2140
2307
  {
@@ -2154,12 +2321,125 @@ function BridgePanel({ rpcCall }) {
2154
2321
  React.createElement("span", null, "\u{1F513} \u7BA1\u7406\u5458\u6743\u9650\u5DF2\u89E3\u9501\uFF08\u5F53\u524D\u4E34\u65F6\u4F1A\u8BDD\u6709\u6548\uFF09"),
2155
2322
  React.createElement("button", {
2156
2323
  style: { ...s.btnGhost, height: 24, fontSize: 11, padding: "0 8px" },
2157
- onClick: () => setAdminUnlocked(false)
2324
+ onClick: handleLockAdmin
2158
2325
  }, "\u{1F512} \u91CD\u65B0\u9501\u5B9A\u540E\u53F0")
2159
2326
  ),
2160
- React.createElement(VersionBanner, { rpcCall }),
2327
+ // 未解锁时的顶部引导条
2328
+ !isLocalhost && !adminUnlocked && auth?.enabled && policy !== "open" && React.createElement(
2329
+ "div",
2330
+ {
2331
+ style: {
2332
+ display: "flex",
2333
+ alignItems: "center",
2334
+ justifyContent: "space-between",
2335
+ padding: "8px 14px",
2336
+ background: "var(--dsw-alias-state-warn-bg,#fffbeb)",
2337
+ border: "1px solid var(--dsw-alias-state-warn-border,#fde68a)",
2338
+ borderRadius: 8,
2339
+ marginBottom: 14,
2340
+ fontSize: 12,
2341
+ color: "var(--dsw-alias-state-warn-primary,#92400e)"
2342
+ }
2343
+ },
2344
+ React.createElement("span", null, "\u{1F512} \u540E\u53F0\u7BA1\u7406\u6743\u9650\u672A\u89E3\u9501\uFF08\u4FEE\u6539\u654F\u611F\u914D\u7F6E\u9700\u5148\u89E3\u9501\uFF09"),
2345
+ React.createElement("button", {
2346
+ type: "button",
2347
+ style: { ...s.btnPri, height: 24, fontSize: 11, padding: "0 10px", background: "#d97706" },
2348
+ onClick: () => setShowUnlockModal(true)
2349
+ }, "\u{1F511} \u89E3\u9501\u7BA1\u7406\u6743\u9650")
2350
+ ),
2351
+ React.createElement(VersionBanner, { rpcCall: authRpcCall }),
2161
2352
  React.createElement(TabBar, { active: activeTab, onChange: setActiveTab, dots }),
2162
- tabContent
2353
+ tabContent,
2354
+ // 全局交互式解锁弹窗 Modal
2355
+ showUnlockModal && React.createElement(
2356
+ "div",
2357
+ {
2358
+ style: {
2359
+ position: "fixed",
2360
+ inset: 0,
2361
+ background: "rgba(0,0,0,0.5)",
2362
+ zIndex: 99999,
2363
+ display: "flex",
2364
+ alignItems: "center",
2365
+ justifyContent: "center",
2366
+ padding: 16
2367
+ },
2368
+ onClick: (e) => {
2369
+ if (e.target === e.currentTarget) setShowUnlockModal(false);
2370
+ }
2371
+ },
2372
+ React.createElement(
2373
+ "div",
2374
+ {
2375
+ style: {
2376
+ background: "var(--dsw-alias-bg-layer-1,#ffffff)",
2377
+ borderRadius: 14,
2378
+ padding: "24px 24px",
2379
+ maxWidth: 420,
2380
+ width: "100%",
2381
+ boxShadow: "0 20px 25px -5px rgba(0,0,0,0.2)",
2382
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)"
2383
+ }
2384
+ },
2385
+ React.createElement(
2386
+ "div",
2387
+ { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 } },
2388
+ React.createElement(
2389
+ "div",
2390
+ { style: { fontSize: 16, fontWeight: 600, color: "var(--dsw-alias-label-primary,currentColor)", display: "flex", alignItems: "center", gap: 8 } },
2391
+ "\u{1F512} \u89E3\u9501\u540E\u53F0\u7BA1\u7406\u6743\u9650"
2392
+ ),
2393
+ React.createElement("button", {
2394
+ type: "button",
2395
+ style: { border: "none", background: "none", cursor: "pointer", fontSize: 18, color: "var(--dsw-alias-label-tertiary,#9ca3af)", padding: 0 },
2396
+ onClick: () => setShowUnlockModal(false)
2397
+ }, "\u2715")
2398
+ ),
2399
+ React.createElement(
2400
+ "div",
2401
+ { style: { fontSize: 13, color: "var(--dsw-alias-label-secondary,#4b5563)", marginBottom: 16, lineHeight: 1.5 } },
2402
+ "\u5F53\u524D\u64CD\u4F5C\u9700\u8981\u540E\u53F0\u7BA1\u7406\u5458\u6743\u9650\u3002\u4E3A\u4FDD\u62A4\u60A8\u7684\u7F51\u7EDC\u914D\u7F6E\u4E0E\u673A\u5668\u4EBA\u5E73\u53F0\u5B89\u5168\uFF0C\u8BF7\u8F93\u5165\u7BA1\u7406\u5BC6\u7801\u89E3\u9501\uFF1A"
2403
+ ),
2404
+ React.createElement(
2405
+ "form",
2406
+ {
2407
+ onSubmit: handleUnlockAdmin,
2408
+ style: { display: "flex", flexDirection: "column", gap: 12 }
2409
+ },
2410
+ React.createElement("input", {
2411
+ type: "password",
2412
+ style: s.input,
2413
+ placeholder: "\u8BF7\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
2414
+ value: unlockPassword,
2415
+ onChange: (e) => setUnlockPassword(e.target.value),
2416
+ autoFocus: true
2417
+ }),
2418
+ unlockErr && React.createElement("div", {
2419
+ style: { fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" }
2420
+ }, unlockErr),
2421
+ React.createElement(
2422
+ "div",
2423
+ { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 4 } },
2424
+ React.createElement("button", {
2425
+ type: "button",
2426
+ style: s.btnGhost,
2427
+ onClick: () => setShowUnlockModal(false)
2428
+ }, "\u53D6\u6D88"),
2429
+ React.createElement("button", {
2430
+ type: "submit",
2431
+ style: { ...s.btnPri, background: "#4f6ef7", color: "#fff" },
2432
+ disabled: unlocking || !unlockPassword
2433
+ }, unlocking ? "\u9A8C\u8BC1\u4E2D\u2026" : "\u7ACB\u5373\u89E3\u9501")
2434
+ )
2435
+ ),
2436
+ React.createElement(
2437
+ "div",
2438
+ { style: { marginTop: 14, paddingTop: 10, borderTop: "1px solid var(--dsw-alias-border-l2,#f3f4f6)", fontSize: 11, color: "var(--dsw-alias-label-tertiary,#9ca3af)", textAlign: "center", lineHeight: 1.5 } },
2439
+ "\u{1F4A1} \u63D0\u793A\uFF1A\u82E5\u672A\u5355\u72EC\u914D\u7F6E\u7BA1\u7406\u5BC6\u7801\uFF0C\u8BF7\u8F93\u5165\u521D\u6B21\u8BBE\u7F6E\u7684\u8BBF\u95EE\u5BC6\u7801\uFF1B\u7535\u8111\u672C\u673A\uFF08127.0.0.1\uFF09\u8BBF\u95EE\u4EAB\u6709\u514D\u5BC6\u7BA1\u7406\u7279\u6743\u3002"
2440
+ )
2441
+ )
2442
+ )
2163
2443
  );
2164
2444
  }
2165
2445
  function apply(ctx) {