@zhangfengshun/dsh-remote-ssh 2.0.3 → 2.0.5
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/lib/client.js +11 -3
- package/lib/index.js +84 -86
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -747,11 +747,19 @@ window.__ModuleLoader__.load({
|
|
|
747
747
|
// ======================================================================
|
|
748
748
|
// 本地目录列举:走 DSH browse 能力(无原生对话框时替代 pickDirectory)。
|
|
749
749
|
function localListFn(workspaces, p) {
|
|
750
|
-
|
|
750
|
+
// 走 better-sidebar 的 fs.tree 端点(已被本插件拦截,本地工作区走本地 fs)
|
|
751
|
+
// 不依赖 workspaces.listDirectory(需要 browse capability,可能被禁用)
|
|
752
|
+
return fetch("/sidebar/api/fs.tree", {
|
|
753
|
+
method: "POST",
|
|
754
|
+
headers: { "content-type": "application/json" },
|
|
755
|
+
body: JSON.stringify({ sessionId: "", path: p || "" })
|
|
756
|
+
}).then(function (r) { return r.json(); }).then(function (j) {
|
|
757
|
+
if (!j || !j.ok) return { ok: false, error: (j && j.error && j.error.message) || "list failed" };
|
|
758
|
+
var v = j.value || {};
|
|
751
759
|
return {
|
|
752
760
|
ok: true,
|
|
753
|
-
path:
|
|
754
|
-
entries: (
|
|
761
|
+
path: v.path || p || "",
|
|
762
|
+
entries: (v.entries || []).filter(function (e) { return e.isDir; }).map(function (e) { return { name: e.name, type: "directory", path: e.path }; })
|
|
755
763
|
};
|
|
756
764
|
}).catch(function (e) {
|
|
757
765
|
return { ok: false, error: String(e && e.message ? e.message : e) };
|
package/lib/index.js
CHANGED
|
@@ -820,13 +820,8 @@ function apply(ctx, config) {
|
|
|
820
820
|
try {
|
|
821
821
|
await writeFile(join(mirrorDir, "README.md"), remoteWorkspaceReadme(p.name, a.remotePath), "utf8");
|
|
822
822
|
} catch (e) {}
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
try {
|
|
826
|
-
await remoteSyncDown(subprocess, p, a.remotePath, mirrorDir);
|
|
827
|
-
} catch (e) {}
|
|
828
|
-
// 写 .remote-ssh.json:供 shell wrapper 读取连接信息(仅密钥认证,不含密码)。
|
|
829
|
-
// 必须在 remoteSyncDown 之后写,否则会被同步过程清空。
|
|
823
|
+
// 不再需要 syncDown:内置「文件」页签已通过 fs.* 拦截直接 SSH 读写远程文件。
|
|
824
|
+
// 写 .remote-ssh.json:供 shell wrapper 和 fs.* 拦截器读取连接信息(仅密钥认证,不含密码)。
|
|
830
825
|
try {
|
|
831
826
|
const connInfo = {
|
|
832
827
|
profileId: p.id, host: p.host, port: p.port || 22, user: p.user,
|
|
@@ -1545,91 +1540,94 @@ function apply(ctx, config) {
|
|
|
1545
1540
|
return { kind: "text", content: r.content || "", truncated: !!r.truncated };
|
|
1546
1541
|
}
|
|
1547
1542
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
const r = await remoteGlob(runPooled, profile, "*" + (payload.query || "") + "*", rp);
|
|
1602
|
-
result = { entries: (r.files || []).map(function (f) {
|
|
1603
|
-
return { path: join(actualCwd, f), isDir: false };
|
|
1604
|
-
}), truncated: !!r.truncated };
|
|
1605
|
-
} else {
|
|
1606
|
-
writeJson(res, 404, { ok: false, error: { code: "not-found", message: "unknown method " + method } }); return;
|
|
1607
|
-
}
|
|
1543
|
+
// exact 路由优先于 prefix 路由匹配,所以每个 fs.* 端点注册一个 exact 路由即可拦截。
|
|
1544
|
+
// webServer prefix 匹配要求 pathname 以 prefix+"/" 开头,但 fs.tree 用的是点分隔符,
|
|
1545
|
+
// 所以无法用 prefix 拦截,必须用 exact。
|
|
1546
|
+
async function interceptFsHandler(req, res, method) {
|
|
1547
|
+
if (!isTrusted(req)) { writeJson(res, 403, { ok: false, error: { code: "forbidden", message: "forbidden" } }); return; }
|
|
1548
|
+
if (req.method !== "POST") { writeJson(res, 405, { ok: false, error: { code: "method-error", message: "method not allowed" } }); return; }
|
|
1549
|
+
let payload;
|
|
1550
|
+
try { payload = await readJsonBody(req); }
|
|
1551
|
+
catch (e) { writeJson(res, 400, { ok: false, error: { code: "bad-request", message: String(e && e.message ? e.message : e) } }); return; }
|
|
1552
|
+
try {
|
|
1553
|
+
// 获取会话 cwd
|
|
1554
|
+
const sessionId = payload && payload.sessionId;
|
|
1555
|
+
let sessionCwd = payload && payload.cwd;
|
|
1556
|
+
if ((!sessionCwd || sessionCwd === "") && sessionId) {
|
|
1557
|
+
const sessions = ctx.get("sessions");
|
|
1558
|
+
const session = sessions ? sessions.get(sessionId) : null;
|
|
1559
|
+
sessionCwd = session && session.header && session.header.cwd;
|
|
1560
|
+
}
|
|
1561
|
+
// 如果 sessionCwd 还是空,尝试用 payload.path(文件操作时 path 包含工作区路径)
|
|
1562
|
+
if ((!sessionCwd || sessionCwd === "") && payload && payload.path) {
|
|
1563
|
+
sessionCwd = payload.path;
|
|
1564
|
+
}
|
|
1565
|
+
// 检查是否远程工作区:先查 sessionCwd,再查 payload.path
|
|
1566
|
+
let remoteInfo = readRemoteInfoSync(sessionCwd);
|
|
1567
|
+
let remoteBase = sessionCwd;
|
|
1568
|
+
if (!remoteInfo && payload && payload.path) {
|
|
1569
|
+
remoteInfo = readRemoteInfoSync(payload.path);
|
|
1570
|
+
remoteBase = payload.path;
|
|
1571
|
+
}
|
|
1572
|
+
console.log("[dsh-remote-ssh] intercept " + method + ": sessionId=" + sessionId + " cwd=" + sessionCwd + " path=" + (payload && payload.path) + " remote=" + (remoteInfo ? "YES" : "NO"));
|
|
1573
|
+
let result;
|
|
1574
|
+
if (remoteInfo && remoteInfo.keyPath && remoteInfo.host && remoteInfo.user) {
|
|
1575
|
+
// ---- 远程工作区:走 SSH ----
|
|
1576
|
+
const profile = getProfile(remoteInfo.profileId);
|
|
1577
|
+
if (!profile) { writeJson(res, 500, { ok: false, error: { code: "internal", message: "remote profile not found: " + remoteInfo.profileId } }); return; }
|
|
1578
|
+
if (method === "fs.tree") {
|
|
1579
|
+
const localPath = payload.path || sessionCwd;
|
|
1580
|
+
const rp = localToRemote(localPath, remoteBase, remoteInfo.remotePath);
|
|
1581
|
+
result = await remoteListForSidebar(profile, rp, localPath);
|
|
1582
|
+
} else if (method === "fs.read") {
|
|
1583
|
+
const localPath = payload.path || sessionCwd;
|
|
1584
|
+
const rp = localToRemote(localPath, remoteBase, remoteInfo.remotePath);
|
|
1585
|
+
result = await remoteReadForSidebar(profile, rp);
|
|
1586
|
+
} else if (method === "fs.write") {
|
|
1587
|
+
const localPath = payload.path || sessionCwd;
|
|
1588
|
+
const rp = localToRemote(localPath, remoteBase, remoteInfo.remotePath);
|
|
1589
|
+
result = await remoteWriteFile(runPooled, profile, rp, payload.content || "");
|
|
1590
|
+
} else if (method === "fs.search") {
|
|
1591
|
+
const rp = remoteInfo.remotePath;
|
|
1592
|
+
const r = await remoteGlob(runPooled, profile, "*" + (payload.query || "") + "*", rp);
|
|
1593
|
+
result = { entries: (r.files || []).map(function (f) {
|
|
1594
|
+
return { path: join(remoteBase, f), isDir: false };
|
|
1595
|
+
}), truncated: !!r.truncated };
|
|
1608
1596
|
} else {
|
|
1609
|
-
|
|
1610
|
-
if (method === "fs.tree") {
|
|
1611
|
-
const p = payload.path || sessionCwd;
|
|
1612
|
-
result = await localListDir(p, LIST_LIMIT);
|
|
1613
|
-
} else if (method === "fs.read") {
|
|
1614
|
-
result = await localReadText(payload.path, READ_LIMIT);
|
|
1615
|
-
} else if (method === "fs.write") {
|
|
1616
|
-
result = await localWriteFile(payload.path, payload.content || "");
|
|
1617
|
-
} else if (method === "fs.search") {
|
|
1618
|
-
result = await localSearchFiles(sessionCwd, payload.query || "");
|
|
1619
|
-
} else {
|
|
1620
|
-
writeJson(res, 404, { ok: false, error: { code: "not-found", message: "unknown method " + method } }); return;
|
|
1621
|
-
}
|
|
1597
|
+
writeJson(res, 404, { ok: false, error: { code: "not-found", message: "unknown method " + method } }); return;
|
|
1622
1598
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1599
|
+
} else {
|
|
1600
|
+
// ---- 本地工作区:走本地 fs ----
|
|
1601
|
+
if (method === "fs.tree") {
|
|
1602
|
+
const p = payload.path || sessionCwd;
|
|
1603
|
+
result = await localListDir(p, LIST_LIMIT);
|
|
1604
|
+
} else if (method === "fs.read") {
|
|
1605
|
+
result = await localReadText(payload.path, READ_LIMIT);
|
|
1606
|
+
} else if (method === "fs.write") {
|
|
1607
|
+
result = await localWriteFile(payload.path, payload.content || "");
|
|
1608
|
+
} else if (method === "fs.search") {
|
|
1609
|
+
result = await localSearchFiles(sessionCwd, payload.query || "");
|
|
1625
1610
|
} else {
|
|
1626
|
-
|
|
1611
|
+
writeJson(res, 404, { ok: false, error: { code: "not-found", message: "unknown method " + method } }); return;
|
|
1627
1612
|
}
|
|
1628
|
-
} catch (e) {
|
|
1629
|
-
writeJson(res, 400, { ok: false, error: { code: "fs-error", message: String(e && e.message ? e.message : e) } });
|
|
1630
1613
|
}
|
|
1614
|
+
if (result && result.ok === false) {
|
|
1615
|
+
writeJson(res, 400, { ok: false, error: { code: "fs-error", message: result.error || "operation failed" } });
|
|
1616
|
+
} else {
|
|
1617
|
+
writeOk(res, result);
|
|
1618
|
+
}
|
|
1619
|
+
} catch (e) {
|
|
1620
|
+
writeJson(res, 400, { ok: false, error: { code: "fs-error", message: String(e && e.message ? e.message : e) } });
|
|
1631
1621
|
}
|
|
1632
|
-
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
["fs.tree", "fs.read", "fs.write", "fs.search"].forEach(function (m) {
|
|
1625
|
+
ctx.effect(() => ctx.webServer.register({
|
|
1626
|
+
kind: "exact",
|
|
1627
|
+
path: "/sidebar/api/" + m,
|
|
1628
|
+
handler: function (req, res) { return interceptFsHandler(req, res, m); }
|
|
1629
|
+
}), "dsh-remote-ssh: intercept /sidebar/api/" + m);
|
|
1630
|
+
});
|
|
1633
1631
|
|
|
1634
1632
|
// ---- 清理 ----
|
|
1635
1633
|
ctx.effect(() => () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhangfengshun/dsh-remote-ssh",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "DSH web plugin: VSCode Remote-SSH-like remote development (SSH to supercomputers/servers, remote workspace, file explorer, integrated terminal), integrated with dsh-better-sidebar and DSH settings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|