dsh-code-server-app 0.2.0 → 0.2.2
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 +50 -5
- package/README.md +45 -6
- package/lib/client.js +2 -2
- package/lib/index.js +10 -0
- package/lib/launcher.mjs +56 -0
- package/package.json +2 -2
- package/vendor/VENDOR.json +1 -1
package/lib/index.js
CHANGED
|
@@ -99,6 +99,9 @@ export const Config = z.object({
|
|
|
99
99
|
* (同源、无额外端口、复用 DSH 的 Host/Origin + cookie 防护;需要 DSH 提供 webServer 服务,
|
|
100
100
|
* 缺失或注册失败时自动回退 loopback)。 */
|
|
101
101
|
serve: z.union([z.const('loopback'), z.const('dsh')]).default('loopback'),
|
|
102
|
+
/** keepResident=true(默认)客户端在宿主 running 后把 IDE 预先加载到"停放区",
|
|
103
|
+
* 切标签/收起侧栏不再重载;false 则只在打开面板时才加载(省内存)。 */
|
|
104
|
+
keepResident: z.boolean().default(true),
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
const DEFAULT_CONFIG = {
|
|
@@ -384,6 +387,7 @@ export async function apply(ctx, config) {
|
|
|
384
387
|
let reserveComposer = true;
|
|
385
388
|
let windowedOpen = false;
|
|
386
389
|
let serveSetting = 'loopback';
|
|
390
|
+
let keepResident = true; // 客户端常驻预热(0.2.2)
|
|
387
391
|
if (settingsSvc !== undefined && typeof settingsSvc.register === 'function') {
|
|
388
392
|
try {
|
|
389
393
|
const scope = settingsSvc.register(SETTINGS_NS, Config);
|
|
@@ -393,6 +397,7 @@ export async function apply(ctx, config) {
|
|
|
393
397
|
reserveComposer = resolved && typeof resolved.reserveComposer === 'boolean' ? resolved.reserveComposer : true;
|
|
394
398
|
windowedOpen = resolved && typeof resolved.windowedOpen === 'boolean' ? resolved.windowedOpen : false;
|
|
395
399
|
serveSetting = resolved && resolved.serve === 'dsh' ? 'dsh' : 'loopback';
|
|
400
|
+
keepResident = resolved && typeof resolved.keepResident === 'boolean' ? resolved.keepResident : true;
|
|
396
401
|
}
|
|
397
402
|
scope.watch((next) => {
|
|
398
403
|
if (next != null && typeof next.reserveComposer === 'boolean') {
|
|
@@ -409,6 +414,10 @@ export async function apply(ctx, config) {
|
|
|
409
414
|
console.log(`[code-server] serve updated: ${serveSetting}(下次启动生效)`);
|
|
410
415
|
}
|
|
411
416
|
}
|
|
417
|
+
if (next != null && typeof next.keepResident === 'boolean') {
|
|
418
|
+
keepResident = next.keepResident;
|
|
419
|
+
console.log(`[code-server] keepResident updated: ${keepResident}`);
|
|
420
|
+
}
|
|
412
421
|
});
|
|
413
422
|
} catch (error) {
|
|
414
423
|
console.error(`[code-server] settings unavailable; using defaults (reserveComposer=true, windowedOpen=false): ${error.message}`);
|
|
@@ -502,6 +511,7 @@ export async function apply(ctx, config) {
|
|
|
502
511
|
adopted: state.adopted,
|
|
503
512
|
reserveComposer,
|
|
504
513
|
windowedOpen,
|
|
514
|
+
keepResident,
|
|
505
515
|
env: state.env,
|
|
506
516
|
setup: {
|
|
507
517
|
running: state.setup.running,
|
package/lib/launcher.mjs
CHANGED
|
@@ -207,6 +207,56 @@ function handleProxyUpgrade(req, socket, head, target) {
|
|
|
207
207
|
socket.on('close', () => upstream.destroy());
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
// ---------------------------------------------------------------- 跨源防护(等价 code-server 的 ensureOrigin)
|
|
211
|
+
|
|
212
|
+
/** 取请求 Host:优先 `Forwarded: host=`、其次 `X-Forwarded-Host`(取第一个)、最后 `Host`。
|
|
213
|
+
* trim + 小写,与 code-server 的 getHost() 同语义(反向代理未透传 Host 时返回 undefined)。 */
|
|
214
|
+
function getHostHeader(req) {
|
|
215
|
+
const first = (name) => {
|
|
216
|
+
const value = req.headers[name];
|
|
217
|
+
return Array.isArray(value) ? value[0] : value;
|
|
218
|
+
};
|
|
219
|
+
const forwarded = first('forwarded');
|
|
220
|
+
if (forwarded !== undefined && forwarded !== '') {
|
|
221
|
+
for (const part of forwarded.split(/[;,]/)) {
|
|
222
|
+
const eq = part.indexOf('=');
|
|
223
|
+
if (eq < 0) continue;
|
|
224
|
+
const key = part.slice(0, eq).trim().toLowerCase();
|
|
225
|
+
const value = part.slice(eq + 1).trim();
|
|
226
|
+
if (key === 'host' && value !== '') return value.toLowerCase();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const xHost = first('x-forwarded-host');
|
|
230
|
+
if (xHost !== undefined && xHost !== '') {
|
|
231
|
+
const head = xHost.split(',')[0];
|
|
232
|
+
if (head !== undefined && head.trim() !== '') return head.trim().toLowerCase();
|
|
233
|
+
}
|
|
234
|
+
const host = first('host');
|
|
235
|
+
return host !== undefined && host !== '' ? host.trim().toLowerCase() : undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** code-server `authenticateOrigin()` 的等价物:带 Origin 的请求(浏览器)其 host 必须等于 Host;
|
|
239
|
+
* 缺 Origin(非浏览器,如本地工具/测试)放行。code-server 另外支持 `--trusted-origins` /
|
|
240
|
+
* `--proxy-domain` 通配,本插件没有这两个概念,故不实现。
|
|
241
|
+
* 为什么必须有:VS Code 的 handleUpgrade 不校验来源,而 IDE 是 auth=none —— 缺了这道检查,
|
|
242
|
+
* 本机任意浏览器页面都能开 ws://127.0.0.1:<port>/stable-<commit> 直接驱动 IDE。
|
|
243
|
+
* (dsh 模式下请求来自 DSH 自己的路由并已过 requestRejection,Origin/Host 天然一致。) */
|
|
244
|
+
function originAllowed(req) {
|
|
245
|
+
const raw = req.headers.origin;
|
|
246
|
+
const originRaw = Array.isArray(raw) ? raw[0] : raw;
|
|
247
|
+
if (originRaw === undefined || originRaw === '') return true;
|
|
248
|
+
let origin;
|
|
249
|
+
try {
|
|
250
|
+
origin = new URL(originRaw).host.trim().toLowerCase();
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
if (origin === '') return false;
|
|
255
|
+
const host = getHostHeader(req);
|
|
256
|
+
if (host === undefined) return false;
|
|
257
|
+
return host === origin;
|
|
258
|
+
}
|
|
259
|
+
|
|
210
260
|
// ---------------------------------------------------------------- HTTP / WS 分发
|
|
211
261
|
|
|
212
262
|
const server = createHttpServer((req, res) => {
|
|
@@ -243,6 +293,12 @@ server.on('upgrade', (req, socket, head) => {
|
|
|
243
293
|
const url = req.url ?? '/';
|
|
244
294
|
recentUpgrades.push(url);
|
|
245
295
|
if (recentUpgrades.length > 10) recentUpgrades.shift();
|
|
296
|
+
// 跨源防护(与 code-server 一致:只卡 upgrade,HTTP 侧 VS Code 仅接受 GET 且状态变更都走 WS)
|
|
297
|
+
if (!originAllowed(req)) {
|
|
298
|
+
log(`拒绝跨源 WebSocket:origin=${req.headers.origin} host=${req.headers.host ?? '-'} url=${url}`);
|
|
299
|
+
socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
246
302
|
if (!args.disableProxy) {
|
|
247
303
|
const target = proxyTarget(url);
|
|
248
304
|
if (target !== null) { handleProxyUpgrade(req, socket, head, target); return; }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-code-server-app",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "VS Code (from a code-server release) inside DSH Web and Desktop: right-sidebar tab with a floating-ball fallback, driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs). Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). No code-server Node layer, no argon2, no C++ toolchain.",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "VS Code (from a code-server release) inside DSH Web and Desktop: right-sidebar tab with a floating-ball fallback, driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs). The IDE is a resident surface moved with Element.moveBefore instead of being remounted, so switching sidebar tabs no longer reloads the workbench. Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). No code-server Node layer, no argon2, no C++ toolchain.",
|
|
5
5
|
"homepage": "https://github.com/jinsiyu/dsh-code-server-app",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/vendor/VENDOR.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"vscodeVersion": "1.136.1",
|
|
4
4
|
"productPath": "stable-8d5f383f301ca20681f5b6606b8207d9dc87bdd8",
|
|
5
5
|
"layout": "vscode-only",
|
|
6
|
-
"preparedAt": "2026-09-
|
|
6
|
+
"preparedAt": "2026-09-10T13:43:43.880Z",
|
|
7
7
|
"source": "registry",
|
|
8
8
|
"node": "v24.13.1",
|
|
9
9
|
"platform": "win32",
|