@qzsy/dsh-bridge 0.0.0-stage → 2.18.0
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 +1107 -0
- package/LICENSE +21 -0
- package/README.en.md +488 -0
- package/README.md +509 -2
- package/client/build.mjs +43 -0
- package/client/client.js +9864 -0
- package/client/host-families.js +442 -0
- package/client/index.js +6883 -0
- package/client/locales.mjs +242 -0
- package/client/mobile-styles.js +1299 -0
- package/client/mobile-yield.js +219 -0
- package/client/panel-locales.mjs +777 -0
- package/client/picker-yield.js +77 -0
- package/client/resource-url-compat.js +90 -0
- package/client/settings-nav-icon.js +105 -0
- package/client/theme-tokens.js +49 -0
- package/client/tunnel-guard.js +46 -0
- package/client/unlock-manager.js +144 -0
- package/cordis.patch.yml +4 -0
- package/lib/auth/dsh-native-cookie.js +148 -0
- package/lib/auth/login-template.js +516 -0
- package/lib/auth/manager.js +596 -0
- package/lib/bridge-rpc-constants.js +66 -0
- package/lib/bridge-rpc.js +722 -0
- package/lib/cloudflared-manager.mjs +1277 -0
- package/lib/cloudflared-network-args.js +99 -0
- package/lib/cloudflared-process-scan.js +153 -0
- package/lib/compat.js +172 -0
- package/lib/connection-compat.js +115 -0
- package/lib/feishu/comment.js +50 -0
- package/lib/feishu/gateway.js +644 -0
- package/lib/feishu/index.js +432 -0
- package/lib/feishu/lark-bundled.mjs +131929 -0
- package/lib/feishu/markdown.js +148 -0
- package/lib/feishu/node.js +1152 -0
- package/lib/feishu/slash-commands.js +255 -0
- package/lib/index.js +3554 -0
- package/lib/platform/account-ops.js +248 -0
- package/lib/platform/activity.js +169 -0
- package/lib/platform/ask-user.js +293 -0
- package/lib/platform/base.js +147 -0
- package/lib/platform/changes-digest.js +93 -0
- package/lib/platform/commands.js +267 -0
- package/lib/platform/config-restore.js +78 -0
- package/lib/platform/conversation-bridge.js +1544 -0
- package/lib/platform/dsh-storage.js +222 -0
- package/lib/platform/index.js +10 -0
- package/lib/platform/instances.js +458 -0
- package/lib/platform/manager.js +130 -0
- package/lib/platform/message-split.js +229 -0
- package/lib/platform/session-catalog.js +405 -0
- package/lib/platform/session-config.js +52 -0
- package/lib/platform/stream-slices.js +21 -0
- package/lib/qq/gateway.js +1107 -0
- package/lib/qq/index.js +561 -0
- package/lib/qq/node.js +1153 -0
- package/lib/qq/qqbot-bundled.mjs +8273 -0
- package/lib/restart-helper.mjs +133 -0
- package/lib/saas/device-auth.mjs +366 -0
- package/lib/saas/tunnel-client.mjs +695 -0
- package/lib/security/log-sanitize.js +50 -0
- package/lib/security/path-validator.js +195 -0
- package/lib/security/rate-limiter.js +93 -0
- package/lib/session-strip.js +100 -0
- package/lib/sse-keepalive.js +115 -0
- package/lib/telegram/gateway.js +643 -0
- package/lib/telegram/index.js +255 -0
- package/lib/telegram/node.js +410 -0
- package/lib/tunnel-client.mjs +537 -0
- package/lib/wechat/gateway.js +1000 -0
- package/lib/wechat/index.js +282 -0
- package/lib/wechat/media.js +302 -0
- package/lib/wechat/node.js +360 -0
- package/llms.txt +36 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +135 -4
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// dsh-bridge 客户端国际化:跟随 DSH 主体语言(LocaleFace)。
|
|
2
|
+
//
|
|
3
|
+
// DSH 0.1.7 的 client 提供 locale 服务(`dsh-client-locale` 的 LocaleFace):
|
|
4
|
+
// - ctx.locale.register(namespace, { zh: {...}, en: {...} }) 注册词典;
|
|
5
|
+
// - ctx.locale.translate(map) 按当前语言取译文(纯字符串原样返回、map 走词典,
|
|
6
|
+
// 未声明语言回退到 fallback 链,最终到 en);
|
|
7
|
+
// - ctx.locale.subscribe(fn) 在语言切换时通知。
|
|
8
|
+
// 插件把 "locale" 加进 inject 后即可拿到 ctx.locale,从而自动跟随 DSH 主体语言
|
|
9
|
+
// (含用户手动切换、浏览器语言回退、<html lang> 同步),无需自建语言开关。
|
|
10
|
+
//
|
|
11
|
+
// 本文件有两类词条,分属两层:
|
|
12
|
+
// ① 语义化键词典(下面的 `zh` / `en`):移动端自绘 UI、DOM 注入、aria/title 等
|
|
13
|
+
// 「拿不到原文上下文」的调用点,用 localeT('menu.open') 这类键;
|
|
14
|
+
// ② 「中文原文 → 英文」渲染层词典(panel-locales.mjs 的 panelEn / serverEn):
|
|
15
|
+
// client/index.js 的 h() 包装器在渲染时按原文精确命中翻译,动态拼接与服务端
|
|
16
|
+
// 运行时文案另有 `${var}` 占位符与正则模式。
|
|
17
|
+
// 内部字符串(DOM/CSS 类名匹配、注释、日志)保持中文原样——它们不是 UI 文案,
|
|
18
|
+
// 翻译会破坏匹配(见 readHostSessionTitle 教训);这类字面量由
|
|
19
|
+
// test/panel-i18n.test.mjs 的内部白名单显式登记。
|
|
20
|
+
|
|
21
|
+
import { panelEn, serverEn, serverPatterns } from './panel-locales.mjs';
|
|
22
|
+
|
|
23
|
+
export const LOCALE_NAMESPACE = '@wenbin_wb/dsh-bridge';
|
|
24
|
+
|
|
25
|
+
/** 词典:每个命名空间至少要有 en(最终 fallback);zh 为默认语言。 */
|
|
26
|
+
export const zh = {
|
|
27
|
+
// 移动端顶栏
|
|
28
|
+
'menu.open': '打开菜单',
|
|
29
|
+
'menu.newSession': '新建会话',
|
|
30
|
+
'title.fallback': '新会话',
|
|
31
|
+
// 移动端输入区
|
|
32
|
+
'composer.placeholder': '✍️ 点击输入消息…',
|
|
33
|
+
'composer.expand': '展开输入框',
|
|
34
|
+
'composer.collapse': '收起输入框,最大化对话阅读区',
|
|
35
|
+
'panel.backToChat': '返回对话',
|
|
36
|
+
// 宿主浮层(智能体团队 / 子智能体下拉)在移动端由本插件统一补收起按钮:动作文案,兼作 aria-label
|
|
37
|
+
'popover.collapse': '收起',
|
|
38
|
+
// 移动端设置面板 / 抽屉(dsh-bridge 自绘部分)
|
|
39
|
+
'workspace.choose': '选择电脑工作区',
|
|
40
|
+
'workspace.reading': '正在读取目录内容…',
|
|
41
|
+
'workspace.currentDir': '当前目录:',
|
|
42
|
+
'workspace.registeredHint': '已注册工作区 (${count} 个,点击直接切换):',
|
|
43
|
+
'workspace.enterHint': '点击进入文件夹,或点击「+ 选为工作区」直接添加并切换',
|
|
44
|
+
'workspace.unlockHint': '远程访问时浏览/添加工作区需输入后台管理密码解锁(与访问密码不同)。',
|
|
45
|
+
'workspace.chooseFolder': '选择工作区',
|
|
46
|
+
'workspace.addFolder': '添加',
|
|
47
|
+
'workspace.refresh': '刷新目录',
|
|
48
|
+
'workspace.back': '返回上一级',
|
|
49
|
+
'workspace.filter': '过滤子文件夹…',
|
|
50
|
+
'workspace.more': '还有更多',
|
|
51
|
+
'workspace.addSubfolder': '直接添加此子文件夹为工作区并进入',
|
|
52
|
+
'workspace.onlyAdmin': '仅限本机管理',
|
|
53
|
+
'workspace.isCurrent': '当前主入口',
|
|
54
|
+
'workspace.isFallback': '主入口',
|
|
55
|
+
// 远程访问面板
|
|
56
|
+
'access.copyLink': '复制链接',
|
|
57
|
+
'access.copied': '✓ 已复制',
|
|
58
|
+
'access.hideQr': '隐藏二维码',
|
|
59
|
+
'access.showQr': '显示二维码',
|
|
60
|
+
'access.privateEnv': '请在私密环境下使用',
|
|
61
|
+
'access.switching': '切换中…',
|
|
62
|
+
'access.publicEntry': '公网访问入口',
|
|
63
|
+
'access.copy': '复制',
|
|
64
|
+
'access.stopReconnect': '停止重连',
|
|
65
|
+
'access.close': '关闭',
|
|
66
|
+
'access.connecting': '连接中…',
|
|
67
|
+
'access.downloading': '下载中…',
|
|
68
|
+
'access.enable': '开启公网隧道',
|
|
69
|
+
'access.scanHint': '请在私密环境下扫码使用',
|
|
70
|
+
'access.autoStart': '随 DSH 启动自动开启',
|
|
71
|
+
'access.tunnelConfig': '隧道配置',
|
|
72
|
+
'access.expand': '展开 ▾',
|
|
73
|
+
'access.collapse': '收起 ▴',
|
|
74
|
+
'access.saveError': '保存配置失败',
|
|
75
|
+
'access.serverConfig': '隧道服务器配置',
|
|
76
|
+
'access.saving': '保存中…',
|
|
77
|
+
'access.saved': '✓ 已保存',
|
|
78
|
+
'access.saveConfig': '保存配置',
|
|
79
|
+
'access.clear': '清除',
|
|
80
|
+
'access.externalTunnel': '外部已部署隧道',
|
|
81
|
+
'access.saveFixed': '保存固定域名配置',
|
|
82
|
+
'access.updateFailed': '更新失败',
|
|
83
|
+
'access.securityHint': '设置外部访客访问密码',
|
|
84
|
+
'access.goSetup': '去开启 ➔',
|
|
85
|
+
'access.gotoSettings': '设置 ➔',
|
|
86
|
+
'access.tutorial': '查看自建隧道服务器搭建教程',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const en = {
|
|
90
|
+
'menu.open': 'Open menu',
|
|
91
|
+
'menu.newSession': 'New session',
|
|
92
|
+
'title.fallback': 'New Session',
|
|
93
|
+
'composer.placeholder': '✍️ Tap to type a message…',
|
|
94
|
+
'composer.expand': 'Expand input',
|
|
95
|
+
'composer.collapse': 'Collapse input to maximize chat view',
|
|
96
|
+
'panel.backToChat': 'Back to chat',
|
|
97
|
+
// Host popovers (Agent Team / subagent dropdown) get a collapse control from this plugin on mobile
|
|
98
|
+
'popover.collapse': 'Collapse',
|
|
99
|
+
'workspace.choose': 'Choose a workspace',
|
|
100
|
+
'workspace.reading': 'Reading directory…',
|
|
101
|
+
'workspace.currentDir': 'Current directory:',
|
|
102
|
+
'workspace.registeredHint': 'Registered workspaces (${count}, click to switch):',
|
|
103
|
+
'workspace.enterHint': 'Enter a folder, or tap “+ Select as workspace” to add & switch',
|
|
104
|
+
'workspace.unlockHint': 'Browsing/adding workspaces remotely requires the admin password to unlock (different from the access password).',
|
|
105
|
+
'workspace.chooseFolder': 'Choose workspace',
|
|
106
|
+
'workspace.addFolder': 'Add',
|
|
107
|
+
'workspace.refresh': 'Refresh',
|
|
108
|
+
'workspace.back': 'Back',
|
|
109
|
+
'workspace.filter': 'Filter subfolders…',
|
|
110
|
+
'workspace.more': 'More',
|
|
111
|
+
'workspace.addSubfolder': 'Add this subfolder as a workspace and enter',
|
|
112
|
+
'workspace.onlyAdmin': 'Local admin only',
|
|
113
|
+
'workspace.isCurrent': 'Current entry',
|
|
114
|
+
'workspace.isFallback': 'Main entry',
|
|
115
|
+
'access.copyLink': 'Copy link',
|
|
116
|
+
'access.copied': '✓ Copied',
|
|
117
|
+
'access.hideQr': 'Hide QR',
|
|
118
|
+
'access.showQr': 'Show QR',
|
|
119
|
+
'access.privateEnv': 'Use in a private environment',
|
|
120
|
+
'access.switching': 'Switching…',
|
|
121
|
+
'access.publicEntry': 'Public access entry',
|
|
122
|
+
'access.copy': 'Copy',
|
|
123
|
+
'access.stopReconnect': 'Stop reconnecting',
|
|
124
|
+
'access.close': 'Close',
|
|
125
|
+
'access.connecting': 'Connecting…',
|
|
126
|
+
'access.downloading': 'Downloading…',
|
|
127
|
+
'access.enable': 'Enable public tunnel',
|
|
128
|
+
'access.scanHint': 'Scan in a private environment',
|
|
129
|
+
'access.autoStart': 'Start automatically with DSH',
|
|
130
|
+
'access.tunnelConfig': 'Tunnel configuration',
|
|
131
|
+
'access.expand': 'Expand ▾',
|
|
132
|
+
'access.collapse': 'Collapse ▴',
|
|
133
|
+
'access.saveError': 'Failed to save configuration',
|
|
134
|
+
'access.serverConfig': 'Tunnel server configuration',
|
|
135
|
+
'access.saving': 'Saving…',
|
|
136
|
+
'access.saved': '✓ Saved',
|
|
137
|
+
'access.saveConfig': 'Save configuration',
|
|
138
|
+
'access.clear': 'Clear',
|
|
139
|
+
'access.externalTunnel': 'External tunnel',
|
|
140
|
+
'access.saveFixed': 'Save fixed-domain configuration',
|
|
141
|
+
'access.updateFailed': 'Update failed',
|
|
142
|
+
'access.securityHint': 'Set an external visitor access password',
|
|
143
|
+
'access.goSetup': 'Set up ➔',
|
|
144
|
+
'access.gotoSettings': 'Settings ➔',
|
|
145
|
+
'access.tutorial': 'View the self-hosted tunnel server tutorial',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 构造一个翻译函数:优先用宿主的 ctx.locale.translate(跟随主体语言),
|
|
150
|
+
* 宿主不支持时退化为「取当前语言词典(en/zh)+ 互为兜底」。
|
|
151
|
+
* @param {object} ctx - Cordis 客户端上下文(含可选 locale 服务)
|
|
152
|
+
* @returns {{ t: Function, active: () => string, subscribe: Function }}
|
|
153
|
+
*/
|
|
154
|
+
/**
|
|
155
|
+
* 构造翻译器:读 DSH 主体语言(`<html lang>`,宿主 LocaleFace 实时同步),
|
|
156
|
+
* 中英词典按当前语言选词。`ctx.locale` 只作可选的词典注册(拿到则注册,
|
|
157
|
+
* 拿不到或未声明 inject 也不抛错、不影响翻译——动态插件无法把 locale 加进
|
|
158
|
+
* inject,宿主会对未声明服务抛 denyRead,详见 index.js 注释)。
|
|
159
|
+
* @param {object} ctx - Cordis 客户端上下文(可选)
|
|
160
|
+
* @returns {{ t: Function, active: () => string }}
|
|
161
|
+
*/
|
|
162
|
+
export function createTranslator(ctx) {
|
|
163
|
+
// 当前语言:宿主把 active 语言同步到 <html lang>(zh→zh-CN,其余原样)。
|
|
164
|
+
// 读它即可跟随 DSH 主体语言且无需声明 locale 服务。
|
|
165
|
+
// 读 <html lang>。document 可能不存在(Node 测试)或只有残缺 stub(宿主外壳/测试
|
|
166
|
+
// 替身没有 documentElement.getAttribute)——整体容错:翻译函数在渲染/初始化链路上
|
|
167
|
+
// 被高频调用,任何一个残缺 document 都不该把面板初始化打断。
|
|
168
|
+
const readLang = () => {
|
|
169
|
+
try {
|
|
170
|
+
const root = typeof document !== 'undefined' ? document.documentElement : null;
|
|
171
|
+
return (root && typeof root.getAttribute === 'function' ? root.getAttribute('lang') : '') || '';
|
|
172
|
+
} catch {
|
|
173
|
+
return '';
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const active = () => (readLang().toLowerCase().startsWith('zh') ? 'zh' : 'en');
|
|
177
|
+
// 词典查询必须用「自有属性」判定:词典是普通对象,直接 dict[key] 会让
|
|
178
|
+
// 'constructor' / 'toString' / '__proto__' 这类原型属性名返回函数或对象。
|
|
179
|
+
// 渲染层 h() 会把**所有**字符串子节点送进来(含用户数据、路径、会话标题),
|
|
180
|
+
// 一旦命中就会把函数当 React child 渲染(React 直接抛错)。见
|
|
181
|
+
// test/panel-i18n.test.mjs 的「h() 包装器」用例。
|
|
182
|
+
const own = (dict, key) => (typeof key === 'string' && Object.hasOwn(dict, key) ? dict[key] : undefined);
|
|
183
|
+
// 先查语义化键词典(locales.mjs),未命中再查「中文原文 → 译文」词典
|
|
184
|
+
// (panel-locales.mjs,渲染层兜底翻译用)。中文模式下 panel 词典不生效——
|
|
185
|
+
// 中文就是原文本身(键),直接返回 undefined 让 t() 回退键。
|
|
186
|
+
const lookup = (key, lang, vars, depth = 0) => {
|
|
187
|
+
if (typeof key !== 'string') return undefined;
|
|
188
|
+
// `$1t` 允许捕获组再翻译一次;深度设上限,防止超长嵌套前缀把调用栈打爆
|
|
189
|
+
if (depth > 8) return undefined;
|
|
190
|
+
const semantic = lang === 'zh' ? own(zh, key) : (own(en, key) ?? own(zh, key));
|
|
191
|
+
if (semantic !== undefined) return semantic;
|
|
192
|
+
if (lang === 'zh') return undefined;
|
|
193
|
+
const panel = own(panelEn, key);
|
|
194
|
+
if (panel !== undefined) {
|
|
195
|
+
if (Array.isArray(panel)) return vars && vars.count === 1 ? panel[0] : panel[1];
|
|
196
|
+
return panel;
|
|
197
|
+
}
|
|
198
|
+
// 服务端运行时文案(RPC 错误 / 隧道状态 / 网络诊断):精确匹配优先,其次正则模式。
|
|
199
|
+
const server = own(serverEn, key);
|
|
200
|
+
if (server !== undefined) return server;
|
|
201
|
+
for (const [pattern, english] of serverPatterns) {
|
|
202
|
+
const matched = pattern.exec(key);
|
|
203
|
+
if (!matched) continue;
|
|
204
|
+
// `$1` 原样插入捕获组;`$1t` 表示「该捕获组本身也要过一遍词典」
|
|
205
|
+
// (服务端常把中文原因/状态嵌在英文模板里,如『连接异常: 连接超时 (3.5s)』)。
|
|
206
|
+
return english.replace(/\$(\d)(t?)/g, (_, group, translate) => {
|
|
207
|
+
const captured = matched[Number(group)] ?? '';
|
|
208
|
+
return translate ? (lookup(captured, 'en', undefined, depth + 1) ?? captured) : captured;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
};
|
|
213
|
+
const t = (key, vars) => {
|
|
214
|
+
let text = lookup(key, active(), vars);
|
|
215
|
+
// 英文缺失译文时回退原文(宁可见中文,也不显示键名/空白)
|
|
216
|
+
if (text === undefined) text = key;
|
|
217
|
+
if (vars && text) {
|
|
218
|
+
for (const [k, v] of Object.entries(vars)) text = text.replaceAll(`\${${k}}`, String(v));
|
|
219
|
+
}
|
|
220
|
+
return text;
|
|
221
|
+
};
|
|
222
|
+
// 可选:向宿主注册词典(仅当其有 register 且可访问时;失败无妨)
|
|
223
|
+
try {
|
|
224
|
+
const locale = ctx && (typeof ctx.get === 'function' ? ctx.get('locale') : ctx.locale);
|
|
225
|
+
if (locale && typeof locale.register === 'function') {
|
|
226
|
+
locale.register(LOCALE_NAMESPACE, { zh, en });
|
|
227
|
+
}
|
|
228
|
+
} catch { /* 动态插件的 locale 服务被宿主门控时忽略 */ }
|
|
229
|
+
return { t, active };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const globalTranslator = createTranslator(null);
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 无 ctx 的全局翻译函数:给「不在 React 渲染层、也拿不到 ctx」的模块用
|
|
236
|
+
* (例:client/unlock-manager.js 的错误文案、client/settings-nav-icon.js 的兜底标签)。
|
|
237
|
+
* 语言每次调用实时读 <html lang>,与面板翻译器同源。
|
|
238
|
+
* @param {string} key 语义化键或中文原文
|
|
239
|
+
* @param {Record<string, unknown>} [vars] 占位符/单复数变量
|
|
240
|
+
* @returns {string}
|
|
241
|
+
*/
|
|
242
|
+
export const t = (key, vars) => globalTranslator.t(key, vars);
|