Skip to content

Commit 2554c19

Browse files
committed
Add http server.
Add deletion patch and fix tests. Break import cycle between main and cli. Cleanup
1 parent 3fd9f88 commit 2554c19

File tree

9 files changed

+642
-316
lines changed

9 files changed

+642
-316
lines changed

patches/store-socket.diff

+87-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Store a static reference to the IPC socket
1+
Store the IPC socket with workspace metadata.
22

33
This lets us use it to open files inside code-server from outside of
44
code-server.
@@ -9,6 +9,8 @@ To test this:
99

1010
It should open in your existing code-server instance.
1111

12+
When the extension host is terminated, the socket is unregistered.
13+
1214
Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
1315
===================================================================
1416
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -18,8 +20,8 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
1820
* Licensed under the MIT License. See License.txt in the project root for license information.
1921
*--------------------------------------------------------------------------------------------*/
2022
-
21-
+import { promises as fs } from 'fs';
22-
+import * as os from 'os'
23+
+import * as os from 'os';
24+
+import * as _http from 'http';
2325
+import * as path from 'vs/base/common/path';
2426
import * as performance from 'vs/base/common/performance';
2527
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
@@ -32,12 +34,12 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
3234

3335
class NodeModuleRequireInterceptor extends RequireInterceptor {
3436

35-
@@ -79,6 +82,24 @@ export class ExtHostExtensionService ext
37+
@@ -79,6 +82,59 @@ export class ExtHostExtensionService ext
3638
await interceptor.install();
3739
performance.mark('code/extHost/didInitAPI');
3840

3941
+ (async () => {
40-
+ let socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
42+
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
4143
+ if (!socketPath) {
4244
+ return;
4345
+ }
@@ -49,11 +51,90 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
4951
+ workspace,
5052
+ socketPath
5153
+ };
52-
+ fs.appendFile(path.join(os.tmpdir(), 'vscode-ipc'), '\n' + JSON.stringify(entry), 'utf-8');
54+
+ const message = JSON.stringify({entry});
55+
+ const codeServerSocketPath = path.join(os.tmpdir(), 'code-server-ipc.sock');
56+
+ await new Promise<void>((resolve, reject) => {
57+
+ const opts: _http.RequestOptions = {
58+
+ path: '/add-session',
59+
+ socketPath: codeServerSocketPath,
60+
+ method: 'POST',
61+
+ headers: {
62+
+ 'content-type': 'application/json',
63+
+ 'accept': 'application/json'
64+
+ }
65+
+ };
66+
+ const req = _http.request(opts, (res) => {
67+
+ if (res.headers['content-type'] !== 'application/json') {
68+
+ reject('Error in response: Invalid content type: Expected \'application/json\', is: ' + res.headers['content-type']);
69+
+ return;
70+
+ }
71+
+
72+
+ res.setEncoding('utf8');
73+
+ res.on('error', reject);
74+
+ res.on('end', () => {
75+
+ try {
76+
+ if (res.statusCode === 200) {
77+
+ resolve();
78+
+ } else {
79+
+ reject(new Error('Unexpected status code: ' + res.statusCode));
80+
+ }
81+
+ } catch (e: unknown) {
82+
+ reject(e);
83+
+ }
84+
+ });
85+
+ });
86+
+ req.on('error', reject);
87+
+ req.write(message);
88+
+ req.end();
89+
+ });
5390
+ })().catch(error => {
5491
+ this._logService.error(error);
5592
+ });
5693
+
5794
// Do this when extension service exists, but extensions are not being activated yet.
5895
const configProvider = await this._extHostConfiguration.getConfigProvider();
5996
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
97+
Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
98+
===================================================================
99+
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
100+
+++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
101+
@@ -3,6 +3,9 @@
102+
* Licensed under the MIT License. See License.txt in the project root for license information.
103+
*--------------------------------------------------------------------------------------------*/
104+
105+
+import * as os from 'os';
106+
+import * as _http from 'http';
107+
+import * as path from 'vs/base/common/path';
108+
import * as nativeWatchdog from 'native-watchdog';
109+
import * as net from 'net';
110+
import * as minimist from 'minimist';
111+
@@ -400,7 +403,28 @@ async function startExtensionHostProcess
112+
);
113+
114+
// rewrite onTerminate-function to be a proper shutdown
115+
- onTerminate = (reason: string) => extensionHostMain.terminate(reason);
116+
+ onTerminate = (reason: string) => {
117+
+ extensionHostMain.terminate(reason);
118+
+
119+
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
120+
+ if (!socketPath) {
121+
+ return;
122+
+ }
123+
+ const message = JSON.stringify({socketPath});
124+
+ const codeServerSocketPath = path.join(os.tmpdir(), 'code-server-ipc.sock');
125+
+ const opts: _http.RequestOptions = {
126+
+ path: '/delete-session',
127+
+ socketPath: codeServerSocketPath,
128+
+ method: 'POST',
129+
+ headers: {
130+
+ 'content-type': 'application/json',
131+
+ 'accept': 'application/json'
132+
+ }
133+
+ };
134+
+ const req = _http.request(opts);
135+
+ req.write(message);
136+
+ req.end();
137+
+ };
138+
}
139+
140+
startExtensionHostProcess().catch((err) => console.log(err));

src/node/app.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import * as util from "../common/util"
99
import { DefaultedArgs } from "./cli"
1010
import { disposer } from "./http"
1111
import { isNodeJSErrnoException } from "./util"
12+
import { DEFAULT_SOCKET_PATH, EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
1213
import { handleUpgrade } from "./wsRouter"
1314

14-
type ListenOptions = Pick<DefaultedArgs, "socket-mode" | "socket" | "port" | "host">
15+
type ListenOptions = Pick<Partial<DefaultedArgs>, "socket-mode" | "socket" | "port" | "host">
1516

1617
export interface App extends Disposable {
1718
/** Handles regular HTTP requests. */
@@ -20,6 +21,8 @@ export interface App extends Disposable {
2021
wsRouter: Express
2122
/** The underlying HTTP server. */
2223
server: http.Server
24+
/** Handles requests to the editor session management API. */
25+
editorSessionManagerServer: http.Server
2326
}
2427

2528
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
@@ -42,7 +45,7 @@ export const listen = async (server: http.Server, { host, port, socket, "socket-
4245
server.listen(socket, onListen)
4346
} else {
4447
// [] is the correct format when using :: but Node errors with them.
45-
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
48+
server.listen(port, host!.replace(/^\[|\]$/g, ""), onListen)
4649
}
4750
})
4851

@@ -70,14 +73,22 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
7073
)
7174
: http.createServer(router)
7275

73-
const dispose = disposer(server)
76+
const disposeServer = disposer(server)
7477

7578
await listen(server, args)
7679

7780
const wsRouter = express()
7881
handleUpgrade(wsRouter, server)
7982

80-
return { router, wsRouter, server, dispose }
83+
const editorSessionManager = new EditorSessionManager()
84+
const editorSessionManagerServer = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, editorSessionManager)
85+
const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer)
86+
87+
const dispose = async () => {
88+
await Promise.all([disposeServer(), disposeEditorSessionManagerServer()])
89+
}
90+
91+
return { router, wsRouter, server, dispose, editorSessionManagerServer }
8192
}
8293

8394
/**

src/node/cli.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { load } from "js-yaml"
44
import * as os from "os"
55
import * as path from "path"
66
import { generateCertificate, generatePassword, humanPath, paths, splitOnFirstEquals } from "./util"
7-
import { DEFAULT_SOCKET_PATH, VscodeSocketResolver } from "./vscodeSocket"
8-
import { getResolvedPathsFromArgs } from "./main"
7+
import { DEFAULT_SOCKET_PATH, EditorSessionManagerClient } from "./vscodeSocket"
98

109
export enum Feature {
1110
// No current experimental features!
@@ -571,9 +570,7 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
571570
process.env.VSCODE_PROXY_URI = `{{port}}.${args["proxy-domain"][0]}`
572571
}
573572

574-
if (typeof args._ === "undefined") {
575-
args._ = []
576-
}
573+
args._ = getResolvedPathsFromArgs(args)
577574

578575
return {
579576
...args,
@@ -582,6 +579,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
582579
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
583580
}
584581

582+
export function getResolvedPathsFromArgs(args: UserProvidedArgs): string[] {
583+
return (args._ ?? []).map((p) => path.resolve(p))
584+
}
585+
585586
/**
586587
* Helper function to return the default config file.
587588
*
@@ -734,7 +735,12 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
734735
}
735736

736737
const paths = getResolvedPathsFromArgs(args)
737-
const resolver = new VscodeSocketResolver(DEFAULT_SOCKET_PATH)
738+
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH)
739+
740+
// If we can't connect to the socket then there's no existing instance.
741+
if (!(await client.canConnect())) {
742+
return undefined
743+
}
738744

739745
// If these flags are set then assume the user is trying to open in an
740746
// existing instance since these flags have no effect otherwise.
@@ -743,8 +749,7 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
743749
}, 0)
744750
if (openInFlagCount > 0) {
745751
logger.debug("Found --reuse-window or --new-window")
746-
await resolver.load()
747-
return await resolver.getConnectedSocketPath(paths)
752+
return await client.getConnectedSocketPath(paths[0])
748753
}
749754

750755
// It's possible the user is trying to spawn another instance of code-server.
@@ -753,8 +758,7 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
753758
// 2. That a file or directory was passed.
754759
// 3. That the socket is active.
755760
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
756-
await resolver.load()
757-
const socketPath = await resolver.getConnectedSocketPath(paths)
761+
const socketPath = await client.getConnectedSocketPath(paths[0])
758762
if (socketPath) {
759763
logger.debug("Found existing code-server socket")
760764
return socketPath

src/node/main.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { field, logger } from "@coder/logger"
22
import http from "http"
33
import * as os from "os"
4-
import path from "path"
54
import { Disposable } from "../common/emitter"
65
import { plural } from "../common/util"
76
import { createApp, ensureAddress } from "./app"
@@ -61,11 +60,6 @@ export const runCodeCli = async (args: DefaultedArgs): Promise<void> => {
6160
process.exit(0)
6261
}
6362

64-
export const getResolvedPathsFromArgs = (args: UserProvidedArgs): string[] => {
65-
const paths = args._ || []
66-
return paths.map((p) => path.resolve(p))
67-
}
68-
6963
export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
7064
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = {
7165
type: "open",
@@ -75,9 +69,8 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
7569
forceNewWindow: args["new-window"],
7670
gotoLineMode: true,
7771
}
78-
const paths = getResolvedPathsFromArgs(args)
79-
for (let i = 0; i < paths.length; i++) {
80-
const fp = paths[i]
72+
for (let i = 0; i < args._.length; i++) {
73+
const fp = args._[i]
8174
if (await isDirectory(fp)) {
8275
pipeArgs.folderURIs.push(fp)
8376
} else {

0 commit comments

Comments
 (0)