dsh-vscode-workbench 0.1.7 → 0.1.9
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.en.md +112 -31
- package/README.md +113 -31
- package/lib/client.js +45162 -3381
- package/lib/client.js.map +1 -1
- package/lib/index.js +286 -61
- package/lib/index.js.map +1 -1
- package/lib/types/client/Icon.d.ts +1 -1
- package/lib/types/client/TerminalPanel.d.ts +3 -0
- package/lib/types/client/Workbench.d.ts +6 -2
- package/lib/types/client/documentPreview.d.ts +11 -0
- package/lib/types/client/index.d.ts +1 -11
- package/lib/types/client/session.d.ts +3 -0
- package/lib/types/client/shortcuts.d.ts +4 -0
- package/lib/types/client/terminal.d.ts +43 -0
- package/lib/types/context.d.ts +8 -8
- package/lib/types/host.d.ts +10 -0
- package/lib/types/terminal.d.ts +41 -0
- package/lib/types/wire.d.ts +3 -1
- package/package.json +33 -16
- package/src/client/DshConversationPanel.tsx +2 -2
- package/src/client/Icon.tsx +1 -1
- package/src/client/TerminalPanel.tsx +90 -0
- package/src/client/Workbench.tsx +9 -8
- package/src/client/documentPreview.tsx +122 -0
- package/src/client/index.tsx +11 -4
- package/src/client/session.ts +7 -0
- package/src/client/shortcuts.ts +17 -1
- package/src/client/styles.ts +9 -1
- package/src/client/terminal.ts +83 -0
- package/src/context.ts +5 -3
- package/src/host.ts +11 -2
- package/src/index.ts +26 -11
- package/src/terminal.ts +148 -0
- package/src/wire.ts +8 -10
package/lib/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { access, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
4
4
|
import chokidar from "chokidar";
|
|
5
5
|
import { WebSocket, WebSocketServer } from "ws";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
|
-
import { constants } from "node:fs";
|
|
8
|
+
import { constants, realpathSync } from "node:fs";
|
|
9
|
+
import * as pty from "node-pty";
|
|
9
10
|
//#region src/wire.ts
|
|
10
11
|
var WorkbenchError = class extends Error {
|
|
11
12
|
code;
|
|
@@ -67,23 +68,19 @@ function sendError(res, error) {
|
|
|
67
68
|
}
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return new URL(origin).host === hostUrl.host;
|
|
84
|
-
} catch {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
71
|
+
/** Reuse Harness authentication and its deployment-aware Host/Origin fence. */
|
|
72
|
+
function authorize(req, res, connection) {
|
|
73
|
+
const rejection = connection.requestRejection(req);
|
|
74
|
+
if (rejection === void 0) return true;
|
|
75
|
+
const code = rejection === 401 ? "unauthorized" : "forbidden";
|
|
76
|
+
send(res, rejection, {
|
|
77
|
+
ok: false,
|
|
78
|
+
error: {
|
|
79
|
+
code,
|
|
80
|
+
message: code
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return false;
|
|
87
84
|
}
|
|
88
85
|
//#endregion
|
|
89
86
|
//#region src/host.ts
|
|
@@ -91,8 +88,9 @@ const require = createRequire(import.meta.url);
|
|
|
91
88
|
const TEXT_LIMIT = 5242880;
|
|
92
89
|
const PROCESS_TIMEOUT = 3e4;
|
|
93
90
|
function cwdFor(ctx, sessionId) {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
91
|
+
const session = ctx.sessions.get(sessionId);
|
|
92
|
+
if (session === void 0) throw new WorkbenchError("session-not-found", "session not found", 404);
|
|
93
|
+
const cwd = ctx.workingDirectory.get(session);
|
|
96
94
|
if (!isAbsolute(cwd)) throw new WorkbenchError("cwd-invalid", "session working directory is not absolute");
|
|
97
95
|
return normalize(cwd);
|
|
98
96
|
}
|
|
@@ -231,7 +229,7 @@ async function deleteEntry(cwd, path) {
|
|
|
231
229
|
force: false
|
|
232
230
|
});
|
|
233
231
|
}
|
|
234
|
-
const
|
|
232
|
+
const PREVIEW_TYPES = {
|
|
235
233
|
".avif": "image/avif",
|
|
236
234
|
".bmp": "image/bmp",
|
|
237
235
|
".gif": "image/gif",
|
|
@@ -240,18 +238,29 @@ const IMAGE_TYPES = {
|
|
|
240
238
|
".jpg": "image/jpeg",
|
|
241
239
|
".png": "image/png",
|
|
242
240
|
".svg": "image/svg+xml",
|
|
243
|
-
".webp": "image/webp"
|
|
241
|
+
".webp": "image/webp",
|
|
242
|
+
".pdf": "application/pdf",
|
|
243
|
+
".doc": "application/msword",
|
|
244
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
245
|
+
".odt": "application/vnd.oasis.opendocument.text"
|
|
244
246
|
};
|
|
245
|
-
/** Resolve
|
|
246
|
-
async function
|
|
247
|
+
/** Resolve a supported preview file inside the workspace. */
|
|
248
|
+
async function workspacePreviewFile(cwd, path) {
|
|
247
249
|
const resolved = await workspacePath(cwd, path);
|
|
248
|
-
const type =
|
|
249
|
-
if (type === void 0) throw new WorkbenchError("unsupported-media", "file is not a supported
|
|
250
|
+
const type = PREVIEW_TYPES[extname(resolved.absolute).toLowerCase()];
|
|
251
|
+
if (type === void 0) throw new WorkbenchError("unsupported-media", "file is not a supported preview format", 415);
|
|
250
252
|
return {
|
|
251
253
|
path: resolved.absolute,
|
|
252
254
|
type
|
|
253
255
|
};
|
|
254
256
|
}
|
|
257
|
+
/** Resolve the PDF.js worker shipped with this plugin's runtime dependency. */
|
|
258
|
+
function pdfWorkerAsset() {
|
|
259
|
+
return {
|
|
260
|
+
path: require.resolve("pdfjs-dist/legacy/build/pdf.worker.min.mjs"),
|
|
261
|
+
type: "text/javascript; charset=utf-8"
|
|
262
|
+
};
|
|
263
|
+
}
|
|
255
264
|
function run(command, args, cwd, timeout = PROCESS_TIMEOUT, signal) {
|
|
256
265
|
return new Promise((resolvePromise, reject) => {
|
|
257
266
|
const child = spawn(command, args, {
|
|
@@ -692,13 +701,229 @@ function monacoAsset(raw) {
|
|
|
692
701
|
};
|
|
693
702
|
}
|
|
694
703
|
//#endregion
|
|
704
|
+
//#region src/terminal.ts
|
|
705
|
+
const SENSITIVE = /(api[_-]?key|token|credential|secret|password|passwd|private[_-]?key|^dsh_.*(?:key|token|secret))/i;
|
|
706
|
+
const MAX_TERMINALS = 8;
|
|
707
|
+
const REPLAY_BYTES = 2097152;
|
|
708
|
+
/** Remove credentials while retaining ordinary interactive-shell variables. */
|
|
709
|
+
function sanitizedTerminalEnv(env) {
|
|
710
|
+
return Object.fromEntries(Object.entries(env).filter(([key, value]) => value !== void 0 && !SENSITIVE.test(key)));
|
|
711
|
+
}
|
|
712
|
+
/** Select the user's interactive shell with platform-specific fallbacks. */
|
|
713
|
+
function defaultTerminalShell(platform = process.platform, env = process.env) {
|
|
714
|
+
if (platform === "win32") return {
|
|
715
|
+
shell: env.COMSPEC?.toLowerCase().includes("powershell") ? env.COMSPEC : "powershell.exe",
|
|
716
|
+
args: ["-NoLogo"]
|
|
717
|
+
};
|
|
718
|
+
return {
|
|
719
|
+
shell: env.SHELL || (platform === "darwin" ? "/bin/zsh" : "/bin/bash"),
|
|
720
|
+
args: ["-l"]
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/** Own interactive terminals by their canonical workspace directory. */
|
|
724
|
+
var TerminalManager = class {
|
|
725
|
+
workspaces = /* @__PURE__ */ new Map();
|
|
726
|
+
workspace(cwd) {
|
|
727
|
+
return realpathSync(cwd);
|
|
728
|
+
}
|
|
729
|
+
list(cwd) {
|
|
730
|
+
return [...this.workspaces.get(this.workspace(cwd))?.values() ?? []].map((handle) => ({ ...handle.info }));
|
|
731
|
+
}
|
|
732
|
+
create(cwd, title) {
|
|
733
|
+
const key = this.workspace(cwd);
|
|
734
|
+
const terminals = this.workspaces.get(key) ?? /* @__PURE__ */ new Map();
|
|
735
|
+
this.workspaces.set(key, terminals);
|
|
736
|
+
if (terminals.size >= MAX_TERMINALS) throw new Error(`terminal limit reached (${MAX_TERMINALS})`);
|
|
737
|
+
const { shell, args } = defaultTerminalShell();
|
|
738
|
+
const child = pty.spawn(shell, args, {
|
|
739
|
+
name: "xterm-256color",
|
|
740
|
+
cols: 80,
|
|
741
|
+
rows: 24,
|
|
742
|
+
cwd: key,
|
|
743
|
+
env: sanitizedTerminalEnv(process.env)
|
|
744
|
+
});
|
|
745
|
+
const info = {
|
|
746
|
+
id: randomUUID(),
|
|
747
|
+
title: title || basename(shell),
|
|
748
|
+
cwd: key,
|
|
749
|
+
running: true,
|
|
750
|
+
sequence: 0
|
|
751
|
+
};
|
|
752
|
+
const handle = {
|
|
753
|
+
info,
|
|
754
|
+
process: child,
|
|
755
|
+
replay: "",
|
|
756
|
+
listeners: /* @__PURE__ */ new Set()
|
|
757
|
+
};
|
|
758
|
+
terminals.set(info.id, handle);
|
|
759
|
+
child.onData((data) => {
|
|
760
|
+
info.sequence++;
|
|
761
|
+
handle.replay += data;
|
|
762
|
+
if (Buffer.byteLength(handle.replay) > REPLAY_BYTES) handle.replay = handle.replay.slice(-2097152);
|
|
763
|
+
for (const listener of handle.listeners) listener({
|
|
764
|
+
sequence: info.sequence,
|
|
765
|
+
data
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
child.onExit(({ exitCode }) => {
|
|
769
|
+
info.running = false;
|
|
770
|
+
info.exitCode = exitCode;
|
|
771
|
+
info.sequence++;
|
|
772
|
+
const data = `\r\n[process exited with code ${exitCode}]\r\n`;
|
|
773
|
+
for (const listener of handle.listeners) listener({
|
|
774
|
+
sequence: info.sequence,
|
|
775
|
+
data
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
return { ...info };
|
|
779
|
+
}
|
|
780
|
+
get(cwd, id) {
|
|
781
|
+
return this.workspaces.get(this.workspace(cwd))?.get(id);
|
|
782
|
+
}
|
|
783
|
+
close(cwd, id) {
|
|
784
|
+
const key = this.workspace(cwd);
|
|
785
|
+
const terminals = this.workspaces.get(key);
|
|
786
|
+
const handle = terminals?.get(id);
|
|
787
|
+
if (handle === void 0 || terminals === void 0) return;
|
|
788
|
+
terminals.delete(id);
|
|
789
|
+
try {
|
|
790
|
+
handle.process.kill();
|
|
791
|
+
} catch {}
|
|
792
|
+
if (terminals.size === 0) this.workspaces.delete(key);
|
|
793
|
+
}
|
|
794
|
+
rename(cwd, id, title) {
|
|
795
|
+
const handle = this.get(cwd, id);
|
|
796
|
+
if (handle === void 0) throw new Error("terminal not found");
|
|
797
|
+
handle.info.title = title;
|
|
798
|
+
return { ...handle.info };
|
|
799
|
+
}
|
|
800
|
+
dispose() {
|
|
801
|
+
for (const [cwd, terminals] of this.workspaces) for (const id of terminals.keys()) this.close(cwd, id);
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
function terminalRequestAllowed(req) {
|
|
805
|
+
const host = req.headers.host;
|
|
806
|
+
if (host === void 0 || req.headers["sec-fetch-site"] === "cross-site") return false;
|
|
807
|
+
let hostname;
|
|
808
|
+
try {
|
|
809
|
+
hostname = new URL(`http://${host}`).hostname;
|
|
810
|
+
} catch {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
if (hostname !== "localhost" && hostname !== "[::1]" && !hostname.startsWith("127.")) return false;
|
|
814
|
+
const origin = req.headers.origin;
|
|
815
|
+
try {
|
|
816
|
+
return origin === void 0 || new URL(origin).host === host;
|
|
817
|
+
} catch {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/** Register the private local terminal API and WebSocket transport. */
|
|
822
|
+
function installTerminal(ctx) {
|
|
823
|
+
const manager = new TerminalManager();
|
|
824
|
+
ctx.effect(() => ctx.webServer.register({
|
|
825
|
+
kind: "prefix",
|
|
826
|
+
path: "/dsh-vscode-terminal/api",
|
|
827
|
+
handler: async (req, res) => {
|
|
828
|
+
if (!authorize(req, res, ctx.connection)) return;
|
|
829
|
+
if (!terminalRequestAllowed(req)) return send(res, 403, {
|
|
830
|
+
ok: false,
|
|
831
|
+
error: { message: "forbidden" }
|
|
832
|
+
});
|
|
833
|
+
if (req.method !== "POST") return send(res, 405, {
|
|
834
|
+
ok: false,
|
|
835
|
+
error: { message: "POST required" }
|
|
836
|
+
});
|
|
837
|
+
try {
|
|
838
|
+
const body = await readBody(req);
|
|
839
|
+
const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
|
|
840
|
+
if (sessionId === "") throw new Error("sessionId required");
|
|
841
|
+
const cwd = cwdFor(ctx, sessionId);
|
|
842
|
+
const method = new URL(req.url ?? "/", "http://dsh.local").pathname.slice(25);
|
|
843
|
+
let value;
|
|
844
|
+
if (method === "list") value = manager.list(cwd);
|
|
845
|
+
else if (method === "create") value = manager.create(cwd, typeof body.title === "string" ? body.title : void 0);
|
|
846
|
+
else if (method === "close") {
|
|
847
|
+
manager.close(cwd, typeof body.id === "string" ? body.id : "");
|
|
848
|
+
value = manager.list(cwd);
|
|
849
|
+
} else if (method === "rename") value = manager.rename(cwd, typeof body.id === "string" ? body.id : "", typeof body.title === "string" ? body.title : "");
|
|
850
|
+
else throw new Error("unknown terminal method");
|
|
851
|
+
send(res, 200, {
|
|
852
|
+
ok: true,
|
|
853
|
+
value
|
|
854
|
+
});
|
|
855
|
+
} catch (error) {
|
|
856
|
+
sendError(res, error);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}), "dsh-vscode-workbench: terminal API");
|
|
860
|
+
const webSocket = new WebSocketServer({ noServer: true });
|
|
861
|
+
ctx.effect(() => ctx.webServer.registerUpgrade({
|
|
862
|
+
path: "/dsh-vscode-terminal/ws",
|
|
863
|
+
handler(req, socket, head) {
|
|
864
|
+
if (ctx.connection.requestRejection(req) !== void 0 || !terminalRequestAllowed(req)) {
|
|
865
|
+
socket.destroy();
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
webSocket.handleUpgrade(req, socket, head, (connection) => {
|
|
869
|
+
const url = new URL(req.url ?? "/", "http://dsh.local");
|
|
870
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
871
|
+
const id = url.searchParams.get("id");
|
|
872
|
+
if (sessionId === null || id === null) {
|
|
873
|
+
connection.close(1008, "sessionId and id required");
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
let handle;
|
|
877
|
+
try {
|
|
878
|
+
handle = manager.get(cwdFor(ctx, sessionId), id);
|
|
879
|
+
} catch {
|
|
880
|
+
connection.close(1008, "session workspace unavailable");
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
if (handle === void 0) {
|
|
884
|
+
connection.close(1008, "terminal not found");
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
if (handle.replay !== "") connection.send(JSON.stringify({
|
|
888
|
+
type: "replay",
|
|
889
|
+
sequence: handle.info.sequence,
|
|
890
|
+
data: handle.replay
|
|
891
|
+
}));
|
|
892
|
+
const listener = (output) => {
|
|
893
|
+
if (connection.readyState === WebSocket.OPEN) connection.send(JSON.stringify({
|
|
894
|
+
type: "output",
|
|
895
|
+
...output
|
|
896
|
+
}));
|
|
897
|
+
};
|
|
898
|
+
handle.listeners.add(listener);
|
|
899
|
+
connection.on("message", (raw) => {
|
|
900
|
+
const text = raw.toString();
|
|
901
|
+
try {
|
|
902
|
+
const message = JSON.parse(text);
|
|
903
|
+
if (message !== null && typeof message === "object" && "type" in message && message.type === "input" && "data" in message && typeof message.data === "string") handle.process.write(message.data);
|
|
904
|
+
else if (message !== null && typeof message === "object" && "type" in message && message.type === "resize" && "cols" in message && "rows" in message && typeof message.cols === "number" && typeof message.rows === "number") handle.process.resize(Math.max(2, Math.floor(message.cols)), Math.max(2, Math.floor(message.rows)));
|
|
905
|
+
} catch {
|
|
906
|
+
handle.process.write(text);
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
connection.on("close", () => handle.listeners.delete(listener));
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}), "dsh-vscode-workbench: terminal WebSocket");
|
|
913
|
+
ctx.effect(() => () => {
|
|
914
|
+
webSocket.close();
|
|
915
|
+
manager.dispose();
|
|
916
|
+
}, "dsh-vscode-workbench: terminal teardown");
|
|
917
|
+
}
|
|
918
|
+
//#endregion
|
|
695
919
|
//#region src/index.ts
|
|
696
920
|
/** DSH host plugin for the VS Code workbench. */
|
|
697
921
|
const name = "dsh-vscode-workbench";
|
|
698
922
|
const inject = [
|
|
699
923
|
"webServer",
|
|
700
924
|
"sessions",
|
|
701
|
-
"
|
|
925
|
+
"connection",
|
|
926
|
+
"workingDirectory"
|
|
702
927
|
];
|
|
703
928
|
async function dispatch(ctx, method, body, signal) {
|
|
704
929
|
const cwd = cwdFor(ctx, stringField(body, "sessionId"));
|
|
@@ -720,15 +945,12 @@ async function dispatch(ctx, method, body, signal) {
|
|
|
720
945
|
}
|
|
721
946
|
}
|
|
722
947
|
function apply(ctx) {
|
|
948
|
+
installTerminal(ctx);
|
|
723
949
|
ctx.effect(() => ctx.webServer.register({
|
|
724
950
|
kind: "exact",
|
|
725
951
|
path: "/dsh-vscode/codicons/codicon.ttf",
|
|
726
952
|
handler: async (req, res) => {
|
|
727
|
-
if (!
|
|
728
|
-
res.writeHead(403);
|
|
729
|
-
res.end();
|
|
730
|
-
return;
|
|
731
|
-
}
|
|
953
|
+
if (!authorize(req, res, ctx.connection)) return;
|
|
732
954
|
try {
|
|
733
955
|
const asset = codiconAsset();
|
|
734
956
|
res.writeHead(200, {
|
|
@@ -741,17 +963,28 @@ function apply(ctx) {
|
|
|
741
963
|
}
|
|
742
964
|
}
|
|
743
965
|
}), "dsh-vscode-workbench: Codicon font");
|
|
966
|
+
ctx.effect(() => ctx.webServer.register({
|
|
967
|
+
kind: "exact",
|
|
968
|
+
path: "/dsh-vscode/pdfjs/worker.mjs",
|
|
969
|
+
handler: async (req, res) => {
|
|
970
|
+
if (!authorize(req, res, ctx.connection)) return;
|
|
971
|
+
try {
|
|
972
|
+
const asset = pdfWorkerAsset();
|
|
973
|
+
res.writeHead(200, {
|
|
974
|
+
"content-type": asset.type,
|
|
975
|
+
"cache-control": "public, max-age=31536000, immutable"
|
|
976
|
+
});
|
|
977
|
+
res.end(await readFile(asset.path));
|
|
978
|
+
} catch (error) {
|
|
979
|
+
sendError(res, error);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}), "dsh-vscode-workbench: PDF.js worker");
|
|
744
983
|
ctx.effect(() => ctx.webServer.register({
|
|
745
984
|
kind: "prefix",
|
|
746
985
|
path: "/dsh-vscode/api",
|
|
747
986
|
handler: async (req, res) => {
|
|
748
|
-
if (!
|
|
749
|
-
ok: false,
|
|
750
|
-
error: {
|
|
751
|
-
code: "forbidden",
|
|
752
|
-
message: "forbidden"
|
|
753
|
-
}
|
|
754
|
-
});
|
|
987
|
+
if (!authorize(req, res, ctx.connection)) return;
|
|
755
988
|
if (req.method !== "POST") return send(res, 405, {
|
|
756
989
|
ok: false,
|
|
757
990
|
error: {
|
|
@@ -775,13 +1008,7 @@ function apply(ctx) {
|
|
|
775
1008
|
kind: "exact",
|
|
776
1009
|
path: "/dsh-vscode/upload",
|
|
777
1010
|
handler: async (req, res) => {
|
|
778
|
-
if (!
|
|
779
|
-
ok: false,
|
|
780
|
-
error: {
|
|
781
|
-
code: "forbidden",
|
|
782
|
-
message: "forbidden"
|
|
783
|
-
}
|
|
784
|
-
});
|
|
1011
|
+
if (!authorize(req, res, ctx.connection)) return;
|
|
785
1012
|
if (req.method !== "POST") return send(res, 405, {
|
|
786
1013
|
ok: false,
|
|
787
1014
|
error: {
|
|
@@ -808,17 +1035,13 @@ function apply(ctx) {
|
|
|
808
1035
|
kind: "prefix",
|
|
809
1036
|
path: "/dsh-vscode/file",
|
|
810
1037
|
handler: async (req, res) => {
|
|
811
|
-
if (!
|
|
812
|
-
res.writeHead(403);
|
|
813
|
-
res.end();
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
1038
|
+
if (!authorize(req, res, ctx.connection)) return;
|
|
816
1039
|
try {
|
|
817
1040
|
const url = new URL(req.url ?? "/", "http://dsh.local");
|
|
818
1041
|
const sessionId = url.searchParams.get("sessionId");
|
|
819
1042
|
const path = url.searchParams.get("path");
|
|
820
1043
|
if (sessionId === null || path === null) throw new WorkbenchError("bad-request", "sessionId and path are required");
|
|
821
|
-
const asset = await
|
|
1044
|
+
const asset = await workspacePreviewFile(cwdFor(ctx, sessionId), path);
|
|
822
1045
|
res.writeHead(200, {
|
|
823
1046
|
"content-type": asset.type,
|
|
824
1047
|
"x-content-type-options": "nosniff",
|
|
@@ -829,16 +1052,12 @@ function apply(ctx) {
|
|
|
829
1052
|
sendError(res, error);
|
|
830
1053
|
}
|
|
831
1054
|
}
|
|
832
|
-
}), "dsh-vscode-workbench:
|
|
1055
|
+
}), "dsh-vscode-workbench: file preview");
|
|
833
1056
|
ctx.effect(() => ctx.webServer.register({
|
|
834
1057
|
kind: "prefix",
|
|
835
1058
|
path: "/dsh-vscode/monaco",
|
|
836
1059
|
handler: async (req, res) => {
|
|
837
|
-
if (!
|
|
838
|
-
res.writeHead(403);
|
|
839
|
-
res.end();
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
1060
|
+
if (!authorize(req, res, ctx.connection)) return;
|
|
842
1061
|
try {
|
|
843
1062
|
const asset = monacoAsset(decodeURIComponent(new URL(req.url ?? "/", "http://dsh.local").pathname.slice(19)));
|
|
844
1063
|
res.writeHead(200, {
|
|
@@ -855,7 +1074,7 @@ function apply(ctx) {
|
|
|
855
1074
|
ctx.effect(() => ctx.webServer.registerUpgrade({
|
|
856
1075
|
path: "/dsh-vscode/ws/files",
|
|
857
1076
|
handler(req, socket, head) {
|
|
858
|
-
if (
|
|
1077
|
+
if (ctx.connection.requestRejection(req) !== void 0) {
|
|
859
1078
|
socket.destroy();
|
|
860
1079
|
return;
|
|
861
1080
|
}
|
|
@@ -865,7 +1084,13 @@ function apply(ctx) {
|
|
|
865
1084
|
ws.close(1008, "sessionId required");
|
|
866
1085
|
return;
|
|
867
1086
|
}
|
|
868
|
-
|
|
1087
|
+
let workspace;
|
|
1088
|
+
try {
|
|
1089
|
+
workspace = cwdFor(ctx, sessionId);
|
|
1090
|
+
} catch {
|
|
1091
|
+
ws.close(1008, "session workspace unavailable");
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
869
1094
|
const watcher = chokidar.watch(workspace, {
|
|
870
1095
|
ignoreInitial: true,
|
|
871
1096
|
ignored: /(^|[/\\])(\.git|node_modules|\.next|\.nuxt|\.turbo|\.vite|coverage)([/\\]|$)/,
|