@dshfly/remote-connector 0.2.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/LICENSE +21 -0
- package/README.md +44 -0
- package/config.js +69 -0
- package/cordis.patch.yml +6 -0
- package/core/cloud.js +55 -0
- package/core/connector-core.js +910 -0
- package/core/dsh-web.js +556 -0
- package/core/keys.js +68 -0
- package/core/path-guard.js +60 -0
- package/core/pending-approvals.js +80 -0
- package/core/push-notifier.js +79 -0
- package/core/session-titles.js +85 -0
- package/core/tunnel.js +607 -0
- package/dist/client.js +556 -0
- package/dist/client.js.map +7 -0
- package/http-api.js +531 -0
- package/index.js +173 -0
- package/mobile-bridge/README.md +33 -0
- package/mobile-bridge/core/bridge-core.js +367 -0
- package/mobile-bridge/core/enumerate.js +44 -0
- package/mobile-bridge/core/files.js +431 -0
- package/mobile-bridge/core/roots.js +50 -0
- package/mobile-bridge/index.js +59 -0
- package/package.json +87 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DSH Fly contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @dshfly/remote-connector
|
|
2
|
+
|
|
3
|
+
DeepSeek Harness (DSH) 的 **cordis 插件**。它让手机 App(DSH Fly)能随时随地**创建并监督**跑在你 PC 上的 DSH 开发任务:
|
|
4
|
+
|
|
5
|
+
- **出站反向连接**:PC 主动拨出到云端中继(绕开家庭 NAT/CGNAT)。
|
|
6
|
+
- **端到端加密 (E2EE) 终点**:解手机密文 → 明文走 loopback 转 `dsh web`;把下行事件加密回手机。
|
|
7
|
+
- **/dshfly 控制路由**(loopback-only):配对 QR、设备管理、二次确认。
|
|
8
|
+
- **移动端插件承载**:随包内置 `ctx.mobileBridge` 服务(移动端插件规范实现)。
|
|
9
|
+
|
|
10
|
+
数据面:手机 App ──E2EE──▶ 云端中继(只见密文)──E2EE──▶ 本插件──loopback──▶ `dsh web (127.0.0.1:3080)`。
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 零配置:relayUrl 内建默认 https://relay.dshfly.com + 账号自动生成
|
|
16
|
+
dsh plugin --profile web add @dshfly/remote-connector
|
|
17
|
+
dsh web # 重启生效
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
配对:打开 dsh web 设置页 → **DSH Fly** → 「新建配对」→ 手机扫码 → PC 端二次确认 → 完成。手机端随即可用(工作区/会话/审批/文件浏览……)。
|
|
21
|
+
|
|
22
|
+
## 要求
|
|
23
|
+
|
|
24
|
+
- DSH `>= 0.1.0-rc.6`,且 `dsh web` 绑定 **loopback**(`127.0.0.1`)——配对控制路由含一次性密钥,必须 loopback-only,插件会强制校验。
|
|
25
|
+
- Node `>= 22`。
|
|
26
|
+
|
|
27
|
+
## 安全
|
|
28
|
+
|
|
29
|
+
- **E2EE**:云端中继只路由密文,读不到会话/代码 diff;加密终点是手机与本插件。`dsh web` 恒绑 loopback,不暴露公网。
|
|
30
|
+
- 设备身份密钥存 `<dshHome>/dshfly/keys.json`(0600);配对一次性码 + 公钥指纹 + 二次确认。
|
|
31
|
+
- 插件是 fail-loud 宿主:配置缺失等可恢复情况会 `warn + 跳过启动`,绝不拖垮 dsh web。
|
|
32
|
+
|
|
33
|
+
## 配置(可选)
|
|
34
|
+
|
|
35
|
+
| 键 | 默认 | 说明 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `relayUrl` | `https://relay.dshfly.com` | 自建中继时覆盖 |
|
|
38
|
+
| `deviceName` | 主机名 | 显示在手机端 |
|
|
39
|
+
| `target` | `http://127.0.0.1:3080` | dsh web loopback 地址 |
|
|
40
|
+
| `autoConfirm` | `false` | 测试用:跳过二次确认(生产必须 false) |
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
[MIT](LICENSE)
|
package/config.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// packages/remote-connector/config.js
|
|
2
|
+
// 插件配置:schemastery schema(cordis loader 用它校验/规范化 apply 的 config,
|
|
3
|
+
// 以及 M3 设置页配置表单渲染)+ 默认值 + 业务校验。
|
|
4
|
+
//
|
|
5
|
+
// 注意(orbis 同款):DSH profile 的 schemastery 实现没有 .optional(),
|
|
6
|
+
// 字段默认可省略;required 语义留在 validateConfig(apply 里 warn+跳过,不抛错——
|
|
7
|
+
// 保证未配置时 dsh web 仍能正常启动)。
|
|
8
|
+
|
|
9
|
+
import z from '@deepseek-ai/schemastery';
|
|
10
|
+
|
|
11
|
+
export const CONFIG_DEFAULTS = {
|
|
12
|
+
// 零配置默认值:relayUrl 内建(单机自用场景);D1 无账号层——无 loginName。
|
|
13
|
+
relayUrl: 'https://relay.dshfly.com', // 云中继地址(可覆盖)
|
|
14
|
+
deviceName: '', // 设备名,默认 os.hostname()
|
|
15
|
+
keysPath: '', // 密钥文件路径,默认 <dshHome>/dshfly/keys.json(决策:不迁移旧 ~/.dshfly)
|
|
16
|
+
target: '', // DSH 上游,默认 http://127.0.0.1:3080(自环反代)
|
|
17
|
+
autoConfirm: false, // 测试 profile 用:配对轮询到 completed 时自动确认(生产必须 false)
|
|
18
|
+
profileDir: '', // 移动端插件 profile 目录(M4.x-c 整合:bridge 由本插件承载);空 = <dshHome>/profiles/web
|
|
19
|
+
profileName: 'web', // 同上:profile 名(真实安装即 ~/.dsh/profiles/web)
|
|
20
|
+
dshBin: '', // dsh CLI 路径(空 = 用 PATH 的 'dsh');一键安装插件用
|
|
21
|
+
recommendedPlugins: [ // 设置页"推荐插件"清单(白名单:一键安装只接受这里的 name)
|
|
22
|
+
{
|
|
23
|
+
name: '@dshfly/plugin-notifications', // 包名(loader.create 用)
|
|
24
|
+
spec: '@dshfly/plugin-notifications', // `dsh plugin add <spec>` 的参数(npm 名;开发期可配 file:<绝对路径>)
|
|
25
|
+
id: 'notifications',
|
|
26
|
+
title: '消息中心',
|
|
27
|
+
description: 'PC 端会话动态与待办',
|
|
28
|
+
icon: 'feather:bell',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** cordis loader 用的 schemastery schema(~standard.validate 由 cordis resolveConfig 调用)。 */
|
|
34
|
+
export const Config = z.object({
|
|
35
|
+
relayUrl: z.string(),
|
|
36
|
+
deviceName: z.string(),
|
|
37
|
+
keysPath: z.string(),
|
|
38
|
+
target: z.string(),
|
|
39
|
+
autoConfirm: z.boolean(),
|
|
40
|
+
profileDir: z.string(),
|
|
41
|
+
profileName: z.string(),
|
|
42
|
+
dshBin: z.string(),
|
|
43
|
+
recommendedPlugins: z.array(z.object({
|
|
44
|
+
name: z.string(),
|
|
45
|
+
spec: z.string(),
|
|
46
|
+
id: z.string(),
|
|
47
|
+
title: z.string(),
|
|
48
|
+
description: z.string(),
|
|
49
|
+
icon: z.string(),
|
|
50
|
+
})),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export function resolveConfig(config = {}) {
|
|
54
|
+
const merged = { ...CONFIG_DEFAULTS, ...(config || {}) };
|
|
55
|
+
// schemastery 的 ~standard.validate 会把缺失的数组字段规范化为 [](非 undefined),
|
|
56
|
+
// 从而覆盖 CONFIG_DEFAULTS 的默认清单 → 空数组回退默认(2026-08 实测发现)。
|
|
57
|
+
if (!Array.isArray(merged.recommendedPlugins) || merged.recommendedPlugins.length === 0) {
|
|
58
|
+
merged.recommendedPlugins = CONFIG_DEFAULTS.recommendedPlugins;
|
|
59
|
+
}
|
|
60
|
+
return merged;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function validateConfig(config) {
|
|
64
|
+
// relayUrl 有内建默认;仅当显式传了空串(覆盖默认)时告警。
|
|
65
|
+
// (core 用自动生成的设备账号兜底)。
|
|
66
|
+
const errors = [];
|
|
67
|
+
if (!config.relayUrl) errors.push('relayUrl 为空(云中继地址)');
|
|
68
|
+
return errors;
|
|
69
|
+
}
|
package/cordis.patch.yml
ADDED
package/core/cloud.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// packages/remote-connector/core/cloud.js
|
|
2
|
+
// connector 调云端控制面的 HTTP 客户端。自 services/connector/cloud.js 迁入(单一代码源)。
|
|
3
|
+
// D1 方案 B(无账号层):PC 登记纯公钥(无 token);device token 经 device-login 或登记响应获取。
|
|
4
|
+
|
|
5
|
+
export function createCloud(baseUrl) {
|
|
6
|
+
const base = baseUrl.replace(/\/$/, '');
|
|
7
|
+
async function call(method, path, { token, body } = {}) {
|
|
8
|
+
const res = await fetch(`${base}${path}`, {
|
|
9
|
+
method,
|
|
10
|
+
headers: {
|
|
11
|
+
'content-type': 'application/json',
|
|
12
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
13
|
+
},
|
|
14
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
15
|
+
});
|
|
16
|
+
const data = await res.json().catch(() => null);
|
|
17
|
+
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(data)}`);
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
/** D1:设备凭证(身份私钥签名换取 token;PC 未登记前登记响应已含 deviceToken,本方法供手机/换 token 用)。 */
|
|
22
|
+
deviceLogin(publicKey, ts, nonce, sig) {
|
|
23
|
+
return call('POST', '/api/v1/auth/device-login', { body: { publicKey, ts: String(ts), nonce, sig } });
|
|
24
|
+
},
|
|
25
|
+
/** D1:纯公钥登记(无鉴权,信任根 = 配对流程)。 */
|
|
26
|
+
registerDevice({ name, pk_identity, pk_transport }) {
|
|
27
|
+
return call('POST', '/api/v1/devices', { body: { name, pk_identity, pk_transport } });
|
|
28
|
+
},
|
|
29
|
+
createPairing(deviceToken) {
|
|
30
|
+
return call('POST', '/api/v1/pairings', { token: deviceToken });
|
|
31
|
+
},
|
|
32
|
+
/** B3/D2:用当前 deviceToken 换新 token(短 TTL 在线续期)。 */
|
|
33
|
+
rotateDeviceToken(deviceToken, deviceId) {
|
|
34
|
+
return call('POST', `/api/v1/devices/${deviceId}/rotate`, { token: deviceToken });
|
|
35
|
+
},
|
|
36
|
+
/** D1:getPairing 无鉴权(pairingId 一次性码即授权闸门;配对公钥非秘密)。 */
|
|
37
|
+
getPairing(id) {
|
|
38
|
+
return call('GET', `/api/v1/pairings/${id}`);
|
|
39
|
+
},
|
|
40
|
+
/** D1:complete 无 token(纯身份签名)。 */
|
|
41
|
+
completePairing(id, body) {
|
|
42
|
+
return call('POST', `/api/v1/pairings/${id}/complete`, { body });
|
|
43
|
+
},
|
|
44
|
+
confirmPairing(deviceToken, id, body) {
|
|
45
|
+
return call('POST', `/api/v1/pairings/${id}/confirm`, { token: deviceToken, body: body ?? {} });
|
|
46
|
+
},
|
|
47
|
+
rejectPairing(deviceToken, id, body) {
|
|
48
|
+
return call('POST', `/api/v1/pairings/${id}/reject`, { token: deviceToken, body: body ?? {} });
|
|
49
|
+
},
|
|
50
|
+
/** 推送:connector 触发 relay /push/notify(fire-and-forget,调用方吞错)。 */
|
|
51
|
+
notifyPush(deviceToken, body) {
|
|
52
|
+
return call('POST', '/api/v1/push/notify', { token: deviceToken, body });
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|