@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/README.en.md +122 -19
- package/README.md +122 -21
- package/client/client.js +308 -28
- package/client/index.js +283 -32
- package/docs/banner.jpg +0 -0
- package/docs/feishu-usage.md +5 -0
- package/docs/platform-abstraction-design.md +473 -0
- package/docs/screenshots/admin-lock-screen.jpg +0 -0
- package/docs/screenshots/feishu-bot-config.jpg +0 -0
- package/docs/screenshots/feishu-chat.jpg +0 -0
- package/docs/screenshots/lan-access.jpg +0 -0
- package/docs/screenshots/qq-bot-config.jpg +0 -0
- package/docs/screenshots/qq-chat.jpg +0 -0
- package/docs/screenshots/qq-group.jpg +0 -0
- package/docs/screenshots/qr-scan.jpg +0 -0
- package/docs/screenshots/remote-auth-login.jpg +0 -0
- package/docs/screenshots/remote-web-mobile.jpg +0 -0
- package/docs/screenshots/security-auth-config.jpg +0 -0
- package/docs/screenshots/telegram-bot-config.jpg +0 -0
- package/docs/screenshots/tunnel-access.jpg +0 -0
- package/docs/screenshots/wechat-bot-config.jpg +0 -0
- package/docs/screenshots/wechat-chat.jpg +0 -0
- package/docs/telegram-usage.md +2 -0
- package/docs/wechat-bot-plan.md +294 -0
- package/lib/auth/manager.js +63 -18
- package/lib/bridge-rpc.js +95 -8
- package/lib/feishu/node.js +4 -4
- package/lib/index.js +91 -22
- package/lib/qq/node.js +9 -2
- package/lib/telegram/node.js +4 -4
- package/lib/tunnel-client.mjs +11 -2
- package/lib/wechat/media.js +8 -2
- package/lib/wechat/node.js +2 -2
- package/package.json +11 -12
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
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
}, [
|
|
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
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
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:
|
|
2324
|
+
onClick: handleLockAdmin
|
|
2158
2325
|
}, "\u{1F512} \u91CD\u65B0\u9501\u5B9A\u540E\u53F0")
|
|
2159
2326
|
),
|
|
2160
|
-
|
|
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) {
|