@wenbin_wb/dsh-bridge 2.5.1 → 2.5.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.
- package/README.en.md +121 -18
- package/README.md +121 -20
- package/client/client.js +214 -9
- package/client/index.js +182 -7
- package/docs/banner.jpg +0 -0
- package/docs/feishu-usage.md +5 -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/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/telegram-usage.md +2 -0
- package/lib/feishu/gateway.js +2 -1
- package/lib/feishu/lark-bundled.mjs +125150 -0
- package/lib/index.js +32 -0
- package/package.json +9 -6
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";
|
|
@@ -1830,14 +1849,59 @@ function BridgePanel({ rpcCall }) {
|
|
|
1830
1849
|
const [unlockErr, setUnlockErr] = React.useState(null);
|
|
1831
1850
|
const [unlocking, setUnlocking] = React.useState(false);
|
|
1832
1851
|
const [showForgotGuide, setShowForgotGuide] = React.useState(false);
|
|
1833
|
-
const
|
|
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
|
+
}
|
|
1834
1890
|
const enriched = {
|
|
1835
1891
|
...payload,
|
|
1836
|
-
...
|
|
1892
|
+
...token ? { adminToken: token } : {},
|
|
1837
1893
|
...isLocalhost ? { isLocalhost: true } : {}
|
|
1838
1894
|
};
|
|
1839
|
-
|
|
1840
|
-
|
|
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]);
|
|
1841
1905
|
const handleUnlockAdmin = React.useCallback(async (e) => {
|
|
1842
1906
|
e?.preventDefault?.();
|
|
1843
1907
|
setUnlocking(true);
|
|
@@ -1848,6 +1912,8 @@ function BridgePanel({ rpcCall }) {
|
|
|
1848
1912
|
setAdminToken(res.value?.adminToken || "");
|
|
1849
1913
|
setAdminUnlocked(true);
|
|
1850
1914
|
setUnlockPassword("");
|
|
1915
|
+
setShowUnlockModal(false);
|
|
1916
|
+
setErr(null);
|
|
1851
1917
|
} else {
|
|
1852
1918
|
setUnlockErr(res?.error?.message || "\u7BA1\u7406\u5458\u5BC6\u7801\u9519\u8BEF");
|
|
1853
1919
|
}
|
|
@@ -2204,12 +2270,38 @@ function BridgePanel({ rpcCall }) {
|
|
|
2204
2270
|
)
|
|
2205
2271
|
);
|
|
2206
2272
|
}
|
|
2273
|
+
const isInterceptionErr = err && (err.includes("\u7BA1\u7406\u5458\u6743\u9650") || err.includes("\u7BA1\u7406\u5BC6\u7801\u89E3\u9501"));
|
|
2207
2274
|
return React.createElement(
|
|
2208
2275
|
"div",
|
|
2209
|
-
{ style: { maxWidth: 620 } },
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
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
|
+
// 管理员解锁状态提示条
|
|
2213
2305
|
!isLocalhost && adminUnlocked && React.createElement(
|
|
2214
2306
|
"div",
|
|
2215
2307
|
{
|
|
@@ -2232,9 +2324,122 @@ function BridgePanel({ rpcCall }) {
|
|
|
2232
2324
|
onClick: handleLockAdmin
|
|
2233
2325
|
}, "\u{1F512} \u91CD\u65B0\u9501\u5B9A\u540E\u53F0")
|
|
2234
2326
|
),
|
|
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
|
+
),
|
|
2235
2351
|
React.createElement(VersionBanner, { rpcCall: authRpcCall }),
|
|
2236
2352
|
React.createElement(TabBar, { active: activeTab, onChange: setActiveTab, dots }),
|
|
2237
|
-
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
|
+
)
|
|
2238
2443
|
);
|
|
2239
2444
|
}
|
|
2240
2445
|
function apply(ctx) {
|
package/client/index.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
// dsh-bridge 客户端插件:设置页「远程访问」面板
|
|
2
2
|
|
|
3
|
+
// 兼容非 HTTPS 环境(如手机局域网 HTTP 访问):为非安全上下文补齐 crypto.randomUUID
|
|
4
|
+
if (typeof window !== 'undefined') {
|
|
5
|
+
if (!window.crypto) {
|
|
6
|
+
window.crypto = {};
|
|
7
|
+
}
|
|
8
|
+
if (!window.crypto.randomUUID) {
|
|
9
|
+
window.crypto.randomUUID = function() {
|
|
10
|
+
if (typeof window.crypto.getRandomValues === 'function') {
|
|
11
|
+
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c) {
|
|
12
|
+
return (c ^ window.crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> (c / 4)).toString(16);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
16
|
+
var r = (Math.random() * 16) | 0;
|
|
17
|
+
var v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
18
|
+
return v.toString(16);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
3
24
|
import { BRIDGE_RPC_CHANNEL, BRIDGE_ENDPOINTS } from '../lib/bridge-rpc-constants.js';
|
|
4
25
|
|
|
5
26
|
const GITHUB_URL = 'https://github.com/wenbin-wb/dsh-bridge';
|
|
@@ -1624,15 +1645,63 @@ function BridgePanel({ rpcCall }) {
|
|
|
1624
1645
|
const [unlockErr, setUnlockErr] = React.useState(null);
|
|
1625
1646
|
const [unlocking, setUnlocking] = React.useState(false);
|
|
1626
1647
|
const [showForgotGuide, setShowForgotGuide] = React.useState(false);
|
|
1648
|
+
const [showUnlockModal, setShowUnlockModal] = React.useState(false);
|
|
1649
|
+
|
|
1650
|
+
// 本机物理访问自动静默获取 adminToken,免输密码直通管理(支持 3080 原生端口与 3082 代理端口)
|
|
1651
|
+
const fetchLoopbackToken = React.useCallback(async () => {
|
|
1652
|
+
if (!isLocalhost) return null;
|
|
1653
|
+
const currentPort = typeof window !== 'undefined' ? (window.location.port || (window.location.protocol === 'https:' ? '443' : '80')) : '3082';
|
|
1654
|
+
const proxyPort = status?.proxy?.port || 3082;
|
|
1655
|
+
const candidateUrls = [
|
|
1656
|
+
'/__dsh_bridge__/loopback-token',
|
|
1657
|
+
`http://127.0.0.1:${proxyPort}/__dsh_bridge__/loopback-token`,
|
|
1658
|
+
`http://localhost:${proxyPort}/__dsh_bridge__/loopback-token`,
|
|
1659
|
+
'http://127.0.0.1:3082/__dsh_bridge__/loopback-token',
|
|
1660
|
+
];
|
|
1661
|
+
const uniqueUrls = [...new Set(candidateUrls)];
|
|
1662
|
+
|
|
1663
|
+
for (const url of uniqueUrls) {
|
|
1664
|
+
try {
|
|
1665
|
+
const res = await fetch(url, { method: 'POST' });
|
|
1666
|
+
if (res.ok) {
|
|
1667
|
+
const data = await res.json();
|
|
1668
|
+
if (data?.ok && data.adminToken) {
|
|
1669
|
+
setAdminToken(data.adminToken);
|
|
1670
|
+
setAdminUnlocked(true);
|
|
1671
|
+
return data.adminToken;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
} catch {}
|
|
1675
|
+
}
|
|
1676
|
+
return null;
|
|
1677
|
+
}, [isLocalhost, status?.proxy?.port]);
|
|
1678
|
+
|
|
1679
|
+
React.useEffect(() => {
|
|
1680
|
+
if (isLocalhost && !adminUnlocked) {
|
|
1681
|
+
fetchLoopbackToken();
|
|
1682
|
+
}
|
|
1683
|
+
}, [isLocalhost, adminUnlocked, fetchLoopbackToken]);
|
|
1627
1684
|
|
|
1628
|
-
const authRpcCall = React.useCallback((endpoint, payload = {}, signal) => {
|
|
1685
|
+
const authRpcCall = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
1686
|
+
let token = adminToken;
|
|
1687
|
+
if (isLocalhost && !token) {
|
|
1688
|
+
token = await fetchLoopbackToken();
|
|
1689
|
+
}
|
|
1629
1690
|
const enriched = {
|
|
1630
1691
|
...payload,
|
|
1631
|
-
...(
|
|
1692
|
+
...(token ? { adminToken: token } : {}),
|
|
1632
1693
|
...(isLocalhost ? { isLocalhost: true } : {}),
|
|
1633
1694
|
};
|
|
1634
|
-
|
|
1635
|
-
|
|
1695
|
+
const res = await rpcCall(endpoint, enriched, signal);
|
|
1696
|
+
if (res?.ok === false) {
|
|
1697
|
+
const msg = res?.error?.message || '';
|
|
1698
|
+
if (msg.includes('管理员权限') || msg.includes('管理密码解锁')) {
|
|
1699
|
+
setUnlockErr(msg);
|
|
1700
|
+
setShowUnlockModal(true);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return res;
|
|
1704
|
+
}, [rpcCall, adminToken, isLocalhost, fetchLoopbackToken]);
|
|
1636
1705
|
|
|
1637
1706
|
const handleUnlockAdmin = React.useCallback(async (e) => {
|
|
1638
1707
|
e?.preventDefault?.();
|
|
@@ -1644,6 +1713,8 @@ function BridgePanel({ rpcCall }) {
|
|
|
1644
1713
|
setAdminToken(res.value?.adminToken || '');
|
|
1645
1714
|
setAdminUnlocked(true);
|
|
1646
1715
|
setUnlockPassword('');
|
|
1716
|
+
setShowUnlockModal(false);
|
|
1717
|
+
setErr(null);
|
|
1647
1718
|
} else {
|
|
1648
1719
|
setUnlockErr(res?.error?.message || '管理员密码错误');
|
|
1649
1720
|
}
|
|
@@ -1967,11 +2038,36 @@ function BridgePanel({ rpcCall }) {
|
|
|
1967
2038
|
);
|
|
1968
2039
|
}
|
|
1969
2040
|
|
|
1970
|
-
|
|
2041
|
+
const isInterceptionErr = err && (err.includes('管理员权限') || err.includes('管理密码解锁'));
|
|
2042
|
+
|
|
2043
|
+
return React.createElement('div', { style: { maxWidth: 620, position: 'relative' } },
|
|
2044
|
+
// 错误横幅(如果是权限拦截,直接提供醒目的输入密码解锁按钮)
|
|
1971
2045
|
err && React.createElement('div', {
|
|
1972
|
-
style: {
|
|
1973
|
-
|
|
2046
|
+
style: {
|
|
2047
|
+
...s.card,
|
|
2048
|
+
background: 'var(--dsw-alias-state-error-bg,#fef2f2)',
|
|
2049
|
+
color: 'var(--dsw-alias-state-error-primary,#dc2626)',
|
|
2050
|
+
fontSize: 13,
|
|
2051
|
+
marginBottom: 16,
|
|
2052
|
+
display: 'flex',
|
|
2053
|
+
alignItems: 'center',
|
|
2054
|
+
justifyContent: 'space-between',
|
|
2055
|
+
flexWrap: 'wrap',
|
|
2056
|
+
gap: 10,
|
|
2057
|
+
},
|
|
2058
|
+
},
|
|
2059
|
+
React.createElement('span', { style: { flex: '1 1 auto' } }, err),
|
|
2060
|
+
isInterceptionErr && React.createElement('button', {
|
|
2061
|
+
type: 'button',
|
|
2062
|
+
style: { ...s.btnPri, background: '#dc2626', color: '#ffffff', height: 26, fontSize: 12, padding: '0 10px', flexShrink: 0 },
|
|
2063
|
+
onClick: () => {
|
|
2064
|
+
setUnlockErr(err);
|
|
2065
|
+
setShowUnlockModal(true);
|
|
2066
|
+
},
|
|
2067
|
+
}, '🔑 立即输入管理密码解锁'),
|
|
2068
|
+
),
|
|
1974
2069
|
|
|
2070
|
+
// 管理员解锁状态提示条
|
|
1975
2071
|
!isLocalhost && adminUnlocked && React.createElement('div', {
|
|
1976
2072
|
style: {
|
|
1977
2073
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
@@ -1987,11 +2083,90 @@ function BridgePanel({ rpcCall }) {
|
|
|
1987
2083
|
}, '🔒 重新锁定后台'),
|
|
1988
2084
|
),
|
|
1989
2085
|
|
|
2086
|
+
// 未解锁时的顶部引导条
|
|
2087
|
+
!isLocalhost && !adminUnlocked && auth?.enabled && policy !== 'open' && React.createElement('div', {
|
|
2088
|
+
style: {
|
|
2089
|
+
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
2090
|
+
padding: '8px 14px', background: 'var(--dsw-alias-state-warn-bg,#fffbeb)',
|
|
2091
|
+
border: '1px solid var(--dsw-alias-state-warn-border,#fde68a)', borderRadius: 8,
|
|
2092
|
+
marginBottom: 14, fontSize: 12, color: 'var(--dsw-alias-state-warn-primary,#92400e)',
|
|
2093
|
+
},
|
|
2094
|
+
},
|
|
2095
|
+
React.createElement('span', null, '🔒 后台管理权限未解锁(修改敏感配置需先解锁)'),
|
|
2096
|
+
React.createElement('button', {
|
|
2097
|
+
type: 'button',
|
|
2098
|
+
style: { ...s.btnPri, height: 24, fontSize: 11, padding: '0 10px', background: '#d97706' },
|
|
2099
|
+
onClick: () => setShowUnlockModal(true),
|
|
2100
|
+
}, '🔑 解锁管理权限'),
|
|
2101
|
+
),
|
|
2102
|
+
|
|
1990
2103
|
React.createElement(VersionBanner, { rpcCall: authRpcCall }),
|
|
1991
2104
|
|
|
1992
2105
|
React.createElement(TabBar, { active: activeTab, onChange: setActiveTab, dots }),
|
|
1993
2106
|
|
|
1994
2107
|
tabContent,
|
|
2108
|
+
|
|
2109
|
+
// 全局交互式解锁弹窗 Modal
|
|
2110
|
+
showUnlockModal && React.createElement('div', {
|
|
2111
|
+
style: {
|
|
2112
|
+
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 99999,
|
|
2113
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16,
|
|
2114
|
+
},
|
|
2115
|
+
onClick: (e) => { if (e.target === e.currentTarget) setShowUnlockModal(false); },
|
|
2116
|
+
},
|
|
2117
|
+
React.createElement('div', {
|
|
2118
|
+
style: {
|
|
2119
|
+
background: 'var(--dsw-alias-bg-layer-1,#ffffff)', borderRadius: 14,
|
|
2120
|
+
padding: '24px 24px', maxWidth: 420, width: '100%',
|
|
2121
|
+
boxShadow: '0 20px 25px -5px rgba(0,0,0,0.2)', border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
2122
|
+
},
|
|
2123
|
+
},
|
|
2124
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 } },
|
|
2125
|
+
React.createElement('div', { style: { fontSize: 16, fontWeight: 600, color: 'var(--dsw-alias-label-primary,currentColor)', display: 'flex', alignItems: 'center', gap: 8 } },
|
|
2126
|
+
'🔒 解锁后台管理权限'
|
|
2127
|
+
),
|
|
2128
|
+
React.createElement('button', {
|
|
2129
|
+
type: 'button',
|
|
2130
|
+
style: { border: 'none', background: 'none', cursor: 'pointer', fontSize: 18, color: 'var(--dsw-alias-label-tertiary,#9ca3af)', padding: 0 },
|
|
2131
|
+
onClick: () => setShowUnlockModal(false),
|
|
2132
|
+
}, '✕'),
|
|
2133
|
+
),
|
|
2134
|
+
React.createElement('div', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary,#4b5563)', marginBottom: 16, lineHeight: 1.5 } },
|
|
2135
|
+
'当前操作需要后台管理员权限。为保护您的网络配置与机器人平台安全,请输入管理密码解锁:'
|
|
2136
|
+
),
|
|
2137
|
+
React.createElement('form', {
|
|
2138
|
+
onSubmit: handleUnlockAdmin,
|
|
2139
|
+
style: { display: 'flex', flexDirection: 'column', gap: 12 },
|
|
2140
|
+
},
|
|
2141
|
+
React.createElement('input', {
|
|
2142
|
+
type: 'password',
|
|
2143
|
+
style: s.input,
|
|
2144
|
+
placeholder: '请输入后台管理密码',
|
|
2145
|
+
value: unlockPassword,
|
|
2146
|
+
onChange: (e) => setUnlockPassword(e.target.value),
|
|
2147
|
+
autoFocus: true,
|
|
2148
|
+
}),
|
|
2149
|
+
unlockErr && React.createElement('div', {
|
|
2150
|
+
style: { fontSize: 12, color: 'var(--dsw-alias-state-error-primary,#dc2626)' },
|
|
2151
|
+
}, unlockErr),
|
|
2152
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 4 } },
|
|
2153
|
+
React.createElement('button', {
|
|
2154
|
+
type: 'button',
|
|
2155
|
+
style: s.btnGhost,
|
|
2156
|
+
onClick: () => setShowUnlockModal(false),
|
|
2157
|
+
}, '取消'),
|
|
2158
|
+
React.createElement('button', {
|
|
2159
|
+
type: 'submit',
|
|
2160
|
+
style: { ...s.btnPri, background: '#4f6ef7', color: '#fff' },
|
|
2161
|
+
disabled: unlocking || !unlockPassword,
|
|
2162
|
+
}, unlocking ? '验证中…' : '立即解锁'),
|
|
2163
|
+
),
|
|
2164
|
+
),
|
|
2165
|
+
React.createElement('div', { 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 } },
|
|
2166
|
+
'💡 提示:若未单独配置管理密码,请输入初次设置的访问密码;电脑本机(127.0.0.1)访问享有免密管理特权。'
|
|
2167
|
+
),
|
|
2168
|
+
),
|
|
2169
|
+
),
|
|
1995
2170
|
);
|
|
1996
2171
|
}
|
|
1997
2172
|
|
package/docs/banner.jpg
CHANGED
|
Binary file
|
package/docs/feishu-usage.md
CHANGED
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
3. 点击 **「保存并连接」**;
|
|
70
70
|
4. 状态显示为绿色 **「已连接」** 即表示长连接成功建立!
|
|
71
71
|
|
|
72
|
+

|
|
73
|
+
|
|
72
74
|
---
|
|
73
75
|
|
|
74
76
|
## 💬 常用操作与指令
|
|
@@ -91,3 +93,6 @@
|
|
|
91
93
|
当 Agent 尝试执行需授权的操作(如终端命令、写敏感文件)时,飞书端会自动推送 **原生交互卡片**:
|
|
92
94
|
- 点击卡片上的 **「✓ 批准执行」** 或 **「✕ 拒绝执行」** 按钮即可一键处理;
|
|
93
95
|
- 亦可直接回复文字 `/yes` (或 `1`) / `/no` (或 `2`)。
|
|
96
|
+
|
|
97
|
+

|
|
98
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/telegram-usage.md
CHANGED
package/lib/feishu/gateway.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import fs from 'node:fs'
|
|
6
6
|
import path from 'node:path'
|
|
7
7
|
import { Service } from '@deepseek-ai/cordis'
|
|
8
|
-
import
|
|
8
|
+
import LarkSdk from './lark-bundled.mjs'
|
|
9
|
+
const Lark = LarkSdk.default || LarkSdk
|
|
9
10
|
|
|
10
11
|
export class FeishuGateway extends Service {
|
|
11
12
|
static name = 'feishu'
|