@wenbin_wb/dsh-bridge 2.9.0 → 2.10.1
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/CHANGELOG.md +320 -295
- package/client/client.js +370 -124
- package/client/index.js +4244 -4073
- package/client/unlock-manager.js +142 -0
- package/docs/release-process.md +40 -0
- package/lib/auth/manager.js +545 -532
- package/lib/bridge-rpc.js +455 -436
- package/lib/index.js +1831 -1765
- package/package.json +106 -106
package/client/client.js
CHANGED
|
@@ -832,6 +832,125 @@ var MOBILE_STYLES_CSS = `
|
|
|
832
832
|
}
|
|
833
833
|
`;
|
|
834
834
|
|
|
835
|
+
// client/unlock-manager.js
|
|
836
|
+
var SESSION_KEY = "dsh_admin_token";
|
|
837
|
+
var _token = "";
|
|
838
|
+
var _pendingOps = [];
|
|
839
|
+
var _onUnlocked = null;
|
|
840
|
+
function _readStorage() {
|
|
841
|
+
try {
|
|
842
|
+
return typeof window !== "undefined" ? window.sessionStorage.getItem(SESSION_KEY) : "";
|
|
843
|
+
} catch {
|
|
844
|
+
return "";
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function _writeStorage(t) {
|
|
848
|
+
try {
|
|
849
|
+
if (typeof window === "undefined") return;
|
|
850
|
+
if (t) window.sessionStorage.setItem(SESSION_KEY, t);
|
|
851
|
+
else window.sessionStorage.removeItem(SESSION_KEY);
|
|
852
|
+
} catch {
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function getAdminToken() {
|
|
856
|
+
if (_token) return _token;
|
|
857
|
+
const saved = _readStorage();
|
|
858
|
+
if (saved) {
|
|
859
|
+
_token = saved;
|
|
860
|
+
return saved;
|
|
861
|
+
}
|
|
862
|
+
return "";
|
|
863
|
+
}
|
|
864
|
+
function setAdminToken(t) {
|
|
865
|
+
_token = t || "";
|
|
866
|
+
_writeStorage(_token);
|
|
867
|
+
}
|
|
868
|
+
function clearAdminToken() {
|
|
869
|
+
setAdminToken("");
|
|
870
|
+
}
|
|
871
|
+
async function fetchLoopbackTokenOnce(force = true) {
|
|
872
|
+
if (typeof window === "undefined") return null;
|
|
873
|
+
if (!force) {
|
|
874
|
+
const existing = getAdminToken();
|
|
875
|
+
if (existing) return existing;
|
|
876
|
+
}
|
|
877
|
+
const candidates = [
|
|
878
|
+
"/__dsh_bridge__/loopback-token",
|
|
879
|
+
"http://127.0.0.1:3082/__dsh_bridge__/loopback-token",
|
|
880
|
+
"http://localhost:3082/__dsh_bridge__/loopback-token"
|
|
881
|
+
];
|
|
882
|
+
for (const url of [...new Set(candidates)]) {
|
|
883
|
+
try {
|
|
884
|
+
const res = await fetch(url, { method: "POST" });
|
|
885
|
+
if (res.ok) {
|
|
886
|
+
const data = await res.json();
|
|
887
|
+
if (data?.ok && data.adminToken) {
|
|
888
|
+
setAdminToken(data.adminToken);
|
|
889
|
+
return data.adminToken;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
function queuePendingOperation(retry) {
|
|
898
|
+
_pendingOps.push(retry);
|
|
899
|
+
}
|
|
900
|
+
async function replayPending() {
|
|
901
|
+
if (_pendingOps.length === 0) return;
|
|
902
|
+
const ops = _pendingOps;
|
|
903
|
+
_pendingOps = [];
|
|
904
|
+
for (const retry of ops) {
|
|
905
|
+
try {
|
|
906
|
+
await retry();
|
|
907
|
+
} catch {
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function onUnlocked(cb) {
|
|
912
|
+
if (!_onUnlocked) _onUnlocked = [];
|
|
913
|
+
_onUnlocked.push(cb);
|
|
914
|
+
return () => {
|
|
915
|
+
_onUnlocked = _onUnlocked.filter((f) => f !== cb);
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
function _emitUnlocked() {
|
|
919
|
+
if (_onUnlocked) {
|
|
920
|
+
for (const cb of _onUnlocked) {
|
|
921
|
+
try {
|
|
922
|
+
cb();
|
|
923
|
+
} catch {
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
async function unlockAdmin(rpcCall, password) {
|
|
929
|
+
try {
|
|
930
|
+
const res = await rpcCall("authAdminUnlock", { password });
|
|
931
|
+
if (res?.ok) {
|
|
932
|
+
const token = res.value?.adminToken || "";
|
|
933
|
+
setAdminToken(token);
|
|
934
|
+
_emitUnlocked();
|
|
935
|
+
await replayPending();
|
|
936
|
+
return { ok: true };
|
|
937
|
+
}
|
|
938
|
+
return { ok: false, error: res?.error?.message || "\u7BA1\u7406\u5458\u5BC6\u7801\u9519\u8BEF" };
|
|
939
|
+
} catch (err) {
|
|
940
|
+
const msg = String(err?.message || err || "");
|
|
941
|
+
if (msg.includes("401") || msg.includes("transport failure") || msg.includes("unauthorized")) {
|
|
942
|
+
if (typeof window !== "undefined" && typeof window.location?.reload === "function") {
|
|
943
|
+
try {
|
|
944
|
+
window.sessionStorage.setItem("dsh_access_expired_reloaded", "1");
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
window.location.reload();
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return { ok: false, error: err?.message || "\u89E3\u9501\u8BF7\u6C42\u5931\u8D25" };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
835
954
|
// lib/bridge-rpc-constants.js
|
|
836
955
|
var BRIDGE_RPC_CHANNEL = "/dsh-bridge";
|
|
837
956
|
var BRIDGE_ENDPOINTS = {
|
|
@@ -900,37 +1019,35 @@ if (typeof window !== "undefined") {
|
|
|
900
1019
|
};
|
|
901
1020
|
}
|
|
902
1021
|
}
|
|
903
|
-
var _globalAdminToken = "";
|
|
904
|
-
function setGlobalAdminToken(t) {
|
|
905
|
-
_globalAdminToken = t || "";
|
|
906
|
-
if (typeof window !== "undefined") {
|
|
907
|
-
try {
|
|
908
|
-
if (t) sessionStorage.setItem("dsh_admin_token", t);
|
|
909
|
-
else sessionStorage.removeItem("dsh_admin_token");
|
|
910
|
-
} catch {
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
function getGlobalAdminToken() {
|
|
915
|
-
if (_globalAdminToken) return _globalAdminToken;
|
|
916
|
-
if (typeof window !== "undefined") {
|
|
917
|
-
try {
|
|
918
|
-
const saved = sessionStorage.getItem("dsh_admin_token");
|
|
919
|
-
if (saved) {
|
|
920
|
-
_globalAdminToken = saved;
|
|
921
|
-
return saved;
|
|
922
|
-
}
|
|
923
|
-
} catch {
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
return "";
|
|
927
|
-
}
|
|
928
1022
|
function isLocalEnvironment() {
|
|
929
1023
|
if (typeof window === "undefined") return true;
|
|
930
1024
|
const host = window.location.hostname || "";
|
|
931
1025
|
const proto = window.location.protocol || "";
|
|
932
1026
|
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "" || proto === "file:" || proto === "vscode-webview:" || proto === "app:" || typeof window.__DSH_ELECTRON__ !== "undefined" || typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.includes("Electron");
|
|
933
1027
|
}
|
|
1028
|
+
var ACCESS_EXPIRED_FLAG = "dsh_access_expired_reloaded";
|
|
1029
|
+
function isAccessSessionFailure(err) {
|
|
1030
|
+
const msg = String(err?.message || err || "");
|
|
1031
|
+
return msg.includes("401") || msg.includes("transport failure") || msg.includes("unauthorized");
|
|
1032
|
+
}
|
|
1033
|
+
function reloadToLoginPage() {
|
|
1034
|
+
try {
|
|
1035
|
+
if (sessionStorage.getItem(ACCESS_EXPIRED_FLAG)) {
|
|
1036
|
+
sessionStorage.removeItem(ACCESS_EXPIRED_FLAG);
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
sessionStorage.setItem(ACCESS_EXPIRED_FLAG, "1");
|
|
1040
|
+
} catch {
|
|
1041
|
+
}
|
|
1042
|
+
window.location.reload();
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
function clearAccessExpiredFlag() {
|
|
1046
|
+
try {
|
|
1047
|
+
sessionStorage.removeItem(ACCESS_EXPIRED_FLAG);
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
934
1051
|
var GITHUB_URL = "https://github.com/wenbin-wb/dsh-bridge";
|
|
935
1052
|
var RELEASES_URL = "https://github.com/wenbin-wb/dsh-bridge/releases";
|
|
936
1053
|
var ISSUES_URL = "https://github.com/wenbin-wb/dsh-bridge/issues/new";
|
|
@@ -1568,6 +1685,7 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
|
|
|
1568
1685
|
const [mode, setMode] = React.useState(auth?.mode ?? "token_and_password");
|
|
1569
1686
|
const [scope, setScope] = React.useState(auth?.scope ?? "all");
|
|
1570
1687
|
const [adminPolicy, setAdminPolicy] = React.useState(auth?.adminPolicy ?? "password_unlock");
|
|
1688
|
+
const [adminProtection, setAdminProtection] = React.useState(auth?.adminProtection ?? true);
|
|
1571
1689
|
const [accessPassword, setAccessPassword] = React.useState("");
|
|
1572
1690
|
const [showAccessPassword, setShowAccessPassword] = React.useState(false);
|
|
1573
1691
|
const [savingAccess, setSavingAccess] = React.useState(false);
|
|
@@ -1579,12 +1697,21 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
|
|
|
1579
1697
|
const [saveAdminSuccess, setSaveAdminSuccess] = React.useState(false);
|
|
1580
1698
|
const [msgAdmin, setMsgAdmin] = React.useState(null);
|
|
1581
1699
|
const [topMsg, setTopMsg] = React.useState(null);
|
|
1700
|
+
React.useEffect(() => {
|
|
1701
|
+
const off = onUnlocked(() => {
|
|
1702
|
+
setTopMsg(null);
|
|
1703
|
+
setMsgAccess(null);
|
|
1704
|
+
setMsgAdmin(null);
|
|
1705
|
+
});
|
|
1706
|
+
return off;
|
|
1707
|
+
}, []);
|
|
1582
1708
|
React.useEffect(() => {
|
|
1583
1709
|
if (auth) {
|
|
1584
1710
|
setEnabled(auth.enabled ?? false);
|
|
1585
1711
|
setMode(auth.mode ?? "token_and_password");
|
|
1586
1712
|
setScope(auth.scope ?? "all");
|
|
1587
1713
|
setAdminPolicy(auth.adminPolicy ?? "password_unlock");
|
|
1714
|
+
setAdminProtection(auth.adminProtection ?? true);
|
|
1588
1715
|
}
|
|
1589
1716
|
}, [auth]);
|
|
1590
1717
|
const handleToggleEnabled = async () => {
|
|
@@ -1594,13 +1721,27 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
|
|
|
1594
1721
|
try {
|
|
1595
1722
|
const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { enabled: next });
|
|
1596
1723
|
if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
|
|
1597
|
-
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" });
|
|
1724
|
+
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\uFF08\u8BBF\u95EE\u514D\u5BC6\uFF0C\u7BA1\u7406\u4FDD\u62A4\u4E0D\u53D7\u5F71\u54CD\uFF09" });
|
|
1598
1725
|
onUpdate?.();
|
|
1599
1726
|
} catch (e) {
|
|
1600
1727
|
setEnabled(prev);
|
|
1601
1728
|
setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
|
|
1602
1729
|
}
|
|
1603
1730
|
};
|
|
1731
|
+
const handleToggleAdminProtection = async () => {
|
|
1732
|
+
const prev = adminProtection;
|
|
1733
|
+
const next = !adminProtection;
|
|
1734
|
+
setAdminProtection(next);
|
|
1735
|
+
try {
|
|
1736
|
+
const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminProtection: next });
|
|
1737
|
+
if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
|
|
1738
|
+
setTopMsg({ ok: true, text: next ? "\u2713 \u7BA1\u7406\u4FDD\u62A4\u5DF2\u5F00\u542F\uFF08\u4FEE\u6539\u914D\u7F6E\u9700\u7BA1\u7406\u5BC6\u7801\uFF09" : "\u2713 \u7BA1\u7406\u4FDD\u62A4\u5DF2\u5173\u95ED\uFF08\u4FEE\u6539\u914D\u7F6E\u514D\u5BC6\uFF0C\u8BF7\u8C28\u614E\uFF09" });
|
|
1739
|
+
onUpdate?.();
|
|
1740
|
+
} catch (e) {
|
|
1741
|
+
setAdminProtection(prev);
|
|
1742
|
+
setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1604
1745
|
const handleChangeMode = async (m) => {
|
|
1605
1746
|
const prev = mode;
|
|
1606
1747
|
setMode(m);
|
|
@@ -1785,13 +1926,13 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
|
|
|
1785
1926
|
}
|
|
1786
1927
|
}, topMsg.text)
|
|
1787
1928
|
),
|
|
1788
|
-
|
|
1929
|
+
React.createElement(
|
|
1789
1930
|
React.Fragment,
|
|
1790
1931
|
null,
|
|
1791
1932
|
// =========================================================================
|
|
1792
1933
|
// ---- 第一道防线:外部访问门禁(控制谁能进入 Web 界面使用 AI) ----
|
|
1793
1934
|
// =========================================================================
|
|
1794
|
-
React.createElement(
|
|
1935
|
+
enabled && React.createElement(
|
|
1795
1936
|
"div",
|
|
1796
1937
|
{ style: s.card },
|
|
1797
1938
|
React.createElement(
|
|
@@ -1976,6 +2117,22 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
|
|
|
1976
2117
|
"div",
|
|
1977
2118
|
{ style: { ...s.muted, marginTop: 3 } },
|
|
1978
2119
|
"\u9501\u5B9A\u6574\u4E2A\u63D2\u4EF6\u8BBE\u7F6E\u540E\u53F0\uFF08\u5305\u542B\u5C40\u57DF\u7F51\u3001\u516C\u7F51\u96A7\u9053\u3001IM \u673A\u5668\u4EBA\u5BC6\u94A5\u4E0E\u5B89\u5168\u8BBE\u7F6E\uFF09\uFF0C\u9632\u6B62\u4ED6\u4EBA\u968F\u610F\u7BE1\u6539\u914D\u7F6E"
|
|
2120
|
+
),
|
|
2121
|
+
React.createElement(
|
|
2122
|
+
"div",
|
|
2123
|
+
{
|
|
2124
|
+
style: { display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 8, marginTop: 10, paddingTop: 10, borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)" }
|
|
2125
|
+
},
|
|
2126
|
+
React.createElement(
|
|
2127
|
+
"div",
|
|
2128
|
+
{ style: { fontSize: 12, fontWeight: 600, color: "var(--dsw-alias-label-primary,currentColor)" } },
|
|
2129
|
+
adminProtection ? "\u{1F6E1}\uFE0F \u7BA1\u7406\u4FDD\u62A4\u5DF2\u5F00\u542F\uFF08\u4FEE\u6539\u914D\u7F6E\u9700\u7BA1\u7406\u5BC6\u7801\uFF09" : "\u26A0\uFE0F \u7BA1\u7406\u4FDD\u62A4\u5DF2\u5173\u95ED\uFF08\u4FEE\u6539\u914D\u7F6E\u514D\u5BC6\uFF09"
|
|
2130
|
+
),
|
|
2131
|
+
React.createElement("button", {
|
|
2132
|
+
type: "button",
|
|
2133
|
+
style: { ...adminProtection ? s.btnGhost : s.btnPri, height: 28, fontSize: 12, padding: "0 12px" },
|
|
2134
|
+
onClick: handleToggleAdminProtection
|
|
2135
|
+
}, adminProtection ? "\u5173\u95ED\u7BA1\u7406\u4FDD\u62A4" : "\u5F00\u542F\u7BA1\u7406\u4FDD\u62A4")
|
|
1979
2136
|
)
|
|
1980
2137
|
),
|
|
1981
2138
|
// 管理员密码设置
|
|
@@ -3284,6 +3441,25 @@ function VersionBanner({ rpcCall }) {
|
|
|
3284
3441
|
hasUpdate && React.createElement("span", { style: { fontWeight: 600, fontSize: 11 } }, `\u2794 v${info.latest}`),
|
|
3285
3442
|
info?.error && React.createElement("span", { style: { color: "var(--dsw-alias-state-warn-primary,#d97706)", fontSize: 11 } }, "(\u7F51\u7EDC\u8D85\u65F6)")
|
|
3286
3443
|
),
|
|
3444
|
+
// DSH 宿主版本标签
|
|
3445
|
+
info?.dshVersion && React.createElement(
|
|
3446
|
+
"span",
|
|
3447
|
+
{
|
|
3448
|
+
style: {
|
|
3449
|
+
...s.tag,
|
|
3450
|
+
background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
|
|
3451
|
+
color: "var(--dsw-alias-label-tertiary,#6b7280)",
|
|
3452
|
+
padding: "3px 10px",
|
|
3453
|
+
fontSize: 12,
|
|
3454
|
+
fontWeight: 500,
|
|
3455
|
+
display: "inline-flex",
|
|
3456
|
+
alignItems: "center",
|
|
3457
|
+
gap: 5
|
|
3458
|
+
}
|
|
3459
|
+
},
|
|
3460
|
+
React.createElement("span", { style: { opacity: 0.75, fontSize: 11, fontWeight: 400 } }, "DSH"),
|
|
3461
|
+
`v${info.dshVersion}`
|
|
3462
|
+
),
|
|
3287
3463
|
// 刷新检查按钮
|
|
3288
3464
|
React.createElement(
|
|
3289
3465
|
"button",
|
|
@@ -3556,7 +3732,6 @@ function BridgePanel({ rpcCall }) {
|
|
|
3556
3732
|
const [platforms, setPlatforms] = React.useState(null);
|
|
3557
3733
|
const [selectedPlatform, setSelectedPlatform] = React.useState("wechat");
|
|
3558
3734
|
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"));
|
|
3559
|
-
const [adminToken, setAdminToken] = React.useState("");
|
|
3560
3735
|
const [adminUnlocked, setAdminUnlocked] = React.useState(false);
|
|
3561
3736
|
const [unlockPassword, setUnlockPassword] = React.useState("");
|
|
3562
3737
|
const [unlockErr, setUnlockErr] = React.useState(null);
|
|
@@ -3565,39 +3740,27 @@ function BridgePanel({ rpcCall }) {
|
|
|
3565
3740
|
const [showUnlockModal, setShowUnlockModal] = React.useState(false);
|
|
3566
3741
|
const fetchLoopbackToken = React.useCallback(async () => {
|
|
3567
3742
|
if (!isLocalhost) return null;
|
|
3568
|
-
const
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
`http://127.0.0.1:${proxyPort}/__dsh_bridge__/loopback-token`,
|
|
3572
|
-
`http://localhost:${proxyPort}/__dsh_bridge__/loopback-token`,
|
|
3573
|
-
"http://127.0.0.1:3082/__dsh_bridge__/loopback-token"
|
|
3574
|
-
];
|
|
3575
|
-
const uniqueUrls = [...new Set(candidateUrls)];
|
|
3576
|
-
for (const url of uniqueUrls) {
|
|
3577
|
-
try {
|
|
3578
|
-
const res = await fetch(url, { method: "POST" });
|
|
3579
|
-
if (res.ok) {
|
|
3580
|
-
const data = await res.json();
|
|
3581
|
-
if (data?.ok && data.adminToken) {
|
|
3582
|
-
setAdminToken(data.adminToken);
|
|
3583
|
-
setGlobalAdminToken(data.adminToken);
|
|
3584
|
-
setAdminUnlocked(true);
|
|
3585
|
-
return data.adminToken;
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
} catch {
|
|
3589
|
-
}
|
|
3743
|
+
const token = await fetchLoopbackTokenOnce();
|
|
3744
|
+
if (token) {
|
|
3745
|
+
setAdminUnlocked(true);
|
|
3590
3746
|
}
|
|
3591
|
-
return
|
|
3592
|
-
}, [isLocalhost
|
|
3747
|
+
return token;
|
|
3748
|
+
}, [isLocalhost]);
|
|
3593
3749
|
React.useEffect(() => {
|
|
3594
3750
|
if (isLocalhost && !adminUnlocked) {
|
|
3595
3751
|
fetchLoopbackToken();
|
|
3596
3752
|
}
|
|
3597
3753
|
}, [isLocalhost, adminUnlocked, fetchLoopbackToken]);
|
|
3598
|
-
const
|
|
3754
|
+
const handleAccessSessionExpired = React.useCallback(() => {
|
|
3755
|
+
setAdminUnlocked(false);
|
|
3756
|
+
clearAdminToken();
|
|
3757
|
+
const reloaded = reloadToLoginPage();
|
|
3758
|
+
if (!reloaded) {
|
|
3759
|
+
setErr("\u8BBF\u95EE\u4F1A\u8BDD\u5DF2\u5931\u6548\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u91CD\u65B0\u767B\u5F55");
|
|
3760
|
+
}
|
|
3761
|
+
}, []);
|
|
3599
3762
|
const authRpcCall = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
3600
|
-
let token =
|
|
3763
|
+
let token = getAdminToken();
|
|
3601
3764
|
if (isLocalhost && !token) {
|
|
3602
3765
|
token = await fetchLoopbackToken();
|
|
3603
3766
|
}
|
|
@@ -3606,51 +3769,60 @@ function BridgePanel({ rpcCall }) {
|
|
|
3606
3769
|
...token ? { adminToken: token } : {},
|
|
3607
3770
|
...isLocalhost ? { isLocalhost: true } : {}
|
|
3608
3771
|
};
|
|
3609
|
-
|
|
3772
|
+
let res;
|
|
3773
|
+
try {
|
|
3774
|
+
res = await rpcCall(endpoint, enriched, signal);
|
|
3775
|
+
} catch (e) {
|
|
3776
|
+
if (isAccessSessionFailure(e)) {
|
|
3777
|
+
handleAccessSessionExpired();
|
|
3778
|
+
}
|
|
3779
|
+
throw e;
|
|
3780
|
+
}
|
|
3610
3781
|
if (res?.ok === false) {
|
|
3611
3782
|
const msg = res?.error?.message || "";
|
|
3612
3783
|
if (msg.includes("\u7BA1\u7406\u5458\u6743\u9650") || msg.includes("\u7BA1\u7406\u5BC6\u7801\u89E3\u9501")) {
|
|
3613
|
-
|
|
3784
|
+
queuePendingOperation(async () => {
|
|
3785
|
+
let fresh = getAdminToken();
|
|
3786
|
+
if (!fresh && isLocalhost) fresh = await fetchLoopbackToken();
|
|
3787
|
+
if (!fresh) return;
|
|
3788
|
+
const retry = await rpcCall(endpoint, { ...payload, adminToken: fresh, ...isLocalhost ? { isLocalhost: true } : {} }, signal);
|
|
3789
|
+
if (retry?.ok) {
|
|
3790
|
+
setStatus(retry.value);
|
|
3791
|
+
setErr(null);
|
|
3792
|
+
}
|
|
3793
|
+
});
|
|
3614
3794
|
setUnlockErr(msg);
|
|
3615
3795
|
setShowUnlockModal(true);
|
|
3616
3796
|
}
|
|
3617
3797
|
}
|
|
3618
3798
|
return res;
|
|
3619
|
-
}, [rpcCall,
|
|
3799
|
+
}, [rpcCall, isLocalhost, fetchLoopbackToken, handleAccessSessionExpired]);
|
|
3620
3800
|
const handleUnlockAdmin = React.useCallback(async (e) => {
|
|
3621
3801
|
e?.preventDefault?.();
|
|
3622
3802
|
setUnlocking(true);
|
|
3623
3803
|
setUnlockErr(null);
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
setShowUnlockModal(false);
|
|
3633
|
-
setErr(null);
|
|
3634
|
-
} else {
|
|
3635
|
-
setUnlockErr(res?.error?.message || "\u7BA1\u7406\u5458\u5BC6\u7801\u9519\u8BEF");
|
|
3636
|
-
}
|
|
3637
|
-
} catch (err2) {
|
|
3638
|
-
setUnlockErr(err2.message || "\u89E3\u9501\u8BF7\u6C42\u5931\u8D25");
|
|
3639
|
-
} finally {
|
|
3640
|
-
setUnlocking(false);
|
|
3804
|
+
const res = await unlockAdmin(rpcCall, unlockPassword);
|
|
3805
|
+
if (res.ok) {
|
|
3806
|
+
setAdminUnlocked(true);
|
|
3807
|
+
setUnlockPassword("");
|
|
3808
|
+
setShowUnlockModal(false);
|
|
3809
|
+
setErr(null);
|
|
3810
|
+
} else {
|
|
3811
|
+
setUnlockErr(res.error);
|
|
3641
3812
|
}
|
|
3813
|
+
setUnlocking(false);
|
|
3642
3814
|
}, [rpcCall, unlockPassword]);
|
|
3643
3815
|
const handleLockAdmin = React.useCallback(async () => {
|
|
3644
3816
|
try {
|
|
3645
|
-
|
|
3646
|
-
|
|
3817
|
+
const t = getAdminToken();
|
|
3818
|
+
if (t) {
|
|
3819
|
+
await rpcCall(BRIDGE_ENDPOINTS.authAdminLock, { adminToken: t });
|
|
3647
3820
|
}
|
|
3648
3821
|
} catch {
|
|
3649
3822
|
}
|
|
3650
|
-
|
|
3651
|
-
setGlobalAdminToken("");
|
|
3823
|
+
clearAdminToken();
|
|
3652
3824
|
setAdminUnlocked(false);
|
|
3653
|
-
}, [rpcCall
|
|
3825
|
+
}, [rpcCall]);
|
|
3654
3826
|
const loadInFlightRef = React.useRef(false);
|
|
3655
3827
|
const loadSeqRef = React.useRef(0);
|
|
3656
3828
|
const load = React.useCallback(async (quiet = false) => {
|
|
@@ -3663,12 +3835,19 @@ function BridgePanel({ rpcCall }) {
|
|
|
3663
3835
|
if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
|
|
3664
3836
|
setStatus(r.value);
|
|
3665
3837
|
if (!quiet) setErr(null);
|
|
3838
|
+
clearAccessExpiredFlag();
|
|
3666
3839
|
} catch (e) {
|
|
3667
|
-
if (currentSeq === loadSeqRef.current)
|
|
3840
|
+
if (currentSeq === loadSeqRef.current) {
|
|
3841
|
+
if (isAccessSessionFailure(e)) {
|
|
3842
|
+
handleAccessSessionExpired();
|
|
3843
|
+
} else {
|
|
3844
|
+
setErr(e.message);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3668
3847
|
} finally {
|
|
3669
3848
|
loadInFlightRef.current = false;
|
|
3670
3849
|
}
|
|
3671
|
-
}, [authRpcCall]);
|
|
3850
|
+
}, [authRpcCall, handleAccessSessionExpired]);
|
|
3672
3851
|
const pollPlatformsSeqRef = React.useRef(0);
|
|
3673
3852
|
React.useEffect(() => {
|
|
3674
3853
|
let alive = true;
|
|
@@ -3687,38 +3866,20 @@ function BridgePanel({ rpcCall }) {
|
|
|
3687
3866
|
inFlight = false;
|
|
3688
3867
|
}
|
|
3689
3868
|
};
|
|
3869
|
+
if (!adminUnlocked && !isLocalhost) return;
|
|
3690
3870
|
poll();
|
|
3691
3871
|
const t = setInterval(poll, 4e3);
|
|
3692
3872
|
return () => {
|
|
3693
3873
|
alive = false;
|
|
3694
3874
|
clearInterval(t);
|
|
3695
3875
|
};
|
|
3696
|
-
}, [authRpcCall]);
|
|
3876
|
+
}, [authRpcCall, adminUnlocked, isLocalhost]);
|
|
3697
3877
|
React.useEffect(() => {
|
|
3698
3878
|
load();
|
|
3879
|
+
if (!adminUnlocked && !isLocalhost) return;
|
|
3699
3880
|
const t = setInterval(() => load(true), 3e3);
|
|
3700
3881
|
return () => clearInterval(t);
|
|
3701
|
-
}, [load]);
|
|
3702
|
-
React.useEffect(() => {
|
|
3703
|
-
if (!adminUnlocked) return;
|
|
3704
|
-
const pending = pendingRetryRef.current;
|
|
3705
|
-
if (!pending) return;
|
|
3706
|
-
pendingRetryRef.current = null;
|
|
3707
|
-
const token = adminToken || getGlobalAdminToken();
|
|
3708
|
-
(async () => {
|
|
3709
|
-
try {
|
|
3710
|
-
const r = await rpcCall(pending.endpoint, { ...pending.payload, adminToken: token });
|
|
3711
|
-
if (r?.ok) {
|
|
3712
|
-
if (r.value) setStatus(r.value);
|
|
3713
|
-
setErr(null);
|
|
3714
|
-
} else {
|
|
3715
|
-
setErr(r?.error?.message || "\u521A\u624D\u7684\u64CD\u4F5C\u91CD\u8BD5\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u91CD\u8BD5");
|
|
3716
|
-
}
|
|
3717
|
-
} catch (e) {
|
|
3718
|
-
setErr(e.message || "\u521A\u624D\u7684\u64CD\u4F5C\u91CD\u8BD5\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u91CD\u8BD5");
|
|
3719
|
-
}
|
|
3720
|
-
})();
|
|
3721
|
-
}, [adminUnlocked, adminToken, rpcCall]);
|
|
3882
|
+
}, [load, adminUnlocked, isLocalhost]);
|
|
3722
3883
|
const act = React.useCallback(async (endpoint, payload) => {
|
|
3723
3884
|
try {
|
|
3724
3885
|
const r = await authRpcCall(endpoint, payload ?? {});
|
|
@@ -3933,7 +4094,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
3933
4094
|
}
|
|
3934
4095
|
const auth = status?.auth;
|
|
3935
4096
|
const policy = auth?.adminPolicy ?? "password_unlock";
|
|
3936
|
-
const isLocked = !isLocalhost && auth?.
|
|
4097
|
+
const isLocked = !isLocalhost && auth?.adminProtection !== false && policy !== "open" && !adminUnlocked;
|
|
3937
4098
|
if (isLocked) {
|
|
3938
4099
|
return React.createElement(
|
|
3939
4100
|
"div",
|
|
@@ -4227,6 +4388,8 @@ function injectMobileStyles() {
|
|
|
4227
4388
|
if (document.getElementById("dsh-bridge-mobile-styles")) return;
|
|
4228
4389
|
const style = document.createElement("style");
|
|
4229
4390
|
style.id = "dsh-bridge-mobile-styles";
|
|
4391
|
+
style.dataset.plugin = "@wenbin_wb/dsh-bridge";
|
|
4392
|
+
style.dataset.pluginCss = "@wenbin_wb/dsh-bridge/mobile-styles";
|
|
4230
4393
|
style.textContent = MOBILE_STYLES_CSS;
|
|
4231
4394
|
document.head.appendChild(style);
|
|
4232
4395
|
}
|
|
@@ -4509,6 +4672,11 @@ function showRemoteWorkspaceDialog(rpcCall, onWorkspaceAdded, clientCtx, onPicke
|
|
|
4509
4672
|
let isSubmitting = false;
|
|
4510
4673
|
let statusMessage = null;
|
|
4511
4674
|
let isErrorMessage = false;
|
|
4675
|
+
let needUnlock = false;
|
|
4676
|
+
let unlockable = true;
|
|
4677
|
+
let unlockInput = "";
|
|
4678
|
+
let unlockErr = null;
|
|
4679
|
+
let unlocking = false;
|
|
4512
4680
|
function closeModal() {
|
|
4513
4681
|
document.removeEventListener("keydown", handleKeydown);
|
|
4514
4682
|
overlay.style.opacity = "0";
|
|
@@ -4551,6 +4719,20 @@ function showRemoteWorkspaceDialog(rpcCall, onWorkspaceAdded, clientCtx, onPicke
|
|
|
4551
4719
|
<button id="dsh-ws-close-btn" style="border: none; background: none; font-size: 18px; cursor: pointer; color: var(--dsw-alias-label-tertiary, #9ca3af); padding: 4px 8px; border-radius: 6px; line-height: 1; flex-shrink: 0;">\u2715</button>
|
|
4552
4720
|
</div>
|
|
4553
4721
|
|
|
4722
|
+
<!-- \u7BA1\u7406\u6743\u9650\u89E3\u9501\u5757\uFF08\u8FDC\u7A0B\u8BBF\u95EE\u9700\u8981\u7BA1\u7406\u5BC6\u7801\uFF1Blocal_only \u7B56\u7565\u4E0B\u4E0D\u663E\u793A\uFF0C\u4EC5\u63D0\u793A\uFF09 -->
|
|
4723
|
+
${needUnlock && unlockable ? `
|
|
4724
|
+
<div style="padding: 14px 16px; background: var(--dsw-alias-state-warn-bg, #fffbeb); border-bottom: 1px solid var(--dsw-alias-state-warn-border, #fde68a); flex-shrink: 0;">
|
|
4725
|
+
<div style="font-size: 12px; font-weight: 600; color: var(--dsw-alias-state-warn-primary, #92400e); margin-bottom: 6px;">\u{1F512} \u6B64\u64CD\u4F5C\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650</div>
|
|
4726
|
+
<div style="font-size: 11px; color: var(--dsw-alias-label-secondary, #6b7280); margin-bottom: 8px; line-height: 1.5;">\u8FDC\u7A0B\u8BBF\u95EE\u65F6\u6D4F\u89C8/\u6DFB\u52A0\u5DE5\u4F5C\u533A\u9700\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801\u89E3\u9501\uFF08\u4E0E\u8BBF\u95EE\u5BC6\u7801\u4E0D\u540C\uFF09\u3002</div>
|
|
4727
|
+
<form id="dsh-ws-unlock-form" style="display: flex; gap: 8px;">
|
|
4728
|
+
<input id="dsh-ws-unlock-input" type="password" placeholder="\u8BF7\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801" value="${escapeHtml(unlockInput)}"
|
|
4729
|
+
style="flex: 1; font: inherit; font-size: 13px; padding: 7px 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, currentColor); outline: none; box-sizing: border-box;" />
|
|
4730
|
+
<button type="submit" style="border: none; background: var(--dsw-alias-brand-primary, #4f6ef7); color: #fff; border-radius: 8px; padding: 0 14px; font-size: 12px; font-weight: 600; cursor: pointer; flex-shrink: 0;" ${unlocking ? "disabled" : ""}>${unlocking ? "\u89E3\u9501\u4E2D\u2026" : "\u89E3\u9501"}</button>
|
|
4731
|
+
</form>
|
|
4732
|
+
${unlockErr ? `<div style="font-size: 11px; color: var(--dsw-alias-state-error-primary, #dc2626); margin-top: 6px;">${escapeHtml(unlockErr)}</div>` : ""}
|
|
4733
|
+
</div>
|
|
4734
|
+
` : ""}
|
|
4735
|
+
|
|
4554
4736
|
<div style="padding: 12px 16px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 10px;">
|
|
4555
4737
|
<!-- \u63D0\u793A\u4FE1\u606F\u6A2A\u5E45 -->
|
|
4556
4738
|
${statusMessage ? `
|
|
@@ -4711,6 +4893,31 @@ function showRemoteWorkspaceDialog(rpcCall, onWorkspaceAdded, clientCtx, onPicke
|
|
|
4711
4893
|
`;
|
|
4712
4894
|
modal.querySelector("#dsh-ws-close-btn")?.addEventListener("click", closeModal);
|
|
4713
4895
|
modal.querySelector("#dsh-ws-cancel-btn")?.addEventListener("click", closeModal);
|
|
4896
|
+
const unlockForm = modal.querySelector("#dsh-ws-unlock-form");
|
|
4897
|
+
if (unlockForm) {
|
|
4898
|
+
unlockForm.addEventListener("submit", async (e) => {
|
|
4899
|
+
e.preventDefault();
|
|
4900
|
+
if (unlocking) return;
|
|
4901
|
+
const input = modal.querySelector("#dsh-ws-unlock-input");
|
|
4902
|
+
const pwd = input?.value || "";
|
|
4903
|
+
if (!pwd) return;
|
|
4904
|
+
unlocking = true;
|
|
4905
|
+
unlockErr = null;
|
|
4906
|
+
render();
|
|
4907
|
+
const res = await unlockAdmin(rpcCall, pwd);
|
|
4908
|
+
if (res.ok) {
|
|
4909
|
+
needUnlock = false;
|
|
4910
|
+
unlockInput = "";
|
|
4911
|
+
statusMessage = null;
|
|
4912
|
+
isErrorMessage = false;
|
|
4913
|
+
await loadDirectory(currentPath);
|
|
4914
|
+
} else {
|
|
4915
|
+
unlockErr = res.error || "\u89E3\u9501\u5931\u8D25";
|
|
4916
|
+
}
|
|
4917
|
+
unlocking = false;
|
|
4918
|
+
render();
|
|
4919
|
+
});
|
|
4920
|
+
}
|
|
4714
4921
|
modal.querySelectorAll(".dsh-ws-crumb-btn").forEach((btn) => {
|
|
4715
4922
|
btn.addEventListener("click", () => {
|
|
4716
4923
|
const p = btn.getAttribute("data-path");
|
|
@@ -4800,25 +5007,40 @@ function showRemoteWorkspaceDialog(rpcCall, onWorkspaceAdded, clientCtx, onPicke
|
|
|
4800
5007
|
}
|
|
4801
5008
|
}
|
|
4802
5009
|
async function authRpc(endpoint, payload = {}) {
|
|
4803
|
-
let token =
|
|
4804
|
-
if (
|
|
4805
|
-
|
|
4806
|
-
const res = await fetch("/__dsh_bridge__/loopback-token", { method: "POST" });
|
|
4807
|
-
if (res.ok) {
|
|
4808
|
-
const data = await res.json();
|
|
4809
|
-
if (data?.adminToken) {
|
|
4810
|
-
token = data.adminToken;
|
|
4811
|
-
setGlobalAdminToken(token);
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
} catch {
|
|
4815
|
-
}
|
|
5010
|
+
let token = getAdminToken();
|
|
5011
|
+
if (isLocalEnvironment()) {
|
|
5012
|
+
token = await fetchLoopbackTokenOnce();
|
|
4816
5013
|
}
|
|
4817
|
-
|
|
5014
|
+
const enriched = {
|
|
4818
5015
|
...payload,
|
|
4819
5016
|
...token ? { adminToken: token } : {},
|
|
4820
5017
|
...isLocalEnvironment() ? { isLocalhost: true } : {}
|
|
5018
|
+
};
|
|
5019
|
+
const res = await rpcCall(endpoint, enriched).catch((e) => {
|
|
5020
|
+
if (isAccessSessionFailure(e)) {
|
|
5021
|
+
clearAdminToken();
|
|
5022
|
+
reloadToLoginPage();
|
|
5023
|
+
}
|
|
5024
|
+
throw e;
|
|
4821
5025
|
});
|
|
5026
|
+
if (res?.ok === false) {
|
|
5027
|
+
const msg = res?.error?.message || "";
|
|
5028
|
+
if (isLocalEnvironment() && (msg.includes("\u7BA1\u7406\u5458\u6743\u9650") || msg.includes("\u7BA1\u7406\u5BC6\u7801\u89E3\u9501"))) {
|
|
5029
|
+
const fresh = await fetchLoopbackTokenOnce(true);
|
|
5030
|
+
if (fresh) {
|
|
5031
|
+
return rpcCall(endpoint, { ...payload, adminToken: fresh, isLocalhost: true });
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
if (msg.includes("\u7BA1\u7406\u5458\u6743\u9650") || msg.includes("\u7BA1\u7406\u5BC6\u7801\u89E3\u9501")) {
|
|
5035
|
+
clearAdminToken();
|
|
5036
|
+
const err = new Error("need-unlock");
|
|
5037
|
+
err.needUnlock = true;
|
|
5038
|
+
err.message = msg;
|
|
5039
|
+
err.unlockable = !msg.includes("\u4EC5\u9650\u7535\u8111\u672C\u673A\u7BA1\u7406");
|
|
5040
|
+
throw err;
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
return res;
|
|
4822
5044
|
}
|
|
4823
5045
|
async function switchToWorkspace(wsId, wsPath) {
|
|
4824
5046
|
if (isSubmitting) return;
|
|
@@ -4898,13 +5120,25 @@ function showRemoteWorkspaceDialog(rpcCall, onWorkspaceAdded, clientCtx, onPicke
|
|
|
4898
5120
|
drives = res.drives || [];
|
|
4899
5121
|
workspaces = res.workspaces || [];
|
|
4900
5122
|
if (res.error) {
|
|
4901
|
-
statusMessage = res.error;
|
|
5123
|
+
statusMessage = typeof res.error === "string" ? res.error : res.error?.message || "\u8BFB\u53D6\u76EE\u5F55\u5931\u8D25";
|
|
4902
5124
|
isErrorMessage = true;
|
|
4903
5125
|
}
|
|
4904
5126
|
}
|
|
4905
5127
|
} catch (err) {
|
|
4906
|
-
|
|
4907
|
-
|
|
5128
|
+
if (err?.needUnlock) {
|
|
5129
|
+
needUnlock = true;
|
|
5130
|
+
unlockErr = null;
|
|
5131
|
+
statusMessage = null;
|
|
5132
|
+
isErrorMessage = false;
|
|
5133
|
+
unlockable = err.unlockable !== false;
|
|
5134
|
+
if (!unlockable) {
|
|
5135
|
+
statusMessage = err.message || "\u5F53\u524D\u7B56\u7565\u4EC5\u9650\u7535\u8111\u672C\u673A\u7BA1\u7406\uFF0C\u8FDC\u7A0B\u65E0\u6CD5\u89E3\u9501";
|
|
5136
|
+
isErrorMessage = true;
|
|
5137
|
+
}
|
|
5138
|
+
} else {
|
|
5139
|
+
statusMessage = err.message || "\u8BFB\u53D6\u76EE\u5F55\u5931\u8D25";
|
|
5140
|
+
isErrorMessage = true;
|
|
5141
|
+
}
|
|
4908
5142
|
} finally {
|
|
4909
5143
|
isLoading = false;
|
|
4910
5144
|
render();
|
|
@@ -4977,8 +5211,20 @@ function showRemoteWorkspaceDialog(rpcCall, onWorkspaceAdded, clientCtx, onPicke
|
|
|
4977
5211
|
render();
|
|
4978
5212
|
}
|
|
4979
5213
|
} catch (err) {
|
|
4980
|
-
|
|
4981
|
-
|
|
5214
|
+
if (err?.needUnlock) {
|
|
5215
|
+
needUnlock = true;
|
|
5216
|
+
unlockable = err.unlockable !== false;
|
|
5217
|
+
unlockErr = null;
|
|
5218
|
+
statusMessage = null;
|
|
5219
|
+
isErrorMessage = false;
|
|
5220
|
+
if (!unlockable) {
|
|
5221
|
+
statusMessage = err.message || "\u5F53\u524D\u7B56\u7565\u4EC5\u9650\u7535\u8111\u672C\u673A\u7BA1\u7406\uFF0C\u8FDC\u7A0B\u65E0\u6CD5\u89E3\u9501";
|
|
5222
|
+
isErrorMessage = true;
|
|
5223
|
+
}
|
|
5224
|
+
} else {
|
|
5225
|
+
statusMessage = err.message || "\u6DFB\u52A0\u5DE5\u4F5C\u533A\u5F02\u5E38";
|
|
5226
|
+
isErrorMessage = true;
|
|
5227
|
+
}
|
|
4982
5228
|
render();
|
|
4983
5229
|
} finally {
|
|
4984
5230
|
isSubmitting = false;
|