Set session socket into environment variable (#6282)

* Avoid spawning code-server with --reuse-window and --new-window

These flags mean the user explicitly wants to open in an existing
instance so if the socket is down it should error rather than try to
spawn code-server normally.

* Set session socket into environment variable

While I was at it I added a CLI flag to override the default.  I also
swapped the default to --user-data-dir.

The value is set on an environment variable so it can be used by the
extension host similar to VSCODE_IPC_HOOK_CLI.

* Add e2e test for opening files externally
This commit is contained in:
Asher 2023-06-21 22:47:01 -08:00 committed by GitHub
parent 56d10d82bf
commit 5c19962930
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 168 additions and 98 deletions

View File

@ -24,6 +24,16 @@ Code v99.99.999
Code v1.79.2 Code v1.79.2
### Fixed
- Fix being unable to launch multiple instances of code-server for different
users.
### Added
- `--session-socket` CLI flag to configure the location of the session socket.
By default it will be placed in `--user-data-dir`.
## [4.14.0](https://github.com/coder/code-server/releases/tag/v4.14.0) - 2023-06-16 ## [4.14.0](https://github.com/coder/code-server/releases/tag/v4.14.0) - 2023-06-16
Code v1.79.2 Code v1.79.2

View File

@ -15,18 +15,16 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts --- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts +++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -2,7 +2,9 @@ @@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
- -
+import * as os from 'os';
+import * as _http from 'http'; +import * as _http from 'http';
+import * as path from 'vs/base/common/path';
import * as performance from 'vs/base/common/performance'; import * as performance from 'vs/base/common/performance';
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl'; import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
@@ -17,6 +19,7 @@ import { ExtensionRuntime } from 'vs/wor @@ -17,6 +17,7 @@ import { ExtensionRuntime } from 'vs/wor
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
import { realpathSync } from 'vs/base/node/extpath'; import { realpathSync } from 'vs/base/node/extpath';
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder'; import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder';
@ -34,13 +32,14 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider'; import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider';
class NodeModuleRequireInterceptor extends RequireInterceptor { class NodeModuleRequireInterceptor extends RequireInterceptor {
@@ -83,6 +86,52 @@ export class ExtHostExtensionService ext @@ -83,6 +84,52 @@ export class ExtHostExtensionService ext
await interceptor.install(); await interceptor.install();
performance.mark('code/extHost/didInitAPI'); performance.mark('code/extHost/didInitAPI');
+ (async () => { + (async () => {
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI']; + const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
+ if (!socketPath) { + const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
+ if (!socketPath || !codeServerSocketPath) {
+ return; + return;
+ } + }
+ const workspace = this._instaService.invokeFunction((accessor) => { + const workspace = this._instaService.invokeFunction((accessor) => {
@ -52,7 +51,6 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
+ socketPath + socketPath
+ }; + };
+ const message = JSON.stringify({entry}); + const message = JSON.stringify({entry});
+ const codeServerSocketPath = path.join(os.tmpdir(), 'code-server-ipc.sock');
+ await new Promise<void>((resolve, reject) => { + await new Promise<void>((resolve, reject) => {
+ const opts: _http.RequestOptions = { + const opts: _http.RequestOptions = {
+ path: '/add-session', + path: '/add-session',
@ -91,17 +89,15 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts --- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
+++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts +++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
@@ -3,6 +3,9 @@ @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
+import * as os from 'os';
+import * as _http from 'http'; +import * as _http from 'http';
+import * as path from 'vs/base/common/path';
import * as nativeWatchdog from 'native-watchdog'; import * as nativeWatchdog from 'native-watchdog';
import * as net from 'net'; import * as net from 'net';
import * as minimist from 'minimist'; import * as minimist from 'minimist';
@@ -400,7 +403,28 @@ async function startExtensionHostProcess @@ -400,7 +401,28 @@ async function startExtensionHostProcess
); );
// rewrite onTerminate-function to be a proper shutdown // rewrite onTerminate-function to be a proper shutdown
@ -110,11 +106,11 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
+ extensionHostMain.terminate(reason); + extensionHostMain.terminate(reason);
+ +
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI']; + const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
+ if (!socketPath) { + const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
+ if (!socketPath || !codeServerSocketPath) {
+ return; + return;
+ } + }
+ const message = JSON.stringify({socketPath}); + const message = JSON.stringify({socketPath});
+ const codeServerSocketPath = path.join(os.tmpdir(), 'code-server-ipc.sock');
+ const opts: _http.RequestOptions = { + const opts: _http.RequestOptions = {
+ path: '/delete-session', + path: '/delete-session',
+ socketPath: codeServerSocketPath, + socketPath: codeServerSocketPath,

View File

@ -9,7 +9,7 @@ import * as util from "../common/util"
import { DefaultedArgs } from "./cli" import { DefaultedArgs } from "./cli"
import { disposer } from "./http" import { disposer } from "./http"
import { isNodeJSErrnoException } from "./util" import { isNodeJSErrnoException } from "./util"
import { DEFAULT_SOCKET_PATH, EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket" import { EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
import { handleUpgrade } from "./wsRouter" import { handleUpgrade } from "./wsRouter"
type SocketOptions = { socket: string; "socket-mode"?: string } type SocketOptions = { socket: string; "socket-mode"?: string }
@ -88,7 +88,7 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
handleUpgrade(wsRouter, server) handleUpgrade(wsRouter, server)
const editorSessionManager = new EditorSessionManager() const editorSessionManager = new EditorSessionManager()
const editorSessionManagerServer = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, editorSessionManager) const editorSessionManagerServer = await makeEditorSessionManagerServer(args["session-socket"], editorSessionManager)
const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer) const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer)
const dispose = async () => { const dispose = async () => {

View File

@ -4,7 +4,7 @@ import { load } from "js-yaml"
import * as os from "os" import * as os from "os"
import * as path from "path" import * as path from "path"
import { generateCertificate, generatePassword, humanPath, paths, splitOnFirstEquals } from "./util" import { generateCertificate, generatePassword, humanPath, paths, splitOnFirstEquals } from "./util"
import { DEFAULT_SOCKET_PATH, EditorSessionManagerClient } from "./vscodeSocket" import { EditorSessionManagerClient } from "./vscodeSocket"
export enum Feature { export enum Feature {
// No current experimental features! // No current experimental features!
@ -51,6 +51,7 @@ export interface UserProvidedCodeArgs {
"disable-file-downloads"?: boolean "disable-file-downloads"?: boolean
"disable-workspace-trust"?: boolean "disable-workspace-trust"?: boolean
"disable-getting-started-override"?: boolean "disable-getting-started-override"?: boolean
"session-socket"?: string
} }
/** /**
@ -160,6 +161,9 @@ export const options: Options<Required<UserProvidedArgs>> = {
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" + "Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
"then notifies you once every week that a new release is available.", "then notifies you once every week that a new release is available.",
}, },
"session-socket": {
type: "string",
},
"disable-file-downloads": { "disable-file-downloads": {
type: "boolean", type: "boolean",
description: description:
@ -459,6 +463,7 @@ export interface DefaultedArgs extends ConfigArgs {
usingEnvHashedPassword: boolean usingEnvHashedPassword: boolean
"extensions-dir": string "extensions-dir": string
"user-data-dir": string "user-data-dir": string
"session-socket": string
/* Positional arguments. */ /* Positional arguments. */
_: string[] _: string[]
} }
@ -479,6 +484,11 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions") args["extensions-dir"] = path.join(args["user-data-dir"], "extensions")
} }
if (!args["session-socket"]) {
args["session-socket"] = path.join(args["user-data-dir"], "code-server-ipc.sock")
}
process.env.CODE_SERVER_SESSION_SOCKET = args["session-socket"]
// --verbose takes priority over --log and --log takes priority over the // --verbose takes priority over --log and --log takes priority over the
// environment variable. // environment variable.
if (args.verbose) { if (args.verbose) {
@ -739,7 +749,10 @@ function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
* existing instance. The arguments here should be the arguments the user * existing instance. The arguments here should be the arguments the user
* explicitly passed on the command line, *NOT DEFAULTS* or the configuration. * explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
*/ */
export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Promise<string | undefined> => { export const shouldOpenInExistingInstance = async (
args: UserProvidedArgs,
sessionSocket: string,
): Promise<string | undefined> => {
// Always use the existing instance if we're running from VS Code's terminal. // Always use the existing instance if we're running from VS Code's terminal.
if (process.env.VSCODE_IPC_HOOK_CLI) { if (process.env.VSCODE_IPC_HOOK_CLI) {
logger.debug("Found VSCODE_IPC_HOOK_CLI") logger.debug("Found VSCODE_IPC_HOOK_CLI")
@ -747,21 +760,22 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
} }
const paths = getResolvedPathsFromArgs(args) const paths = getResolvedPathsFromArgs(args)
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH) const client = new EditorSessionManagerClient(sessionSocket)
// If we can't connect to the socket then there's no existing instance.
if (!(await client.canConnect())) {
return undefined
}
// If these flags are set then assume the user is trying to open in an // If these flags are set then assume the user is trying to open in an
// existing instance since these flags have no effect otherwise. // existing instance since these flags have no effect otherwise. That means
// if there is no existing instance we should error rather than falling back
// to spawning code-server normally.
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => { const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
}, 0) }, 0)
if (openInFlagCount > 0) { if (openInFlagCount > 0) {
logger.debug("Found --reuse-window or --new-window") logger.debug("Found --reuse-window or --new-window")
return await client.getConnectedSocketPath(paths[0]) const socketPath = await client.getConnectedSocketPath(paths[0])
if (!socketPath) {
throw new Error(`No opened code-server instances found to handle ${paths[0]}`)
}
return socketPath
} }
// It's possible the user is trying to spawn another instance of code-server. // It's possible the user is trying to spawn another instance of code-server.
@ -769,7 +783,11 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
// code-server is invoked exactly like this: `code-server my-file`). // code-server is invoked exactly like this: `code-server my-file`).
// 2. That a file or directory was passed. // 2. That a file or directory was passed.
// 3. That the socket is active. // 3. That the socket is active.
// 4. That an instance exists to handle the path (implied by #3).
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) { if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
if (!(await client.canConnect())) {
return undefined
}
const socketPath = await client.getConnectedSocketPath(paths[0]) const socketPath = await client.getConnectedSocketPath(paths[0])
if (socketPath) { if (socketPath) {
logger.debug("Found existing code-server socket") logger.debug("Found existing code-server socket")

View File

@ -51,7 +51,7 @@ async function entry(): Promise<void> {
return runCodeCli(args) return runCodeCli(args)
} }
const socketPath = await shouldOpenInExistingInstance(cliArgs) const socketPath = await shouldOpenInExistingInstance(cliArgs, args["session-socket"])
if (socketPath) { if (socketPath) {
logger.debug("Trying to open in existing instance") logger.debug("Trying to open in existing instance")
return openInExistingInstance(args, socketPath) return openInExistingInstance(args, socketPath)

View File

@ -4,10 +4,7 @@ import * as http from "http"
import * as path from "path" import * as path from "path"
import { HttpCode } from "../common/http" import { HttpCode } from "../common/http"
import { listen } from "./app" import { listen } from "./app"
import { canConnect, paths } from "./util" import { canConnect } from "./util"
// Socket path of the daemonized code-server instance.
export const DEFAULT_SOCKET_PATH = path.join(paths.data, `code-server-ipc.sock`)
export interface EditorSessionEntry { export interface EditorSessionEntry {
workspace: { workspace: {

View File

@ -117,15 +117,8 @@ export class CodeServer {
* directories. * directories.
*/ */
private async spawn(): Promise<CodeServerProcess> { private async spawn(): Promise<CodeServerProcess> {
// This will be used both as the workspace and data directory to ensure
// instances don't bleed into each other.
const dir = await this.createWorkspace() const dir = await this.createWorkspace()
const args = await this.argsWithDefaults([
return new Promise((resolve, reject) => {
const args = [
this.entry,
"--extensions-dir",
path.join(dir, "extensions"),
"--auth", "--auth",
"none", "none",
// The workspace to open. // The workspace to open.
@ -134,23 +127,16 @@ export class CodeServer {
// Using port zero will spawn on a random port. // Using port zero will spawn on a random port.
"--bind-addr", "--bind-addr",
"127.0.0.1:0", "127.0.0.1:0",
// Setting the XDG variables would be easier and more thorough but the ])
// modules we import ignores those variables for non-Linux operating return new Promise((resolve, reject) => {
// systems so use these flags instead.
"--config",
path.join(dir, "config.yaml"),
"--user-data-dir",
dir,
]
this.logger.debug("spawning `node " + args.join(" ") + "`") this.logger.debug("spawning `node " + args.join(" ") + "`")
const proc = cp.spawn("node", args, { const proc = cp.spawn("node", args, {
cwd: path.join(__dirname, "../../.."), cwd: path.join(__dirname, "../../.."),
env: { env: {
...process.env, ...process.env,
...this.env, ...this.env,
// Set to empty string to prevent code-server from // Prevent code-server from using the existing instance when running
// using the existing instance when running the e2e tests // the e2e tests from an integrated terminal.
// from an integrated terminal.
VSCODE_IPC_HOOK_CLI: "", VSCODE_IPC_HOOK_CLI: "",
PASSWORD, PASSWORD,
}, },
@ -173,11 +159,15 @@ export class CodeServer {
reject(error) reject(error)
}) })
// Tracks when the HTTP and session servers are ready.
let httpAddress: string | undefined
let sessionAddress: string | undefined
let resolved = false let resolved = false
proc.stdout.setEncoding("utf8") proc.stdout.setEncoding("utf8")
onLine(proc, (line) => { onLine(proc, (line) => {
// As long as we are actively getting input reset the timer. If we stop // As long as we are actively getting input reset the timer. If we stop
// getting input and still have not found the address the timer will // getting input and still have not found the addresses the timer will
// reject. // reject.
timer.reset() timer.reset()
@ -186,20 +176,69 @@ export class CodeServer {
if (resolved) { if (resolved) {
return return
} }
const match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
let match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
if (match) { if (match) {
// Cookies don't seem to work on IP address so swap to localhost. // Cookies don't seem to work on IP addresses so swap to localhost.
// TODO: Investigate whether this is a bug with code-server. // TODO: Investigate whether this is a bug with code-server.
const address = match[1].replace("127.0.0.1", "localhost") httpAddress = match[1].replace("127.0.0.1", "localhost")
this.logger.debug(`spawned on ${address}`) }
match = line.trim().match(/Session server listening on (.+)$/)
if (match) {
sessionAddress = match[1]
}
if (typeof httpAddress !== "undefined" && typeof sessionAddress !== "undefined") {
resolved = true resolved = true
timer.dispose() timer.dispose()
resolve({ process: proc, address }) this.logger.debug(`code-server is ready: ${httpAddress} ${sessionAddress}`)
resolve({ process: proc, address: httpAddress })
} }
}) })
}) })
} }
/**
* Execute a short-lived command.
*/
async run(args: string[]): Promise<void> {
args = await this.argsWithDefaults(args)
this.logger.debug("executing `node " + args.join(" ") + "`")
await util.promisify(cp.exec)("node " + args.join(" "), {
cwd: path.join(__dirname, "../../.."),
env: {
...process.env,
...this.env,
// Prevent code-server from using the existing instance when running
// the e2e tests from an integrated terminal.
VSCODE_IPC_HOOK_CLI: "",
},
})
}
/**
* Combine arguments with defaults.
*/
private async argsWithDefaults(args: string[]): Promise<string[]> {
// This will be used both as the workspace and data directory to ensure
// instances don't bleed into each other.
const dir = await this.workspaceDir
return [
this.entry,
"--extensions-dir",
path.join(dir, "extensions"),
...args,
// Setting the XDG variables would be easier and more thorough but the
// modules we import ignores those variables for non-Linux operating
// systems so use these flags instead.
"--config",
path.join(dir, "config.yaml"),
"--user-data-dir",
dir,
]
}
/** /**
* Close the code-server process. * Close the code-server process.
*/ */
@ -364,6 +403,13 @@ export class CodeServerPage {
await this.waitForTab(file) await this.waitForTab(file)
} }
/**
* Open a file through an external command.
*/
async openFileExternally(file: string) {
await this.codeServer.run(["--reuse-window", file])
}
/** /**
* Wait for a tab to open for the specified file. * Wait for a tab to open for the specified file.
*/ */

View File

@ -35,13 +35,19 @@ describe("Integrated Terminal", ["--disable-workspace-trust"], {}, () => {
const tmpFolderPath = await tmpdir(testName) const tmpFolderPath = await tmpdir(testName)
const tmpFile = path.join(tmpFolderPath, "test-file") const tmpFile = path.join(tmpFolderPath, "test-file")
await fs.writeFile(tmpFile, "test") await fs.writeFile(tmpFile, "test")
const fileName = path.basename(tmpFile)
await codeServerPage.focusTerminal() await codeServerPage.focusTerminal()
await codeServerPage.page.keyboard.type(`code-server ${tmpFile}`) await codeServerPage.page.keyboard.type(`code-server ${tmpFile}`)
await codeServerPage.page.keyboard.press("Enter") await codeServerPage.page.keyboard.press("Enter")
await codeServerPage.waitForTab(fileName) await codeServerPage.waitForTab(path.basename(tmpFile))
const externalTmpFile = path.join(tmpFolderPath, "test-external-file")
await fs.writeFile(externalTmpFile, "foobar")
await codeServerPage.openFileExternally(externalTmpFile)
await codeServerPage.waitForTab(path.basename(externalTmpFile))
}) })
}) })

View File

@ -18,7 +18,6 @@ import {
import { shouldSpawnCliProcess } from "../../../src/node/main" import { shouldSpawnCliProcess } from "../../../src/node/main"
import { generatePassword, paths } from "../../../src/node/util" import { generatePassword, paths } from "../../../src/node/util"
import { import {
DEFAULT_SOCKET_PATH,
EditorSessionManager, EditorSessionManager,
EditorSessionManagerClient, EditorSessionManagerClient,
makeEditorSessionManagerServer, makeEditorSessionManagerServer,
@ -37,6 +36,7 @@ const defaults = {
usingEnvHashedPassword: false, usingEnvHashedPassword: false,
"extensions-dir": path.join(paths.data, "extensions"), "extensions-dir": path.join(paths.data, "extensions"),
"user-data-dir": paths.data, "user-data-dir": paths.data,
"session-socket": path.join(paths.data, "code-server-ipc.sock"),
_: [], _: [],
} }
@ -103,6 +103,8 @@ describe("parser", () => {
"--disable-getting-started-override", "--disable-getting-started-override",
["--session-socket", "/tmp/override-code-server-ipc-socket"],
["--host", "0.0.0.0"], ["--host", "0.0.0.0"],
"4", "4",
"--", "--",
@ -136,6 +138,7 @@ describe("parser", () => {
"welcome-text": "welcome to code", "welcome-text": "welcome to code",
version: true, version: true,
"bind-addr": "192.169.0.1:8080", "bind-addr": "192.169.0.1:8080",
"session-socket": "/tmp/override-code-server-ipc-socket",
}) })
}) })
@ -504,22 +507,23 @@ describe("cli", () => {
it("should use existing if inside code-server", async () => { it("should use existing if inside code-server", async () => {
process.env.VSCODE_IPC_HOOK_CLI = "test" process.env.VSCODE_IPC_HOOK_CLI = "test"
const args: UserProvidedArgs = {} const args: UserProvidedArgs = {}
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test") expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
args.port = 8081 args.port = 8081
args._ = ["./file"] args._ = ["./file"]
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test") expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
}) })
it("should use existing if --reuse-window is set", async () => { it("should use existing if --reuse-window is set", async () => {
const server = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, new EditorSessionManager()) const sessionSocket = path.join(tmpDirPath, "session-socket")
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
const args: UserProvidedArgs = {} const args: UserProvidedArgs = {}
args["reuse-window"] = true args["reuse-window"] = true
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(undefined) await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
const socketPath = path.join(tmpDirPath, "socket") const socketPath = path.join(tmpDirPath, "socket")
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH) const client = new EditorSessionManagerClient(sessionSocket)
await client.addSession({ await client.addSession({
entry: { entry: {
workspace: { workspace: {
@ -537,24 +541,25 @@ describe("cli", () => {
}) })
const vscodeSockets = listenOn(socketPath) const vscodeSockets = listenOn(socketPath)
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(socketPath) await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
args.port = 8081 args.port = 8081
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(socketPath) await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
server.close() server.close()
vscodeSockets.close() vscodeSockets.close()
}) })
it("should use existing if --new-window is set", async () => { it("should use existing if --new-window is set", async () => {
const server = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, new EditorSessionManager()) const sessionSocket = path.join(tmpDirPath, "session-socket")
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
const args: UserProvidedArgs = {} const args: UserProvidedArgs = {}
args["new-window"] = true args["new-window"] = true
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(undefined) await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
const socketPath = path.join(tmpDirPath, "socket") const socketPath = path.join(tmpDirPath, "socket")
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH) const client = new EditorSessionManagerClient(sessionSocket)
await client.addSession({ await client.addSession({
entry: { entry: {
workspace: { workspace: {
@ -572,25 +577,26 @@ describe("cli", () => {
}) })
const vscodeSockets = listenOn(socketPath) const vscodeSockets = listenOn(socketPath)
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath) expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
args.port = 8081 args.port = 8081
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath) expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
server.close() server.close()
vscodeSockets.close() vscodeSockets.close()
}) })
it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => { it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => {
const server = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, new EditorSessionManager()) const sessionSocket = path.join(tmpDirPath, "session-socket")
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
const args: UserProvidedArgs = {} const args: UserProvidedArgs = {}
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined) expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
args._ = ["./file"] args._ = ["./file"]
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined) expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH) const client = new EditorSessionManagerClient(sessionSocket)
const socketPath = path.join(tmpDirPath, "socket") const socketPath = path.join(tmpDirPath, "socket")
await client.addSession({ await client.addSession({
entry: { entry: {
@ -609,18 +615,19 @@ describe("cli", () => {
}) })
const vscodeSockets = listenOn(socketPath) const vscodeSockets = listenOn(socketPath)
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath) expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
args.port = 8081 args.port = 8081
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined) expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
server.close() server.close()
vscodeSockets.close() vscodeSockets.close()
}) })
it("should prefer matching sessions for only the first path", async () => { it("should prefer matching sessions for only the first path", async () => {
const server = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, new EditorSessionManager()) const sessionSocket = path.join(tmpDirPath, "session-socket")
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH) const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
const client = new EditorSessionManagerClient(sessionSocket)
await client.addSession({ await client.addSession({
entry: { entry: {
workspace: { workspace: {
@ -655,7 +662,7 @@ describe("cli", () => {
const args: UserProvidedArgs = {} const args: UserProvidedArgs = {}
args._ = ["/aaa/file", "/bbb/file"] args._ = ["/aaa/file", "/bbb/file"]
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(`${tmpDirPath}/vscode-ipc-aaa.sock`) expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(`${tmpDirPath}/vscode-ipc-aaa.sock`)
server.close() server.close()
}) })

View File

@ -43,6 +43,7 @@ describe("plugin", () => {
usingEnvHashedPassword: false, usingEnvHashedPassword: false,
"extensions-dir": "", "extensions-dir": "",
"user-data-dir": "", "user-data-dir": "",
"session-socket": "",
} }
next() next()
} }

View File

@ -1,19 +1,8 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import * as app from "../../../src/node/app" import * as app from "../../../src/node/app"
import { paths } from "../../../src/node/util" import { EditorSessionManager, makeEditorSessionManagerServer } from "../../../src/node/vscodeSocket"
import {
DEFAULT_SOCKET_PATH,
EditorSessionManager,
makeEditorSessionManagerServer,
} from "../../../src/node/vscodeSocket"
import { clean, tmpdir, listenOn, mockLogger } from "../../utils/helpers" import { clean, tmpdir, listenOn, mockLogger } from "../../utils/helpers"
describe("DEFAULT_SOCKET_PATH", () => {
it("should be a unique path per user", () => {
expect(DEFAULT_SOCKET_PATH.startsWith(paths.data)).toBe(true)
})
})
describe("makeEditorSessionManagerServer", () => { describe("makeEditorSessionManagerServer", () => {
let tmpDirPath: string let tmpDirPath: string