dsh-remote 0.8.25 → 0.8.26
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.md +1 -1
- package/README.zh.md +1 -1
- package/lib/telemetry.js +71 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](LICENSE)
|
|
11
11
|
[](https://github.com/topics/dsh-plugin)
|
|
12
12
|
|
|
13
|
-
Maintained by [@flymysql](https://github.com/flymysql) · [Blog](https://gitpull.cn) · [Discussions](https://github.com/flymysql/dsh-remote/discussions) · [Issues](https://github.com/flymysql/dsh-remote/issues) · [中文说明](./README.zh.md)
|
|
13
|
+
Maintained by [@flymysql](https://github.com/flymysql) · [Homepage](https://flymysql.github.io/dsh-remote/) · [Usage stats](https://flymysql.github.io/dsh-remote/stats/) · [Blog](https://gitpull.cn) · [Discussions](https://github.com/flymysql/dsh-remote/discussions) · [Issues](https://github.com/flymysql/dsh-remote/issues) · [中文说明](./README.zh.md)
|
|
14
14
|
|
|
15
15
|

|
|
16
16
|
|
package/README.zh.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# dsh-remote
|
|
6
6
|
|
|
7
|
-
由 [@flymysql](https://github.com/flymysql) 维护 · [博客](https://gitpull.cn) · [讨论区](https://github.com/flymysql/dsh-remote/discussions) · [Issue](https://github.com/flymysql/dsh-remote/issues) · [English](./README.md)
|
|
7
|
+
由 [@flymysql](https://github.com/flymysql) 维护 · [主页](https://flymysql.github.io/dsh-remote/) · [用量统计](https://flymysql.github.io/dsh-remote/stats/) · [博客](https://gitpull.cn) · [讨论区](https://github.com/flymysql/dsh-remote/discussions) · [Issue](https://github.com/flymysql/dsh-remote/issues) · [English](./README.md)
|
|
8
8
|
|
|
9
9
|
## 官方 Desktop 兼容适配(实验性,尚未发布)
|
|
10
10
|
|
package/lib/telemetry.js
CHANGED
|
@@ -23,8 +23,19 @@ import { createHmac, randomUUID } from 'node:crypto'
|
|
|
23
23
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
24
24
|
import path from 'node:path'
|
|
25
25
|
|
|
26
|
-
/** 上报端点(CloudBase HTTP 访问服务)。改域名只需改这一行。
|
|
27
|
-
|
|
26
|
+
/** 上报端点(CloudBase HTTP 访问服务)。改域名只需改这一行。
|
|
27
|
+
* 可用 `DSH_REMOTE_HEARTBEAT_URL` 覆盖——存在的理由不是"让用户改"
|
|
28
|
+
* (默认行为不该需要配置),而是:① 测试必须能把它重定向掉,
|
|
29
|
+
* 否则跑一次 npm test 就会往生产统计里灌假装机(发生过);
|
|
30
|
+
* ② 作者换端点/灰度时不必为了改一行字符串发一次版。 */
|
|
31
|
+
const DEFAULT_HEARTBEAT_URL = 'https://gitbolg-d7gmnsrw46e011706-1256429518.ap-shanghai.app.tcloudbase.com/dsh-hb/heartbeat'
|
|
32
|
+
|
|
33
|
+
/** 解析实际使用的端点(env 覆盖优先,仅接受 https,避免被降级到明文)。 */
|
|
34
|
+
export function heartbeatUrl() {
|
|
35
|
+
const override = process.env.DSH_REMOTE_HEARTBEAT_URL
|
|
36
|
+
if (override && /^https:\/\//i.test(override)) return override
|
|
37
|
+
return DEFAULT_HEARTBEAT_URL
|
|
38
|
+
}
|
|
28
39
|
/** 伪名盐:服务端无法反推身份,且换盐即可切断历史关联。 */
|
|
29
40
|
const SALT = 'dsh-remote/telemetry/v1'
|
|
30
41
|
const ID_FILE = '.dsh-remote-install-id'
|
|
@@ -80,16 +91,34 @@ export function pseudonym(home) {
|
|
|
80
91
|
/** 是否到该上报了(进程内节流,避免每次工具调用都发)。 */
|
|
81
92
|
let lastSentAt = 0
|
|
82
93
|
|
|
94
|
+
/** 是否处于测试进程。
|
|
95
|
+
*
|
|
96
|
+
* ★ 这是一道**代码级**兜底,不是便利设施。事故背景:光靠 package.json 里的
|
|
97
|
+
* `--import ./test/setup-telemetry-off.mjs` 前置不够——它只覆盖 `npm test`,
|
|
98
|
+
* 任何人直接 `node --test test/upload.test.js` 就会绕过它,而 upload.test.js
|
|
99
|
+
* 会调用真实 apply(),于是**线上日活表被写进来自测试的假装机**
|
|
100
|
+
* (实测:直接跑一次,云端立刻多一行;曾出现"只用过 Windows 的机器冒出 Linux 装机")。
|
|
101
|
+
*
|
|
102
|
+
* Node 的测试运行器会在**子进程**里设 `NODE_TEST_CONTEXT`(如 child-v8),
|
|
103
|
+
* 父进程可能没有,所以同时看父进程的 `--test` 参数。判定为真时直接不发。
|
|
104
|
+
* 代价:测试里也就无法验证"真的发出去了"——那正是本函数要保证的,
|
|
105
|
+
* 真机端到端请用隔离沙箱启动真实 DSH(README 有说明)。
|
|
106
|
+
*/
|
|
107
|
+
export function inTestProcess() {
|
|
108
|
+
if (process.env.NODE_TEST_CONTEXT) return true
|
|
109
|
+
const execArgv = process.execArgv || []
|
|
110
|
+
if (execArgv.some((a) => a === '--test' || String(a).startsWith('--test-'))) return true
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
|
|
83
114
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* @
|
|
88
|
-
*
|
|
89
|
-
* 避开启动高峰。测试可传 0。
|
|
90
|
-
* @returns 是否真的发出了请求(仅用于测试与自检)。
|
|
115
|
+
* 真正执行发送(不含"是否处于测试进程"的守卫)。
|
|
116
|
+
*
|
|
117
|
+
* 拆出来的原因:守卫本身必须可测,而"能测"又不能变成生产代码的后门——
|
|
118
|
+
* 所以守卫只存在于 {@link sendHeartbeat} 里,本函数只被它与测试直接调用,
|
|
119
|
+
* 且**不导出给业务代码**(导出名带下划线前缀,见文件末尾)。
|
|
91
120
|
*/
|
|
92
|
-
|
|
121
|
+
async function deliver(home, version, options = {}) {
|
|
93
122
|
const now = Date.now()
|
|
94
123
|
if (now - lastSentAt < MIN_INTERVAL_MS) return false
|
|
95
124
|
|
|
@@ -98,8 +127,12 @@ export async function sendHeartbeat(home, version, options = {}) {
|
|
|
98
127
|
lastSentAt = now
|
|
99
128
|
try {
|
|
100
129
|
if (delayMs) await new Promise((r) => {
|
|
101
|
-
|
|
102
|
-
|
|
130
|
+
// ★ 这里**不能 unref**。曾经 unref 过延迟定时器,结果是:await 一个已
|
|
131
|
+
// unref 的定时器不会让事件循环保活,当进程恰好没有别的 pending 句柄时
|
|
132
|
+
// Node 会**直接退出**,心跳一次都发不出去(CI 上稳定复现:本地因为其它
|
|
133
|
+
// 测试还留着句柄而侥幸变绿)。延迟是为了让心跳成功,就必须让它把进程
|
|
134
|
+
// 留住这几秒;插件真实运行时也不可能因为多等 3s 影响用户。
|
|
135
|
+
setTimeout(r, delayMs)
|
|
103
136
|
})
|
|
104
137
|
const body = JSON.stringify({
|
|
105
138
|
idHash: pseudonym(home),
|
|
@@ -111,7 +144,7 @@ export async function sendHeartbeat(home, version, options = {}) {
|
|
|
111
144
|
const controller = new AbortController()
|
|
112
145
|
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)
|
|
113
146
|
try {
|
|
114
|
-
await fetch(
|
|
147
|
+
await fetch(heartbeatUrl(), {
|
|
115
148
|
method: 'POST',
|
|
116
149
|
headers: { 'content-type': 'application/json' },
|
|
117
150
|
body,
|
|
@@ -129,6 +162,21 @@ export async function sendHeartbeat(home, version, options = {}) {
|
|
|
129
162
|
}
|
|
130
163
|
}
|
|
131
164
|
|
|
165
|
+
/**
|
|
166
|
+
* 发送一次心跳。**永不抛出**,也永不返回可导致分支的失败态:
|
|
167
|
+
* 调用方不需要 try/catch,心跳成功与否都不应改变任何行为。
|
|
168
|
+
* @param home - DSH_HOME 绝对路径。
|
|
169
|
+
* @param version - 运行中的插件版本(用 LOADED_VERSION,即真正在跑的代码版本)。
|
|
170
|
+
* @param options.delayMs - 发送前延迟(默认 {@link STARTUP_DELAY_MS});
|
|
171
|
+
* 避开启动高峰。测试可传 0。
|
|
172
|
+
* @returns 是否真的发出了请求(仅用于测试与自检)。
|
|
173
|
+
*/
|
|
174
|
+
export async function sendHeartbeat(home, version, options = {}) {
|
|
175
|
+
// 测试进程一律不外呼:统计数据的可信度比"测试里能验证外呼"重要得多。
|
|
176
|
+
if (inTestProcess()) return false
|
|
177
|
+
return deliver(home, version, options)
|
|
178
|
+
}
|
|
179
|
+
|
|
132
180
|
/** 清空节流与缓存(仅测试用)。 */
|
|
133
181
|
export function _resetForTest() {
|
|
134
182
|
lastSentAt = 0
|
|
@@ -137,12 +185,7 @@ export function _resetForTest() {
|
|
|
137
185
|
|
|
138
186
|
/** 心跳是否已配置(供设置页显示;不含端点本身)。 */
|
|
139
187
|
export function heartbeatEnabled() {
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** 供自检/文档使用的端点(不涉密,可公开)。 */
|
|
144
|
-
export function heartbeatUrl() {
|
|
145
|
-
return HEARTBEAT_URL
|
|
188
|
+
return heartbeatUrl().startsWith('https://')
|
|
146
189
|
}
|
|
147
190
|
|
|
148
191
|
/** 身份文件是否已存在(自检用,不创建)。 */
|
|
@@ -159,5 +202,12 @@ export function installIdPath(home) {
|
|
|
159
202
|
return path.join(home, ID_FILE)
|
|
160
203
|
}
|
|
161
204
|
|
|
162
|
-
|
|
163
|
-
|
|
205
|
+
/**
|
|
206
|
+
* 测试专用入口:绕过"测试进程不外呼"的守卫,用来验证发送逻辑本身
|
|
207
|
+
* (字段白名单、节流、失败回滚、延迟)。
|
|
208
|
+
*
|
|
209
|
+
* 之所以必须存在:守卫让 sendHeartbeat 在测试里恒返回 false,于是发送逻辑
|
|
210
|
+
* 就完全测不到了。真正的安全来自**端点可覆盖**(测试把 URL 指向本地不可达地址)
|
|
211
|
+
* 而不是来自这个函数——它只是把守卫那道门打开给测试用。
|
|
212
|
+
*/
|
|
213
|
+
export const _deliverForTest = deliver
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.26",
|
|
4
4
|
"description": "Remote-work assistant for DeepSeek Harness: connect SSH (password/key/agent/keyboard-interactive/proxy jump), pick a remote workspace, operate on it with 20 rw_* tools (incl. rw_edit/rw_stat/rw_mkdir/rw_remove/rw_move/rw_forward), conflict-aware mirror sync, port forwarding, optional side-bar remote file editing, audit log, keychain passwords.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "flymysql <flyphp@outlook.com>",
|
|
@@ -77,6 +77,6 @@
|
|
|
77
77
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
|
-
"test": "node --test"
|
|
80
|
+
"test": "node --import ./test/setup-telemetry-off.mjs --test"
|
|
81
81
|
}
|
|
82
82
|
}
|