@hyzyn/dsh-tty 0.10.3 → 0.12.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/README.md +55 -2
- package/client.js +1852 -38
- package/lib/index.d.ts +11 -0
- package/lib/index.js +156 -8
- package/lib/index.js.map +1 -1
- package/lib/probe.d.ts +42 -0
- package/lib/probe.js +274 -0
- package/lib/probe.js.map +1 -0
- package/lib/sftp.d.ts +52 -2
- package/lib/sftp.js +197 -12
- package/lib/sftp.js.map +1 -1
- package/package.json +2 -1
package/lib/probe.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { HostKeyStore, SshSpec } from './ssh.js';
|
|
2
|
+
/** TCP 预检超时(毫秒):DNS 解析 + 建连。 */
|
|
3
|
+
export declare const PROBE_TCP_TIMEOUT_MS = 6000;
|
|
4
|
+
/** ssh2 握手/认证阶段超时(毫秒);覆盖 buildConnectConfig 的 readyTimeout。 */
|
|
5
|
+
export declare const PROBE_AUTH_TIMEOUT_MS = 8000;
|
|
6
|
+
/** 分类结果载荷(HTTP 回传;字段全部可 JSON)。 */
|
|
7
|
+
export interface ProbeResult {
|
|
8
|
+
/** tcp 阶段:DNS 解析 + TCP 建连。 */
|
|
9
|
+
tcp: {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
ms?: number;
|
|
13
|
+
};
|
|
14
|
+
/** hostkey 阶段:TOFU 比对(未到握手时为 unknown)。 */
|
|
15
|
+
hostkey: {
|
|
16
|
+
state: 'unknown' | 'matched' | 'recorded' | 'mismatch';
|
|
17
|
+
/** 服务端 host key 的 sha256 指纹(hostHash:'sha256' 下 hostVerifier 收到的原样值)。 */
|
|
18
|
+
fingerprint: string;
|
|
19
|
+
/** 已记录指纹(mismatch 时展示对照)。 */
|
|
20
|
+
known?: string;
|
|
21
|
+
/** mismatch 时的完整指引文案(与 spawnSsh 一致)。 */
|
|
22
|
+
error?: string;
|
|
23
|
+
};
|
|
24
|
+
/** auth 阶段:认证是否通过(ready)。 */
|
|
25
|
+
auth: {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
ms?: number;
|
|
29
|
+
};
|
|
30
|
+
/** 总耗时。 */
|
|
31
|
+
totalMs: number;
|
|
32
|
+
}
|
|
33
|
+
/** 仅校验一份连接簿 / 对话框条目的形状(新增 / 编辑前先过一遍;不做网络请求)。 */
|
|
34
|
+
export declare function validateSshFields(input: Record<string, unknown>): {
|
|
35
|
+
spec?: SshSpec;
|
|
36
|
+
error?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* 单次连接诊断。无论成败都会关闭连接、在超时内返回,绝不悬挂。
|
|
40
|
+
* 分类见文件头;tcp 预检通过后才进入 ssh2 握手。
|
|
41
|
+
*/
|
|
42
|
+
export declare function probeSsh(spec: SshSpec, store?: HostKeyStore): Promise<ProbeResult>;
|
package/lib/probe.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hyzyn/dsh-tty — SSH 连接测试(probe)模块。
|
|
3
|
+
*
|
|
4
|
+
* 「连接测试」按钮的数据源:不占会话名额、不开 shell channel,只做一次
|
|
5
|
+
* 链路诊断,把分段结果带回设置卡片 / SSH 连接对话框:
|
|
6
|
+
*
|
|
7
|
+
* 阶段 做法 判定
|
|
8
|
+
* tcp net.connect 预检(DNS + TCP 建连,6s 超时) → 主机可达 / DNS / 拒绝 / 超时
|
|
9
|
+
* hostkey ssh2 握手;hostVerifier 内做 TOFU 比对 → 已匹配 / 新指纹 / 不匹配拒绝
|
|
10
|
+
* auth ssh2 ready / error(认证被拒、协商超时等) → 认证通过或分类失败
|
|
11
|
+
*
|
|
12
|
+
* host key 两种策略(与隧道/SFTP 的 hostKeyStore 用法对齐):
|
|
13
|
+
* - 提供 store(设置卡片「测试」按钮):完整 TOFU——新指纹当场持久化
|
|
14
|
+
* record(测试即「首次连接」语义),不匹配拒绝并给出与 spawnSsh 完全
|
|
15
|
+
* 一致的指引文案(含已记录指纹与本次指纹,供对照);
|
|
16
|
+
* - 不提供 store(SSH 连接对话框试连,尚未保存/未必入库):只比对不
|
|
17
|
+
* 记录(避免给未保存的草稿建立钉扎),不匹配照样拒绝。
|
|
18
|
+
*
|
|
19
|
+
* 认证失败返回服务端标准错误消息原文(如 `All configured authentication
|
|
20
|
+
* methods failed`)不额外臆测——真实原因(密码错 / 用户不存在 / 方法未开)
|
|
21
|
+
* 由服务端决定,用户可据原文排查。
|
|
22
|
+
*/
|
|
23
|
+
import { Client } from 'ssh2';
|
|
24
|
+
import { connect as netConnect } from 'node:net';
|
|
25
|
+
import { buildConnectConfig, sshTarget } from './ssh.js';
|
|
26
|
+
/** TCP 预检超时(毫秒):DNS 解析 + 建连。 */
|
|
27
|
+
export const PROBE_TCP_TIMEOUT_MS = 6_000;
|
|
28
|
+
/** ssh2 握手/认证阶段超时(毫秒);覆盖 buildConnectConfig 的 readyTimeout。 */
|
|
29
|
+
export const PROBE_AUTH_TIMEOUT_MS = 8_000;
|
|
30
|
+
/** 把底层错误分类为人类可读诊断;原文保留在返回串里便于对照排查。 */
|
|
31
|
+
function classifyError(message) {
|
|
32
|
+
if (message === 'Host key verification failed') {
|
|
33
|
+
return '主机密钥校验失败(TOFU 不匹配,见 hostkey 指引)';
|
|
34
|
+
}
|
|
35
|
+
if (message.includes('All configured authentication methods failed')) {
|
|
36
|
+
return '认证被拒绝:所有认证方式均失败(用户名/密码/密钥是否正确,或服务端是否允许该认证方式)';
|
|
37
|
+
}
|
|
38
|
+
if (message.includes('Timed out')) {
|
|
39
|
+
return '超时:主机无响应或认证协商超时(检查地址 / 端口 / 防火墙 / 网络)';
|
|
40
|
+
}
|
|
41
|
+
const lower = message.toLowerCase();
|
|
42
|
+
if (lower.includes('econnrefused'))
|
|
43
|
+
return '连接被拒绝(ECONNREFUSED):端口未监听或服务未启动';
|
|
44
|
+
if (lower.includes('enetunreach'))
|
|
45
|
+
return '网络不可达(ENETUNREACH):路由不通或主机离线';
|
|
46
|
+
if (lower.includes('ehostunreach'))
|
|
47
|
+
return '主机不可达(EHOSTUNREACH)';
|
|
48
|
+
if (lower.includes('eai_again') || lower.includes('eai_noname') || lower.includes('enotfound'))
|
|
49
|
+
return 'DNS 解析失败:主机名无法解析';
|
|
50
|
+
if (lower.includes('getaddrinfo'))
|
|
51
|
+
return 'DNS 解析失败:主机名无法解析';
|
|
52
|
+
if (lower.includes('unable to exchange encryption keys') || lower.includes('encryption') || lower.includes('kex')) {
|
|
53
|
+
return '密钥交换失败:服务端可能不是 SSH 服务,或加密算法不兼容';
|
|
54
|
+
}
|
|
55
|
+
if (lower.includes('keepalive'))
|
|
56
|
+
return '连接保活超时(keepalive)';
|
|
57
|
+
if (lower.includes('protocol'))
|
|
58
|
+
return '协议错误:' + message;
|
|
59
|
+
return message;
|
|
60
|
+
}
|
|
61
|
+
/** 与 spawnSsh 的 applyHostKeyPolicy 一致的 TOFU 指引文案。 */
|
|
62
|
+
function mismatchMessage(target, host, port, known, current) {
|
|
63
|
+
return (`SSH 主机密钥指纹变更:${target} 已记录 sha256:${known},本次为 sha256:${current}。` +
|
|
64
|
+
'可能是主机重装或换钥匙,也可能是中间人(MITM)冒充;确认安全后,到 设置 → 插件 → 终端面板 → SSH 主机密钥记录 删除该主机再重连。');
|
|
65
|
+
}
|
|
66
|
+
/** 收集 hostVerifier 收到的指纹(ssh2 可能对多 host key 调用多次,取最后一次)。 */
|
|
67
|
+
function makeHostKeyVerifier(options) {
|
|
68
|
+
const { spec, store, onResult } = options;
|
|
69
|
+
const port = spec.port ?? 22;
|
|
70
|
+
const target = sshTarget(spec);
|
|
71
|
+
let seen = '';
|
|
72
|
+
return (hash) => {
|
|
73
|
+
seen = hash;
|
|
74
|
+
const known = store?.get(spec.host, port);
|
|
75
|
+
if (known === undefined) {
|
|
76
|
+
if (store !== undefined) {
|
|
77
|
+
// 完整 TOFU:新指纹当场持久化(与 spawnSsh 的 hostVerifier 同语义)
|
|
78
|
+
store.record(spec.host, port, hash);
|
|
79
|
+
onResult({ state: 'recorded', fingerprint: hash });
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// 试连(对话框):只比对不落盘
|
|
83
|
+
onResult({ state: 'matched', fingerprint: hash });
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (known === hash) {
|
|
88
|
+
onResult({ state: 'matched', fingerprint: hash, known });
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
onResult({ state: 'mismatch', fingerprint: hash, known, error: mismatchMessage(target, spec.host, port, known, hash) });
|
|
92
|
+
return false;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** 仅校验一份连接簿 / 对话框条目的形状(新增 / 编辑前先过一遍;不做网络请求)。 */
|
|
96
|
+
export function validateSshFields(input) {
|
|
97
|
+
const host = typeof input.host === 'string' ? input.host.trim() : '';
|
|
98
|
+
const username = typeof input.username === 'string' ? input.username.trim() : '';
|
|
99
|
+
if (host === '')
|
|
100
|
+
return { error: '主机必填' };
|
|
101
|
+
if (username === '')
|
|
102
|
+
return { error: '用户名必填' };
|
|
103
|
+
let port = 22;
|
|
104
|
+
if (input.port !== undefined && input.port !== '') {
|
|
105
|
+
const value = Number(input.port);
|
|
106
|
+
if (!Number.isInteger(value) || value < 1 || value > 65535)
|
|
107
|
+
return { error: '端口必须是 1~65535 的整数' };
|
|
108
|
+
port = value;
|
|
109
|
+
}
|
|
110
|
+
const auth = input.auth === 'key' || input.auth === 'password' ? input.auth : 'agent';
|
|
111
|
+
const spec = { host, port, username, auth };
|
|
112
|
+
if (auth === 'key') {
|
|
113
|
+
const keyPath = typeof input.keyPath === 'string' ? input.keyPath.trim() : '';
|
|
114
|
+
if (keyPath === '')
|
|
115
|
+
return { error: 'auth=key 需要私钥路径' };
|
|
116
|
+
spec.keyPath = keyPath;
|
|
117
|
+
const passphrase = typeof input.passphrase === 'string' ? input.passphrase : '';
|
|
118
|
+
if (passphrase !== '')
|
|
119
|
+
spec.passphrase = passphrase;
|
|
120
|
+
}
|
|
121
|
+
if (auth === 'password') {
|
|
122
|
+
const password = typeof input.password === 'string' ? input.password : '';
|
|
123
|
+
if (password === '')
|
|
124
|
+
return { error: 'auth=password 需要密码' };
|
|
125
|
+
spec.password = password;
|
|
126
|
+
}
|
|
127
|
+
if (input.agentForward === true)
|
|
128
|
+
spec.agentForward = true;
|
|
129
|
+
return { spec };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 单次连接诊断。无论成败都会关闭连接、在超时内返回,绝不悬挂。
|
|
133
|
+
* 分类见文件头;tcp 预检通过后才进入 ssh2 握手。
|
|
134
|
+
*/
|
|
135
|
+
export async function probeSsh(spec, store) {
|
|
136
|
+
const startedAt = Date.now();
|
|
137
|
+
const result = {
|
|
138
|
+
tcp: { ok: false },
|
|
139
|
+
hostkey: { state: 'unknown', fingerprint: '' },
|
|
140
|
+
auth: { ok: false },
|
|
141
|
+
totalMs: 0,
|
|
142
|
+
};
|
|
143
|
+
const finish = () => {
|
|
144
|
+
result.totalMs = Date.now() - startedAt;
|
|
145
|
+
return result;
|
|
146
|
+
};
|
|
147
|
+
const port = spec.port ?? 22;
|
|
148
|
+
const target = sshTarget(spec);
|
|
149
|
+
// ---- 阶段 1:agent 预检(与 spawnSsh 同款,快速失败) ----
|
|
150
|
+
if (spec.auth === 'agent' && (process.env.SSH_AUTH_SOCK === undefined || process.env.SSH_AUTH_SOCK === '')) {
|
|
151
|
+
const message = 'agent 认证需要 SSH_AUTH_SOCK(本机未运行 ssh-agent 或变量未设置)';
|
|
152
|
+
result.tcp = { ok: false, error: message, ms: 0 };
|
|
153
|
+
result.auth = { ok: false, error: message };
|
|
154
|
+
return finish();
|
|
155
|
+
}
|
|
156
|
+
// ---- 阶段 1:TCP 预检(DNS + 建连) ----
|
|
157
|
+
const tcpResult = await new Promise((resolve) => {
|
|
158
|
+
const sock = netConnect({ host: spec.host, port });
|
|
159
|
+
const tcpStart = Date.now();
|
|
160
|
+
const done = (ok, error) => {
|
|
161
|
+
try {
|
|
162
|
+
sock.destroy();
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
/* 已关闭 */
|
|
166
|
+
}
|
|
167
|
+
resolve({ ok, error, ms: Date.now() - tcpStart });
|
|
168
|
+
};
|
|
169
|
+
sock.setTimeout(PROBE_TCP_TIMEOUT_MS, () => {
|
|
170
|
+
done(false, 'TCP 连接超时:主机无响应(检查地址 / 防火墙 / 网络)');
|
|
171
|
+
});
|
|
172
|
+
sock.once('connect', () => done(true));
|
|
173
|
+
sock.once('error', (error) => {
|
|
174
|
+
done(false, classifyError(error.message));
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
result.tcp = tcpResult;
|
|
178
|
+
if (!tcpResult.ok) {
|
|
179
|
+
result.auth = { ok: false, error: tcpResult.error };
|
|
180
|
+
return finish();
|
|
181
|
+
}
|
|
182
|
+
// ---- 阶段 2+3:ssh2 握手(host key 交换 + 认证) ----
|
|
183
|
+
let connectConfig;
|
|
184
|
+
try {
|
|
185
|
+
connectConfig = buildConnectConfig(spec);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
189
|
+
result.auth = { ok: false, error: classifyError(message) };
|
|
190
|
+
return finish();
|
|
191
|
+
}
|
|
192
|
+
// 探针专属更短握手超时(buildConnectConfig 默认 20s 对测试太长)
|
|
193
|
+
connectConfig.readyTimeout = PROBE_AUTH_TIMEOUT_MS;
|
|
194
|
+
const password = spec.auth === 'password' ? connectConfig.password ?? '' : '';
|
|
195
|
+
const tryKeyboard = connectConfig.tryKeyboard === true;
|
|
196
|
+
return new Promise((resolve) => {
|
|
197
|
+
let settled = false;
|
|
198
|
+
let seenHostKey = false;
|
|
199
|
+
let hostkeyState = { state: 'unknown', fingerprint: '' };
|
|
200
|
+
const settle = () => {
|
|
201
|
+
if (settled)
|
|
202
|
+
return;
|
|
203
|
+
settled = true;
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
try {
|
|
206
|
+
conn.end();
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
/* 已断开 */
|
|
210
|
+
}
|
|
211
|
+
resolve(finish());
|
|
212
|
+
};
|
|
213
|
+
// hostVerifier 的 onResult 同步回调:把结论收集到 hostkeyState
|
|
214
|
+
connectConfig.hostVerifier = makeHostKeyVerifier({
|
|
215
|
+
spec,
|
|
216
|
+
store,
|
|
217
|
+
onResult: (next) => {
|
|
218
|
+
seenHostKey = true;
|
|
219
|
+
hostkeyState = { ...next, error: next.error };
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
// password 认证挂 keyboard-interactive 自动应答(同 spawnSsh/tunnels/sftp)
|
|
223
|
+
const conn = new Client();
|
|
224
|
+
if (tryKeyboard) {
|
|
225
|
+
conn.on('keyboard-interactive', (_name, _instructions, _lang, _prompts, finishKb) => {
|
|
226
|
+
finishKb([password]);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const authStart = Date.now();
|
|
230
|
+
const timer = setTimeout(() => {
|
|
231
|
+
if (seenHostKey) {
|
|
232
|
+
result.hostkey = hostkeyState;
|
|
233
|
+
result.auth = { ok: false, error: '认证/协商超时(服务端在 8s 内未完成认证)', ms: Date.now() - authStart };
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
result.auth = { ok: false, error: '握手超时(服务端未在 8s 内完成密钥交换)', ms: Date.now() - authStart };
|
|
237
|
+
}
|
|
238
|
+
settle();
|
|
239
|
+
}, PROBE_AUTH_TIMEOUT_MS + 1000);
|
|
240
|
+
timer.unref?.();
|
|
241
|
+
conn.on('ready', () => {
|
|
242
|
+
result.hostkey = hostkeyState;
|
|
243
|
+
result.auth = { ok: true, ms: Date.now() - authStart };
|
|
244
|
+
settle();
|
|
245
|
+
});
|
|
246
|
+
conn.on('error', (error) => {
|
|
247
|
+
const classified = classifyError(error.message);
|
|
248
|
+
if (error.message === 'Host key verification failed' && hostkeyState.state === 'mismatch') {
|
|
249
|
+
result.hostkey = hostkeyState;
|
|
250
|
+
result.auth = { ok: false, error: '主机密钥校验失败(见 hostkey 指引)', ms: Date.now() - authStart };
|
|
251
|
+
settle();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
result.hostkey = seenHostKey ? hostkeyState : { state: 'unknown', fingerprint: '' };
|
|
255
|
+
// TCP 已通:这里只可能是协商 / 认证 / 协议层错误
|
|
256
|
+
result.auth = { ok: false, error: classified, ms: Date.now() - authStart };
|
|
257
|
+
settle();
|
|
258
|
+
});
|
|
259
|
+
conn.on('close', () => {
|
|
260
|
+
// 正常路径(ready / error / 超时)已 settle;未 settle 的 close 兜底
|
|
261
|
+
result.auth = { ok: false, error: '连接已关闭(服务端主动断开)', ms: Date.now() - authStart };
|
|
262
|
+
settle();
|
|
263
|
+
});
|
|
264
|
+
try {
|
|
265
|
+
conn.connect(connectConfig);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
269
|
+
result.auth = { ok: false, error: classifyError(message), ms: Date.now() - authStart };
|
|
270
|
+
settle();
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=probe.js.map
|
package/lib/probe.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"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"}
|
package/lib/sftp.d.ts
CHANGED
|
@@ -49,11 +49,39 @@ export interface SftpUpload {
|
|
|
49
49
|
/** 写入完成(流 close)resolve,写入失败 reject——路由 await 它再回包。 */
|
|
50
50
|
done: Promise<void>;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* 传输任务(0.12.0):双栏 ⇨/⇦ 的服务端直传任务化——start 立刻返回 id,
|
|
54
|
+
* 客户端轮询 /cancel 端点中止。无它时一个目录递归直传在 HTTP 请求里同步跑
|
|
55
|
+
* 完,浏览器无法打断(只能关窗口,服务端还在写远端)。
|
|
56
|
+
*/
|
|
57
|
+
export interface SftpTransferJob {
|
|
58
|
+
id: string;
|
|
59
|
+
direction: 'up' | 'down';
|
|
60
|
+
/** 起始路径对(进度行展示用)。 */
|
|
61
|
+
localPath: string;
|
|
62
|
+
remotePath: string;
|
|
63
|
+
/** 已完成字节 / 总字节(总字节在开传前递归统计,统计中可为 0)。 */
|
|
64
|
+
bytes: number;
|
|
65
|
+
total: number;
|
|
66
|
+
/** 当前正在搬运的文件相对名(展示用)。 */
|
|
67
|
+
current: string;
|
|
68
|
+
state: 'running' | 'done' | 'error' | 'canceled';
|
|
69
|
+
error?: string;
|
|
70
|
+
}
|
|
71
|
+
/** 取消感知的传输选项(直传任务内部用;HTTP 上传/下载路由不传 signal)。 */
|
|
72
|
+
export interface SftpTransferOptions {
|
|
73
|
+
signal?: AbortSignal;
|
|
74
|
+
/** 每搬运一个文件前回调(进度行显示当前文件名)。 */
|
|
75
|
+
onFile?: (name: string) => void;
|
|
76
|
+
/** 字节累加(进度条分子)。 */
|
|
77
|
+
onBytes?: (delta: number) => void;
|
|
78
|
+
}
|
|
52
79
|
export declare class SftpManager {
|
|
53
80
|
private readonly logger;
|
|
54
81
|
private readonly store;
|
|
55
82
|
private readonly conns;
|
|
56
83
|
private sweeper;
|
|
84
|
+
private readonly jobs;
|
|
57
85
|
constructor(logger: SftpLogger, store: HostKeyStore);
|
|
58
86
|
/** 插件卸载:关定时器与全部连接(幂等)。 */
|
|
59
87
|
disposeAll(): void;
|
|
@@ -90,13 +118,35 @@ export declare class SftpManager {
|
|
|
90
118
|
/**
|
|
91
119
|
* 本机文件 / 目录 → 远程(双栏「→ 传输」)。目录递归建目录后逐个上传;
|
|
92
120
|
* 同名文件直接覆盖(openUpload 'w')。不经过浏览器,字节不出宿主进程。
|
|
121
|
+
* signal 中止时销毁读写流并删除半截的远程文件(删除失败只记日志)。
|
|
93
122
|
*/
|
|
94
|
-
uploadFromLocal(spec: SshSpec, localPath: string, remotePath: string): Promise<void>;
|
|
123
|
+
uploadFromLocal(spec: SshSpec, localPath: string, remotePath: string, options?: SftpTransferOptions): Promise<void>;
|
|
95
124
|
/**
|
|
96
125
|
* 远程文件 / 目录 → 本机(双栏「← 传输」)。目录递归建本地目录后逐个下载;
|
|
97
126
|
* 同名文件直接覆盖('w' 写流)。远程符号链接按文件下载(跟随目标)。
|
|
127
|
+
* signal 中止时销毁读写流并删除半截的本机文件(删除失败只记日志)。
|
|
128
|
+
*/
|
|
129
|
+
downloadToLocal(spec: SshSpec, remotePath: string, localPath: string, options?: SftpTransferOptions): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* 带字节计数与取消感知的 pipe:source→sink 之间插一个 PassThrough 只做
|
|
132
|
+
* 「数了就转发」;取消时 destroy 它——pipeline 随即结束并销毁两端流,
|
|
133
|
+
* 不留悬挂的 SFTP 句柄,也不会误报成「传输失败」。
|
|
134
|
+
*/
|
|
135
|
+
private pipeCounted;
|
|
136
|
+
/** 删远端文件(取消后的半截清理):失败只记日志,不影响取消本身。 */
|
|
137
|
+
deleteRemoteQuiet(spec: SshSpec, path: string): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* 预扫描总字节数(直传进度分母):目录递归累加文件大小,只数「源头一侧」
|
|
140
|
+
* ——up 数本机、down 数远程(对侧尚未创建,数不到也不该数)。目的是让
|
|
141
|
+
* 进度条有真实百分比:纯统计(不搬运),单项失败按 0 计、不阻断传输。
|
|
98
142
|
*/
|
|
99
|
-
|
|
143
|
+
private estimateBytes;
|
|
144
|
+
/** 开一个可取消的直传任务(路由 /transfer 的 start 分支)。 */
|
|
145
|
+
startTransfer(spec: SshSpec, direction: 'up' | 'down', localPath: string, remotePath: string): SftpTransferJob;
|
|
146
|
+
/** 任务快照(路由 /transfer?job=<id> 轮询)。 */
|
|
147
|
+
getTransfer(id: string): SftpTransferJob | undefined;
|
|
148
|
+
/** 中止任务(路由 /transfer 的 cancel 分支);已终态的任务返回 undefined。 */
|
|
149
|
+
cancelTransfer(id: string): SftpTransferJob | undefined;
|
|
100
150
|
/** 取(或建立)该 spec 的 SFTP 通道;连接断开的旧条目在此处自动重建。 */
|
|
101
151
|
private acquire;
|
|
102
152
|
private ensureSweeper;
|