Compare commits

...

16 Commits

Author SHA1 Message Date
Asher
e22964915a Support opening workspaces from command line
Partly addresses #1121.
2019-10-28 16:25:51 -05:00
Asher
197d0b6ca9 Strip internal env vars when spawning the shell
This should fix all those reports of code-server dropping straight to
Node and things like #1121.
2019-10-28 16:08:32 -05:00
Asher
422503ef98 Proxy child exit code when exiting parent process
This fixes code-server exiting with zero on errors.
2019-10-28 14:57:01 -05:00
Asher
ea36345d2c Allow fetching any resource
Fixes #1118.
2019-10-28 14:29:51 -05:00
Asher
a89d83cbba Fix other incorrect usages of split 2019-10-28 14:03:13 -05:00
Asher
83ff31b620 Fix passwords that contain =
Fixes #1119.

Apparently `split` does not work the way I'd expect.
2019-10-28 13:47:31 -05:00
Asher
3a9b032c72 Add heartbeat file (#1115)
Fixes #1050.
2019-10-28 09:59:34 -05:00
Asher
f73e9225b4 Remove directory restrictions for /webview/vscode-resource
This makes viewing images work. Fixes #1111.
2019-10-25 15:52:39 -05:00
Asher
168ccb0dfc Prevent cache changes when patch updates 2019-10-25 13:12:04 -05:00
Asher
58f7f5b769 Properly fix blank --cert flag
See #1109.
2019-10-25 12:04:43 -05:00
Asher
b8e6369fbe Fix empty --cert not generating self-signed certificate
Fixes #1101.
2019-10-25 11:01:42 -05:00
Asher
d81d5f499f Remove Cloud Run button
Unfortunately it doesn't allow websockets so it's not working.
2019-10-24 16:45:22 -05:00
Asher
4be178d234 Move Google Cloud button to match Digital Ocean 2019-10-24 16:09:02 -05:00
Ayane Satomi
9c40466b4b Add Google Cloud quick-launch button (#1069) 2019-10-24 16:07:44 -05:00
Asher
95693fb58e Handle /webview/vscode-resource/file urls
See #1103.
2019-10-24 14:35:25 -05:00
Asher
e7945bea94 Enable password authentication by default
Fixes #1062.
2019-10-24 12:35:26 -05:00
9 changed files with 139 additions and 39 deletions

View File

@@ -30,7 +30,7 @@ jobs:
- name: "MacOS build" - name: "MacOS build"
os: osx os: osx
if: tag IS blank if: tag IS blank
script: travis_wait 40 scripts/ci.bash script: travis_wait 60 scripts/ci.bash
git: git:
depth: 3 depth: 3

View File

@@ -73,9 +73,9 @@ yarn binary ${vscodeVersion} ${codeServerVersion} # Or you can package it into a
## Security ## Security
### Authentication ### Authentication
To enable built-in password authentication use `code-server --auth password`. By By default `code-server` enables password authentication using a randomly
default it will use a randomly generated password but you can set the generated password. You can set the `PASSWORD` environment variable to use your
`$PASSWORD` environment variable to use your own. own instead or use `--auth none` to disable password authentication.
Do not expose `code-server` to the open internet without some form of Do not expose `code-server` to the open internet without some form of
authentication. authentication.

View File

@@ -303,14 +303,16 @@ class Builder {
]); ]);
}); });
// This is so it doesn't get cached along with VS Code. There's no point // Prevent needless cache changes.
// since there isn't anything like an incremental build. await this.task("Cleaning for smaller cache", () => {
await this.task("Removing build files for smaller cache", () => {
return Promise.all([ return Promise.all([
fs.remove(serverPath), fs.remove(serverPath),
fs.remove(path.join(vscodeSourcePath, "out-vscode")), fs.remove(path.join(vscodeSourcePath, "out-vscode")),
fs.remove(path.join(vscodeSourcePath, "out-vscode-min")), fs.remove(path.join(vscodeSourcePath, "out-vscode-min")),
fs.remove(path.join(vscodeSourcePath, "out-build")), fs.remove(path.join(vscodeSourcePath, "out-build")),
util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath }).then(() => {
return util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
}),
]); ]);
}); });

View File

@@ -50,6 +50,21 @@ index a657f4a4d9..66bd13dffa 100644
} else if (typeof process === 'object') { } else if (typeof process === 'object') {
_isWindows = (process.platform === 'win32'); _isWindows = (process.platform === 'win32');
_isMacintosh = (process.platform === 'darwin'); _isMacintosh = (process.platform === 'darwin');
diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts
index c52f7b3774..5635cfac8a 100644
--- a/src/vs/base/common/processes.ts
+++ b/src/vs/base/common/processes.ts
@@ -110,7 +110,9 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
/^ELECTRON_.+$/,
/^GOOGLE_API_KEY$/,
/^VSCODE_.+$/,
- /^SNAP(|_.*)$/
+ /^SNAP(|_.*)$/,
+ /^NBIN_BYPASS$/,
+ /^LAUNCH_VSCODE$/
];
const envKeys = Object.keys(env);
envKeys
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index 3ae24454cb..fac8679290 100644 index 3ae24454cb..fac8679290 100644
--- a/src/vs/base/node/languagePacks.js --- a/src/vs/base/node/languagePacks.js
@@ -87,7 +102,7 @@ index 990755c4f3..06449bb9cb 100644
+ extraBuiltinExtensionPaths: string[]; + extraBuiltinExtensionPaths: string[];
} }
diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts
index 3e48fe4ddd..e0962b8736 100644 index 3e48fe4ddd..2212ff5471 100644
--- a/src/vs/platform/environment/node/argv.ts --- a/src/vs/platform/environment/node/argv.ts
+++ b/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts
@@ -58,6 +58,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = { @@ -58,6 +58,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
@@ -99,6 +114,15 @@ index 3e48fe4ddd..e0962b8736 100644
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") }, 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") }, 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
@@ -185,7 +187,7 @@ export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, err
delete parsedArgs[o.deprecates];
}
- if (val) {
+ if (typeof val !== 'undefined') {
if (o.type === 'string[]') {
if (val && !Array.isArray(val)) {
val = [val];
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
index f7d207009d..5c37b52dab 100644 index f7d207009d..5c37b52dab 100644
--- a/src/vs/platform/environment/node/environmentService.ts --- a/src/vs/platform/environment/node/environmentService.ts

View File

@@ -3,15 +3,16 @@ import { URI } from "vs/base/common/uri";
import { registerSingleton } from "vs/platform/instantiation/common/extensions"; import { registerSingleton } from "vs/platform/instantiation/common/extensions";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection"; import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
import { ILocalizationsService } from "vs/platform/localizations/common/localizations"; import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService"; import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry"; import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
import { coderApi, vscodeApi } from "vs/server/src/browser/api"; import { coderApi, vscodeApi } from "vs/server/src/browser/api";
import { IUploadService, UploadService } from "vs/server/src/browser/upload"; import { IUploadService, UploadService } from "vs/server/src/browser/upload";
import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy"; import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy";
import { TelemetryChannelClient } from "vs/server/src/common/telemetry"; import { TelemetryChannelClient } from "vs/server/src/common/telemetry";
import { split } from "vs/server/src/common/util";
import "vs/workbench/contrib/localizations/browser/localizations.contribution"; import "vs/workbench/contrib/localizations/browser/localizations.contribution";
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService"; import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
class TelemetryService extends TelemetryChannelClient { class TelemetryService extends TelemetryChannelClient {
public constructor( public constructor(
@@ -79,7 +80,7 @@ export const withQuery = (url: string, replace: Query): string => {
const uri = URI.parse(url); const uri = URI.parse(url);
const query = { ...replace }; const query = { ...replace };
uri.query.split("&").forEach((kv) => { uri.query.split("&").forEach((kv) => {
const [key, value] = kv.split("=", 2); const [key, value] = split(kv, "=");
if (!(key in query)) { if (!(key in query)) {
query[key] = value; query[key] = value;
} }

10
src/common/util.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* Split a string up to the delimiter. If the delimiter doesn't exist the first
* item will have all the text and the second item will be an empty string.
*/
export const split = (str: string, delimiter: string): [string, string] => {
const index = str.indexOf(delimiter);
return index !== -1
? [str.substring(0, index).trim(), str.substring(index + 1)]
: [str, ""];
};

View File

@@ -86,18 +86,18 @@ const startVscode = async (): Promise<void | void[]> => {
const args = getArgs(); const args = getArgs();
const extra = args["_"] || []; const extra = args["_"] || [];
const options = { const options = {
auth: args.auth, auth: args.auth || AuthType.Password,
basePath: args["base-path"], basePath: args["base-path"],
cert: args.cert, cert: args.cert,
certKey: args["cert-key"], certKey: args["cert-key"],
folderUri: extra.length > 1 ? extra[extra.length - 1] : undefined, openUri: extra.length > 1 ? extra[extra.length - 1] : undefined,
host: args.host, host: args.host,
password: process.env.PASSWORD, password: process.env.PASSWORD,
}; };
if (options.auth && enumToArray(AuthType).filter((t) => t === options.auth).length === 0) { if (enumToArray(AuthType).filter((t) => t === options.auth).length === 0) {
throw new Error(`'${options.auth}' is not a valid authentication type.`); throw new Error(`'${options.auth}' is not a valid authentication type.`);
} else if (options.auth && !options.password) { } else if (options.auth === "password" && !options.password) {
options.password = await generatePassword(); options.password = await generatePassword();
} }
@@ -125,10 +125,13 @@ const startVscode = async (): Promise<void | void[]> => {
]); ]);
logger.info(`Server listening on ${serverAddress}`); logger.info(`Server listening on ${serverAddress}`);
if (options.auth && !process.env.PASSWORD) { if (options.auth === "password" && !process.env.PASSWORD) {
logger.info(` - Password is ${options.password}`); logger.info(` - Password is ${options.password}`);
logger.info(" - To use your own password, set the PASSWORD environment variable"); logger.info(" - To use your own password, set the PASSWORD environment variable");
} else if (options.auth) { if (!args.auth) {
logger.info(" - To disable use `--auth none`");
}
} else if (options.auth === "password") {
logger.info(" - Using custom password for authentication"); logger.info(" - Using custom password for authentication");
} else { } else {
logger.info(" - No authentication"); logger.info(" - No authentication");
@@ -201,6 +204,7 @@ export class WrapperProcess {
logger.info("Relaunching..."); logger.info("Relaunching...");
this.started = undefined; this.started = undefined;
if (this.process) { if (this.process) {
this.process.removeAllListeners();
this.process.kill(); this.process.kill();
} }
try { try {
@@ -220,7 +224,9 @@ export class WrapperProcess {
public start(): Promise<void> { public start(): Promise<void> {
if (!this.started) { if (!this.started) {
const child = this.spawn(); const child = this.spawn();
this.started = ipcMain.handshake(child); this.started = ipcMain.handshake(child).then(() => {
child.once("exit", (code) => exit(code!));
});
this.process = child; this.process = child;
} }
return this.started; return this.started;

View File

@@ -56,6 +56,7 @@ import { resolveCommonProperties } from "vs/platform/telemetry/node/commonProper
import { UpdateChannel } from "vs/platform/update/electron-main/updateIpc"; import { UpdateChannel } from "vs/platform/update/electron-main/updateIpc";
import { INodeProxyService, NodeProxyChannel } from "vs/server/src/common/nodeProxy"; import { INodeProxyService, NodeProxyChannel } from "vs/server/src/common/nodeProxy";
import { TelemetryChannel } from "vs/server/src/common/telemetry"; import { TelemetryChannel } from "vs/server/src/common/telemetry";
import { split } from "vs/server/src/common/util";
import { ExtensionEnvironmentChannel, FileProviderChannel, NodeProxyService } from "vs/server/src/node/channel"; import { ExtensionEnvironmentChannel, FileProviderChannel, NodeProxyService } from "vs/server/src/node/channel";
import { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/src/node/connection"; import { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/src/node/connection";
import { TelemetryClient } from "vs/server/src/node/insights"; import { TelemetryClient } from "vs/server/src/node/insights";
@@ -110,12 +111,12 @@ export class HttpError extends Error {
} }
export interface ServerOptions { export interface ServerOptions {
readonly auth?: AuthType; readonly auth: AuthType;
readonly basePath?: string; readonly basePath?: string;
readonly connectionToken?: string; readonly connectionToken?: string;
readonly cert?: string; readonly cert?: string;
readonly certKey?: string; readonly certKey?: string;
readonly folderUri?: string; readonly openUri?: string;
readonly host?: string; readonly host?: string;
readonly password?: string; readonly password?: string;
readonly port?: number; readonly port?: number;
@@ -133,7 +134,7 @@ export abstract class Server {
public constructor(options: ServerOptions) { public constructor(options: ServerOptions) {
this.options = { this.options = {
host: options.auth && options.cert ? "0.0.0.0" : "localhost", host: options.auth === "password" && options.cert ? "0.0.0.0" : "localhost",
...options, ...options,
basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "", basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "",
}; };
@@ -193,6 +194,11 @@ export abstract class Server {
return { content: await util.promisify(fs.readFile)(filePath), filePath }; return { content: await util.promisify(fs.readFile)(filePath), filePath };
} }
protected async getAnyResource(...parts: string[]): Promise<Response> {
const filePath = path.join(...parts);
return { content: await util.promisify(fs.readFile)(filePath), filePath };
}
protected async getTarredResource(...parts: string[]): Promise<Response> { protected async getTarredResource(...parts: string[]): Promise<Response> {
const filePath = this.ensureAuthorizedFilePath(...parts); const filePath = this.ensureAuthorizedFilePath(...parts);
return { stream: tarFs.pack(filePath), filePath, mime: "application/tar", cache: true }; return { stream: tarFs.pack(filePath), filePath, mime: "application/tar", cache: true };
@@ -207,8 +213,8 @@ export abstract class Server {
} }
protected withBase(request: http.IncomingMessage, path: string): string { protected withBase(request: http.IncomingMessage, path: string): string {
const split = request.url ? request.url.split("?", 2) : []; const [, query] = request.url ? split(request.url, "?") : [];
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${split.length === 2 ? `?${split[1]}` : ""}`; return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${query ? `?${query}` : ""}`;
} }
private isAllowedRequestPath(path: string): boolean { private isAllowedRequestPath(path: string): boolean {
@@ -269,7 +275,7 @@ export abstract class Server {
base = path.normalize(base); base = path.normalize(base);
requestPath = path.normalize(requestPath || "/index.html"); requestPath = path.normalize(requestPath || "/index.html");
if (base !== "/login" || !this.options.auth || requestPath !== "/index.html") { if (base !== "/login" || this.options.auth !== "password" || requestPath !== "/index.html") {
this.ensureGet(request); this.ensureGet(request);
} }
@@ -300,7 +306,7 @@ export abstract class Server {
response.cache = true; response.cache = true;
return response; return response;
case "/login": case "/login":
if (!this.options.auth || requestPath !== "/index.html") { if (this.options.auth !== "password" || requestPath !== "/index.html") {
throw new HttpError("Not found", HttpCode.NotFound); throw new HttpError("Not found", HttpCode.NotFound);
} }
return this.tryLogin(request); return this.tryLogin(request);
@@ -421,7 +427,7 @@ export abstract class Server {
} }
private authenticate(request: http.IncomingMessage, payload?: LoginPayload): boolean { private authenticate(request: http.IncomingMessage, payload?: LoginPayload): boolean {
if (!this.options.auth) { if (this.options.auth !== "password") {
return true; return true;
} }
const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index"); const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index");
@@ -435,8 +441,8 @@ export abstract class Server {
const cookies: { [key: string]: string } = {}; const cookies: { [key: string]: string } = {};
if (request.headers.cookie) { if (request.headers.cookie) {
request.headers.cookie.split(";").forEach((keyValue) => { request.headers.cookie.split(";").forEach((keyValue) => {
const [key, value] = keyValue.split("=", 2); const [key, value] = split(keyValue, "=");
cookies[key.trim()] = decodeURI(value); cookies[key] = decodeURI(value);
}); });
} }
return cookies as T; return cookies as T;
@@ -469,6 +475,9 @@ export class MainServer extends Server {
private readonly proxyTimeout = 5000; private readonly proxyTimeout = 5000;
private settings: Settings = {}; private settings: Settings = {};
private heartbeatTimer?: NodeJS.Timeout;
private heartbeatInterval = 60000;
private lastHeartbeat = 0;
public constructor(options: ServerOptions, args: ParsedArgs) { public constructor(options: ServerOptions, args: ParsedArgs) {
super(options); super(options);
@@ -486,6 +495,7 @@ export class MainServer extends Server {
} }
protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> { protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
this.heartbeat();
if (!parsedUrl.query.reconnectionToken) { if (!parsedUrl.query.reconnectionToken) {
throw new Error("Reconnection token is missing from query parameters"); throw new Error("Reconnection token is missing from query parameters");
} }
@@ -509,12 +519,13 @@ export class MainServer extends Server {
parsedUrl: url.UrlWithParsedQuery, parsedUrl: url.UrlWithParsedQuery,
request: http.IncomingMessage, request: http.IncomingMessage,
): Promise<Response> { ): Promise<Response> {
this.heartbeat();
switch (base) { switch (base) {
case "/": return this.getRoot(request, parsedUrl); case "/": return this.getRoot(request, parsedUrl);
case "/resource": case "/resource":
case "/vscode-remote-resource": case "/vscode-remote-resource":
if (typeof parsedUrl.query.path === "string") { if (typeof parsedUrl.query.path === "string") {
return this.getResource(parsedUrl.query.path); return this.getAnyResource(parsedUrl.query.path);
} }
break; break;
case "/tar": case "/tar":
@@ -523,8 +534,8 @@ export class MainServer extends Server {
} }
break; break;
case "/webview": case "/webview":
if (requestPath.indexOf("/vscode-resource") === 0) { if (/^\/vscode-resource/.test(requestPath)) {
return this.getResource(requestPath.replace(/^\/vscode-resource/, "")); return this.getAnyResource(requestPath.replace(/^\/vscode-resource(\/file)?/, ""));
} }
return this.getResource( return this.getResource(
this.rootPath, this.rootPath,
@@ -541,9 +552,9 @@ export class MainServer extends Server {
util.promisify(fs.readFile)(filePath, "utf8"), util.promisify(fs.readFile)(filePath, "utf8"),
this.getFirstValidPath([ this.getFirstValidPath([
{ path: parsedUrl.query.workspace, workspace: true }, { path: parsedUrl.query.workspace, workspace: true },
{ path: parsedUrl.query.folder }, { path: parsedUrl.query.folder, workspace: false },
(await this.readSettings()).lastVisited, (await this.readSettings()).lastVisited,
{ path: this.options.folderUri } { path: this.options.openUri }
]), ]),
this.servicesPromise, this.servicesPromise,
]); ]);
@@ -587,7 +598,9 @@ export class MainServer extends Server {
} }
/** /**
* Choose the first valid path. * Choose the first valid path. If `workspace` is undefined then either a
* workspace or a directory are acceptable. Otherwise it must be a file if a
* workspace or a directory otherwise.
*/ */
private async getFirstValidPath(startPaths: Array<StartPath | undefined>): Promise<{ uri: URI, workspace?: boolean} | undefined> { private async getFirstValidPath(startPaths: Array<StartPath | undefined>): Promise<{ uri: URI, workspace?: boolean} | undefined> {
const logger = this.services.get(ILogService) as ILogService; const logger = this.services.get(ILogService) as ILogService;
@@ -602,9 +615,8 @@ export class MainServer extends Server {
const uri = URI.file(sanitizeFilePath(paths[j], cwd)); const uri = URI.file(sanitizeFilePath(paths[j], cwd));
try { try {
const stat = await util.promisify(fs.stat)(uri.fsPath); const stat = await util.promisify(fs.stat)(uri.fsPath);
// Workspace must be a file. if (typeof startPath.workspace === "undefined" || startPath.workspace !== stat.isDirectory()) {
if (!!startPath.workspace !== stat.isDirectory()) { return { uri, workspace: !stat.isDirectory() };
return { uri, workspace: startPath.workspace };
} }
} catch (error) { } catch (error) {
logger.warn(error.message); logger.warn(error.message);
@@ -871,4 +883,48 @@ export class MainServer extends Server {
(this.services.get(ILogService) as ILogService).warn(error.message); (this.services.get(ILogService) as ILogService).warn(error.message);
} }
} }
/**
* Return the file path for the heartbeat file.
*/
private get heartbeatPath(): string {
const environment = this.services.get(IEnvironmentService) as IEnvironmentService;
return path.join(environment.userDataPath, "heartbeat");
}
/**
* Return all online connections regardless of type.
*/
private get onlineConnections(): Connection[] {
const online = <Connection[]>[];
this.connections.forEach((connections) => {
connections.forEach((connection) => {
if (typeof connection.offline === "undefined") {
online.push(connection);
}
});
});
return online;
}
/**
* Write to the heartbeat file if we haven't already done so within the
* timeout and start or reset a timer that keeps running as long as there are
* active connections. Failures are logged as warnings.
*/
private heartbeat(): void {
const now = Date.now();
if (now - this.lastHeartbeat >= this.heartbeatInterval) {
util.promisify(fs.writeFile)(this.heartbeatPath, "").catch((error) => {
(this.services.get(ILogService) as ILogService).warn(error.message);
});
this.lastHeartbeat = now;
clearTimeout(this.heartbeatTimer!); // We can clear undefined so ! is fine.
this.heartbeatTimer = setTimeout(() => {
if (this.onlineConnections.length > 0) {
this.heartbeat();
}
}, this.heartbeatInterval);
}
}
} }

View File

@@ -14,6 +14,7 @@ import { mkdirp } from "vs/base/node/pfs";
export enum AuthType { export enum AuthType {
Password = "password", Password = "password",
None = "none",
} }
export enum FormatType { export enum FormatType {
@@ -127,7 +128,7 @@ export const enumToArray = (t: any): string[] => {
export const buildAllowedMessage = (t: any): string => { export const buildAllowedMessage = (t: any): string => {
const values = enumToArray(t); const values = enumToArray(t);
return `Allowed value${values.length === 1 ? " is" : "s are"} ${values.map((t) => `'${t}'`).join(",")}`; return `Allowed value${values.length === 1 ? " is" : "s are"} ${values.map((t) => `'${t}'`).join(", ")}`;
}; };
/** /**