Compare commits
37 Commits
2.1637-vsc
...
2.1692-vsc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e14362f322 | ||
|
|
917aa48072 | ||
|
|
938c6ef829 | ||
|
|
0add01d383 | ||
|
|
2018024810 | ||
|
|
a1d6bcb8e5 | ||
|
|
727ac6483b | ||
|
|
2c15c09fc0 | ||
|
|
2ad2582cc0 | ||
|
|
cee0ac213c | ||
|
|
780a673017 | ||
|
|
af71203955 | ||
|
|
fc3acfabb2 | ||
|
|
3d5db8313a | ||
|
|
73cf8f34e3 | ||
|
|
766efd6079 | ||
|
|
87485948ad | ||
|
|
7e4a73ce2d | ||
|
|
2f0878d9b7 | ||
|
|
f65c9b23fc | ||
|
|
cd859d117f | ||
|
|
e22964915a | ||
|
|
197d0b6ca9 | ||
|
|
422503ef98 | ||
|
|
ea36345d2c | ||
|
|
a89d83cbba | ||
|
|
83ff31b620 | ||
|
|
3a9b032c72 | ||
|
|
f73e9225b4 | ||
|
|
168ccb0dfc | ||
|
|
58f7f5b769 | ||
|
|
b8e6369fbe | ||
|
|
d81d5f499f | ||
|
|
4be178d234 | ||
|
|
9c40466b4b | ||
|
|
95693fb58e | ||
|
|
e7945bea94 |
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: "MacOS build"
|
||||
os: osx
|
||||
if: tag IS blank
|
||||
script: travis_wait 40 scripts/ci.bash
|
||||
script: travis_wait 60 scripts/ci.bash
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
@@ -61,7 +61,7 @@ deploy:
|
||||
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: docker build -f ./scripts/ci.dockerfile -t codercom/code-server:"$TAG" -t codercom/code-server:v2 . && docker push codercom/code-server:"$TAG" && docker push codercom/code-server:v2
|
||||
script: docker build -f ./scripts/ci.dockerfile -t codercom/code-server:"$TAG" -t codercom/code-server:v2 -t codercom/code-server . && docker push codercom/code-server:"$TAG" && docker push codercom/code-server:v2 && docker push codercom/code-server
|
||||
on:
|
||||
repo: cdr/code-server
|
||||
branch: master
|
||||
|
||||
@@ -33,7 +33,8 @@ RUN apt-get update && apt-get install -y \
|
||||
dumb-init \
|
||||
vim \
|
||||
curl \
|
||||
wget
|
||||
wget \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN locale-gen en_US.UTF-8
|
||||
# We cannot use update-locale because docker will not use the env variables
|
||||
|
||||
29
README.md
29
README.md
@@ -22,10 +22,11 @@ docker run -it -p 127.0.0.1:8080:8080 -v "${HOME}/.local/share/code-server:/home
|
||||
|
||||
### Requirements
|
||||
|
||||
- Minimum GLIBC version of 2.17 and a minimum version of GLIBCXX of 3.4.15.
|
||||
- This is the main requirement for building Visual Studio Code. We cannot go lower than this.
|
||||
- A 64-bit host with at least 1GB RAM and 2 cores.
|
||||
- 1 core hosts would work but not optimally.
|
||||
- 64-bit host.
|
||||
- At least 1GB of RAM.
|
||||
- 2 cores or more are recommended (1 core works but not optimally).
|
||||
- Secure connection over HTTPS or localhost (required for service workers).
|
||||
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
|
||||
- Docker (for Docker versions of `code-server`).
|
||||
|
||||
### Run over SSH
|
||||
@@ -59,23 +60,23 @@ arguments when launching code-server with Docker. See
|
||||
### Build
|
||||
|
||||
See
|
||||
[VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
|
||||
[VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
|
||||
before building.
|
||||
|
||||
```shell
|
||||
export OUT=/path/to/output/build # Optional if only building. Required if also developing.
|
||||
yarn build ${vscodeVersion} ${codeServerVersion} # See travis.yml for the VS Code version to use.
|
||||
# The code-server version can be anything you want.
|
||||
node ~/path/to/output/build/out/vs/server/main.js # You can run the built JavaScript with Node.
|
||||
node /path/to/output/build/out/vs/server/main.js # You can run the built JavaScript with Node.
|
||||
yarn binary ${vscodeVersion} ${codeServerVersion} # Or you can package it into a binary.
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Authentication
|
||||
To enable built-in password authentication use `code-server --auth password`. By
|
||||
default it will use a randomly generated password but you can set the
|
||||
`$PASSWORD` environment variable to use your own.
|
||||
By default `code-server` enables password authentication using a randomly
|
||||
generated password. You can set the `PASSWORD` environment variable to use your
|
||||
own instead or use `--auth none` to disable password authentication.
|
||||
|
||||
Do not expose `code-server` to the open internet without some form of
|
||||
authentication.
|
||||
@@ -134,7 +135,7 @@ data collected to improve code-server.
|
||||
### Development
|
||||
|
||||
See
|
||||
[VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
|
||||
[VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
|
||||
before developing.
|
||||
|
||||
```shell
|
||||
@@ -154,8 +155,7 @@ yarn start
|
||||
```
|
||||
|
||||
If you run into issues about a different version of Node being used, try running
|
||||
`npm rebuild` in the VS Code directory and ignore the error at the end from
|
||||
`vscode-ripgrep`.
|
||||
`npm rebuild` in the VS Code directory.
|
||||
|
||||
### Upgrading VS Code
|
||||
|
||||
@@ -170,7 +170,6 @@ directory.
|
||||
|
||||
Our changes include:
|
||||
|
||||
- Change the remote schema to `code-server`.
|
||||
- Allow multiple extension directories (both user and built-in).
|
||||
- Modify the loader, websocket, webview, service worker, and asset requests to
|
||||
use the URL of the page as a base (and TLS if necessary for the websocket).
|
||||
@@ -189,8 +188,8 @@ Our changes include:
|
||||
|
||||
## Enterprise
|
||||
|
||||
Visit [our enterprise page](https://coder.com/enterprise) for more information
|
||||
about our enterprise offering.
|
||||
Visit [our enterprise page](https://coder.com) for more information about our
|
||||
enterprise offering.
|
||||
|
||||
## Commercialization
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"remoteAddress\":\"<HOST>\"
|
||||
failregex = ^Failed login attempt\s+{\"remoteAddress\":\"<HOST>\"
|
||||
|
||||
# Use this instead for proxies (ensure the proxy is configured to send the
|
||||
# X-Forwarded-For header).
|
||||
# failregex = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"xForwardedFor\":\"<HOST>\"
|
||||
# failregex = ^Failed login attempt\s+{\"xForwardedFor\":\"<HOST>\"
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
@@ -30,6 +30,6 @@ accessible from the internet (use localhost or block it in your firewall).
|
||||
## Fail2Ban
|
||||
Fail2Ban allows for automatically banning and logging repeated failed
|
||||
authentication attempts for many applications through regex filters. A working
|
||||
filter for code-server can be found in `./code-server.fail2ban.conf`. Once this
|
||||
filter for code-server can be found in `./examples/fail2ban.conf`. Once this
|
||||
is installed and configured correctly, repeated failed login attempts should
|
||||
automatically be banned from connecting to your server.
|
||||
|
||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
code-server:
|
||||
container_name: code-server
|
||||
image: codercom/code-server
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- "${PWD}:/home/coder/project"
|
||||
- "${HOME}/.local/share/code-server:/home/coder/.local/share/code-server"
|
||||
environment:
|
||||
PASSWORD: ${PASSWORD}
|
||||
@@ -11,7 +11,7 @@
|
||||
"patch:apply": "cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coder/nbin": "^1.2.2",
|
||||
"@coder/nbin": "^1.2.3",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/node": "^10.12.12",
|
||||
"@types/pem": "^1.9.5",
|
||||
|
||||
@@ -303,14 +303,16 @@ class Builder {
|
||||
]);
|
||||
});
|
||||
|
||||
// This is so it doesn't get cached along with VS Code. There's no point
|
||||
// since there isn't anything like an incremental build.
|
||||
await this.task("Removing build files for smaller cache", () => {
|
||||
// Prevent needless cache changes.
|
||||
await this.task("Cleaning for smaller cache", () => {
|
||||
return Promise.all([
|
||||
fs.remove(serverPath),
|
||||
fs.remove(path.join(vscodeSourcePath, "out-vscode")),
|
||||
fs.remove(path.join(vscodeSourcePath, "out-vscode-min")),
|
||||
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 });
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts
|
||||
index 6d41e85e42..f845d0bf9e 100644
|
||||
index 6d41e85e42..64f39687a4 100644
|
||||
--- a/src/vs/base/common/network.ts
|
||||
+++ b/src/vs/base/common/network.ts
|
||||
@@ -48,7 +48,9 @@ export namespace Schemas {
|
||||
|
||||
export const command: string = 'command';
|
||||
|
||||
- export const vscodeRemote: string = 'vscode-remote';
|
||||
+ // NOTE@coder: Changed this so it'll be reflected in the explorer to prevent
|
||||
+ // confusion with vscode-remote itself.
|
||||
+ export const vscodeRemote: string = 'code-server';
|
||||
|
||||
export const vscodeRemoteResource: string = 'vscode-remote-resource';
|
||||
|
||||
@@ -96,12 +98,12 @@ class RemoteAuthoritiesImpl {
|
||||
@@ -96,12 +96,12 @@ class RemoteAuthoritiesImpl {
|
||||
if (host && host.indexOf(':') !== -1) {
|
||||
host = `[${host}]`;
|
||||
}
|
||||
@@ -50,6 +39,21 @@ index a657f4a4d9..66bd13dffa 100644
|
||||
} else if (typeof process === 'object') {
|
||||
_isWindows = (process.platform === 'win32');
|
||||
_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
|
||||
index 3ae24454cb..fac8679290 100644
|
||||
--- a/src/vs/base/node/languagePacks.js
|
||||
@@ -87,7 +91,7 @@ index 990755c4f3..06449bb9cb 100644
|
||||
+ extraBuiltinExtensionPaths: string[];
|
||||
}
|
||||
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
|
||||
+++ b/src/vs/platform/environment/node/argv.ts
|
||||
@@ -58,6 +58,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
@@ -99,6 +103,15 @@ index 3e48fe4ddd..e0962b8736 100644
|
||||
'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.") },
|
||||
'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
|
||||
index f7d207009d..5c37b52dab 100644
|
||||
--- a/src/vs/platform/environment/node/environmentService.ts
|
||||
@@ -606,6 +619,34 @@ index 84c46faa36..957e8412e1 100644
|
||||
|
||||
if (!this.configuration.userDataProvider) {
|
||||
const remoteUserDataUri = this.getRemoteUserDataUri();
|
||||
diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts
|
||||
index 53de865d8f..df234821a9 100644
|
||||
--- a/src/vs/workbench/common/resources.ts
|
||||
+++ b/src/vs/workbench/common/resources.ts
|
||||
@@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
+import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
||||
|
||||
@@ -63,7 +64,7 @@ export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
||||
set(value: URI | null) {
|
||||
if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
|
||||
this._resourceKey.set(value);
|
||||
- this._schemeKey.set(value ? value.scheme : null);
|
||||
+ this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null);
|
||||
this._filenameKey.set(value ? basename(value) : null);
|
||||
this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
|
||||
this._extensionKey.set(value ? extname(value) : null);
|
||||
@@ -200,4 +201,4 @@ export class ResourceGlobMatcher extends Disposable {
|
||||
|
||||
return !!expressionForRoot(resourcePathToMatch);
|
||||
}
|
||||
-}
|
||||
\ No newline at end of file
|
||||
+}
|
||||
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
|
||||
index 1f4cd95f65..061931cbde 100644
|
||||
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
|
||||
@@ -771,6 +812,21 @@ index 3bdfa1a79f..ded21cf9c6 100644
|
||||
|
||||
// register services that only throw errors
|
||||
function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
|
||||
diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts
|
||||
index 3b5706ce76..f390ed35dc 100644
|
||||
--- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts
|
||||
+++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts
|
||||
@@ -36,7 +36,9 @@ const nativeAddEventLister = addEventListener.bind(self);
|
||||
self.addEventLister = () => console.trace(`'addEventListener' has been blocked`);
|
||||
|
||||
self.indexedDB.open = () => console.trace(`'indexedDB.open' has been blocked`);
|
||||
-self.caches.open = () => console.trace(`'indexedDB.caches' has been blocked`);
|
||||
+if (self.caches) { // NOTE@coder: on insecure domains this exists in Firefox but not Chromium or Safari.
|
||||
+ self.caches.open = () => console.trace(`'indexedDB.caches' has been blocked`);
|
||||
+}
|
||||
|
||||
//#endregion ---
|
||||
|
||||
diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
||||
index 99394090da..4891e0fece 100644
|
||||
--- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { Emitter } from "vs/base/common/event";
|
||||
import { URI } from "vs/base/common/uri";
|
||||
import { localize } from "vs/nls";
|
||||
import { Extensions, IConfigurationRegistry } from "vs/platform/configuration/common/configurationRegistry";
|
||||
import { registerSingleton } from "vs/platform/instantiation/common/extensions";
|
||||
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
|
||||
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
|
||||
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
|
||||
import { Registry } from "vs/platform/registry/common/platform";
|
||||
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
|
||||
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
||||
import { coderApi, vscodeApi } from "vs/server/src/browser/api";
|
||||
import { IUploadService, UploadService } from "vs/server/src/browser/upload";
|
||||
import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy";
|
||||
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 { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
|
||||
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
|
||||
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
|
||||
|
||||
class TelemetryService extends TelemetryChannelClient {
|
||||
public constructor(
|
||||
@@ -21,6 +25,23 @@ class TelemetryService extends TelemetryChannelClient {
|
||||
}
|
||||
}
|
||||
|
||||
const TELEMETRY_SECTION_ID = "telemetry";
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
|
||||
"id": TELEMETRY_SECTION_ID,
|
||||
"order": 110,
|
||||
"type": "object",
|
||||
"title": localize("telemetryConfigurationTitle", "Telemetry"),
|
||||
"properties": {
|
||||
"telemetry.enableTelemetry": {
|
||||
"type": "boolean",
|
||||
"description": localize("telemetry.enableTelemetry", "Enable usage data and errors to be sent to a Microsoft online service."),
|
||||
"default": true,
|
||||
"tags": ["usesOnlineServices"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
@@ -79,7 +100,7 @@ export const withQuery = (url: string, replace: Query): string => {
|
||||
const uri = URI.parse(url);
|
||||
const query = { ...replace };
|
||||
uri.query.split("&").forEach((kv) => {
|
||||
const [key, value] = kv.split("=", 2);
|
||||
const [key, value] = split(kv, "=");
|
||||
if (!(key in query)) {
|
||||
query[key] = value;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<link rel="manifest" href="./manifest.json">
|
||||
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/out/vs/server/src/media/code-server.png" />
|
||||
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.css">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
|
||||
<!-- Prefetch to avoid waterfall -->
|
||||
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js">
|
||||
|
||||
10
src/common/util.ts
Normal file
10
src/common/util.ts
Normal 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, ""];
|
||||
};
|
||||
@@ -47,7 +47,6 @@ const getArgs = (): Args => {
|
||||
case "wait":
|
||||
case "disable-gpu":
|
||||
// TODO: pretty sure these don't work but not 100%.
|
||||
case "max-memory":
|
||||
case "prof-startup":
|
||||
case "inspect-extensions":
|
||||
case "inspect-brk-extensions":
|
||||
@@ -82,22 +81,21 @@ const getArgs = (): Args => {
|
||||
return validatePaths(args);
|
||||
};
|
||||
|
||||
const startVscode = async (): Promise<void | void[]> => {
|
||||
const args = getArgs();
|
||||
const startVscode = async (args: Args): Promise<void | void[]> => {
|
||||
const extra = args["_"] || [];
|
||||
const options = {
|
||||
auth: args.auth,
|
||||
auth: args.auth || AuthType.Password,
|
||||
basePath: args["base-path"],
|
||||
cert: args.cert,
|
||||
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,
|
||||
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.`);
|
||||
} else if (options.auth && !options.password) {
|
||||
} else if (options.auth === "password" && !options.password) {
|
||||
options.password = await generatePassword();
|
||||
}
|
||||
|
||||
@@ -125,10 +123,13 @@ const startVscode = async (): Promise<void | void[]> => {
|
||||
]);
|
||||
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(" - To use your own password, set the PASSWORD environment variable");
|
||||
} else if (options.auth) {
|
||||
logger.info(" - To use your own password, set the PASSWORD environment variable");
|
||||
if (!args.auth) {
|
||||
logger.info(" - To disable use `--auth none`");
|
||||
}
|
||||
} else if (options.auth === "password") {
|
||||
logger.info(" - Using custom password for authentication");
|
||||
} else {
|
||||
logger.info(" - No authentication");
|
||||
@@ -152,8 +153,7 @@ const startVscode = async (): Promise<void | void[]> => {
|
||||
}
|
||||
};
|
||||
|
||||
const startCli = (): boolean | Promise<void> => {
|
||||
const args = getArgs();
|
||||
const startCli = (args: Args): boolean | Promise<void> => {
|
||||
if (args.help) {
|
||||
const executable = `${product.applicationName}${os.platform() === "win32" ? ".exe" : ""}`;
|
||||
console.log(buildHelpMessage(product.nameLong, executable, product.codeServerVersion, OPTIONS, false));
|
||||
@@ -193,14 +193,17 @@ const startCli = (): boolean | Promise<void> => {
|
||||
export class WrapperProcess {
|
||||
private process?: cp.ChildProcess;
|
||||
private started?: Promise<void>;
|
||||
private currentVersion = product.codeServerVersion;
|
||||
|
||||
public constructor() {
|
||||
public constructor(private readonly args: Args) {
|
||||
ipcMain.onMessage(async (message) => {
|
||||
switch (message) {
|
||||
switch (message.type) {
|
||||
case "relaunch":
|
||||
logger.info("Relaunching...");
|
||||
logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`);
|
||||
this.currentVersion = message.version;
|
||||
this.started = undefined;
|
||||
if (this.process) {
|
||||
this.process.removeAllListeners();
|
||||
this.process.kill();
|
||||
}
|
||||
try {
|
||||
@@ -220,17 +223,35 @@ export class WrapperProcess {
|
||||
public start(): Promise<void> {
|
||||
if (!this.started) {
|
||||
const child = this.spawn();
|
||||
this.started = ipcMain.handshake(child);
|
||||
this.started = ipcMain.handshake(child).then(() => {
|
||||
child.once("exit", (code) => exit(code!));
|
||||
});
|
||||
this.process = child;
|
||||
}
|
||||
return this.started;
|
||||
}
|
||||
|
||||
private spawn(): cp.ChildProcess {
|
||||
return cp.spawn(process.argv[0], process.argv.slice(1), {
|
||||
// Flags to pass along to the Node binary. We use the environment variable
|
||||
// since otherwise the code-server binary will swallow them.
|
||||
const maxMemory = this.args["max-memory"] || 2048;
|
||||
let nodeOptions = `${process.env.NODE_OPTIONS || ""} ${this.args["js-flags"] || ""}`;
|
||||
if (!/max_old_space_size=(\d+)/g.exec(nodeOptions)) {
|
||||
nodeOptions += ` --max_old_space_size=${maxMemory}`;
|
||||
}
|
||||
|
||||
// If we're using loose files then we need to specify the path. If we're in
|
||||
// the binary we need to let the binary determine the path (via nbin) since
|
||||
// it could be different between binaries which presents a problem when
|
||||
// upgrading (different version numbers or different staging directories).
|
||||
const isBinary = (global as any).NBIN_LOADED;
|
||||
return cp.spawn(process.argv[0], process.argv.slice(isBinary ? 2 : 1), {
|
||||
env: {
|
||||
...process.env,
|
||||
LAUNCH_VSCODE: "true",
|
||||
NBIN_BYPASS: undefined,
|
||||
VSCODE_PARENT_PID: process.pid.toString(),
|
||||
NODE_OPTIONS: nodeOptions,
|
||||
},
|
||||
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
||||
});
|
||||
@@ -238,11 +259,12 @@ export class WrapperProcess {
|
||||
}
|
||||
|
||||
const main = async(): Promise<boolean | void | void[]> => {
|
||||
const args = getArgs();
|
||||
if (process.env.LAUNCH_VSCODE) {
|
||||
await ipcMain.handshake();
|
||||
return startVscode();
|
||||
return startVscode(args);
|
||||
}
|
||||
return startCli() || new WrapperProcess().start();
|
||||
return startCli(args) || new WrapperProcess(args).start();
|
||||
};
|
||||
|
||||
const exit = process.exit;
|
||||
@@ -251,6 +273,20 @@ process.exit = function (code?: number) {
|
||||
console.warn(err.stack);
|
||||
} as (code?: number) => never;
|
||||
|
||||
// Copy the extension host behavior of killing oneself if the parent dies. This
|
||||
// also exists in bootstrap-fork.js but spawning with that won't work because we
|
||||
// override process.exit.
|
||||
if (typeof process.env.VSCODE_PARENT_PID !== "undefined") {
|
||||
const parentPid = parseInt(process.env.VSCODE_PARENT_PID, 10);
|
||||
setInterval(() => {
|
||||
try {
|
||||
process.kill(parentPid, 0); // Throws an exception if the process doesn't exist anymore.
|
||||
} catch (e) {
|
||||
exit();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// It's possible that the pipe has closed (for example if you run code-server
|
||||
// --version | head -1). Assume that means we're done.
|
||||
if (!process.stdout.isTTY) {
|
||||
|
||||
@@ -6,7 +6,12 @@ enum ControlMessage {
|
||||
okFromChild = "ok<",
|
||||
}
|
||||
|
||||
export type Message = "relaunch";
|
||||
interface RelaunchMessage {
|
||||
type: "relaunch";
|
||||
version: string;
|
||||
}
|
||||
|
||||
export type Message = RelaunchMessage;
|
||||
|
||||
class IpcMain {
|
||||
protected readonly _onMessage = new Emitter<Message>();
|
||||
@@ -41,11 +46,15 @@ class IpcMain {
|
||||
});
|
||||
}
|
||||
|
||||
public relaunch(): void {
|
||||
public relaunch(version: string): void {
|
||||
this.send({ type: "relaunch", version });
|
||||
}
|
||||
|
||||
private send(message: Message): void {
|
||||
if (!process.send) {
|
||||
throw new Error("Not a child process with IPC enabled");
|
||||
}
|
||||
process.send("relaunch");
|
||||
process.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,13 +56,14 @@ import { resolveCommonProperties } from "vs/platform/telemetry/node/commonProper
|
||||
import { UpdateChannel } from "vs/platform/update/electron-main/updateIpc";
|
||||
import { INodeProxyService, NodeProxyChannel } from "vs/server/src/common/nodeProxy";
|
||||
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 { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/src/node/connection";
|
||||
import { TelemetryClient } from "vs/server/src/node/insights";
|
||||
import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/src/node/nls";
|
||||
import { Protocol } from "vs/server/src/node/protocol";
|
||||
import { UpdateService } from "vs/server/src/node/update";
|
||||
import { AuthType, getMediaMime, getUriTransformer, localRequire, tmpdir } from "vs/server/src/node/util";
|
||||
import { AuthType, getMediaMime, getUriTransformer, hash, localRequire, tmpdir } from "vs/server/src/node/util";
|
||||
import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService";
|
||||
import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api";
|
||||
|
||||
@@ -100,6 +101,10 @@ export interface LoginPayload {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface AuthPayload {
|
||||
key?: string[];
|
||||
}
|
||||
|
||||
export class HttpError extends Error {
|
||||
public constructor(message: string, public readonly code: number) {
|
||||
super(message);
|
||||
@@ -110,12 +115,12 @@ export class HttpError extends Error {
|
||||
}
|
||||
|
||||
export interface ServerOptions {
|
||||
readonly auth?: AuthType;
|
||||
readonly auth: AuthType;
|
||||
readonly basePath?: string;
|
||||
readonly connectionToken?: string;
|
||||
readonly cert?: string;
|
||||
readonly certKey?: string;
|
||||
readonly folderUri?: string;
|
||||
readonly openUri?: string;
|
||||
readonly host?: string;
|
||||
readonly password?: string;
|
||||
readonly port?: number;
|
||||
@@ -133,9 +138,10 @@ export abstract class Server {
|
||||
|
||||
public constructor(options: ServerOptions) {
|
||||
this.options = {
|
||||
host: options.auth && options.cert ? "0.0.0.0" : "localhost",
|
||||
host: options.auth === "password" && options.cert ? "0.0.0.0" : "localhost",
|
||||
...options,
|
||||
basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "",
|
||||
password: options.password ? hash(options.password) : undefined,
|
||||
};
|
||||
this.protocol = this.options.cert ? "https" : "http";
|
||||
if (this.protocol === "https") {
|
||||
@@ -193,6 +199,11 @@ export abstract class Server {
|
||||
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> {
|
||||
const filePath = this.ensureAuthorizedFilePath(...parts);
|
||||
return { stream: tarFs.pack(filePath), filePath, mime: "application/tar", cache: true };
|
||||
@@ -207,8 +218,8 @@ export abstract class Server {
|
||||
}
|
||||
|
||||
protected withBase(request: http.IncomingMessage, path: string): string {
|
||||
const split = request.url ? request.url.split("?", 2) : [];
|
||||
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${split.length === 2 ? `?${split[1]}` : ""}`;
|
||||
const [, query] = request.url ? split(request.url, "?") : [];
|
||||
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${query ? `?${query}` : ""}`;
|
||||
}
|
||||
|
||||
private isAllowedRequestPath(path: string): boolean {
|
||||
@@ -269,7 +280,7 @@ export abstract class Server {
|
||||
base = path.normalize(base);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -300,7 +311,7 @@ export abstract class Server {
|
||||
response.cache = true;
|
||||
return response;
|
||||
case "/login":
|
||||
if (!this.options.auth || requestPath !== "/index.html") {
|
||||
if (this.options.auth !== "password" || requestPath !== "/index.html") {
|
||||
throw new HttpError("Not found", HttpCode.NotFound);
|
||||
}
|
||||
return this.tryLogin(request);
|
||||
@@ -351,16 +362,25 @@ export abstract class Server {
|
||||
}
|
||||
|
||||
private async tryLogin(request: http.IncomingMessage): Promise<Response> {
|
||||
if (this.authenticate(request) && (request.method === "GET" || request.method === "POST")) {
|
||||
return { redirect: "/" };
|
||||
const redirect = (password: string | true) => {
|
||||
return {
|
||||
redirect: "/",
|
||||
headers: typeof password === "string"
|
||||
? { "Set-Cookie": `key=${password}; Path=${this.options.basePath || "/"}; HttpOnly; SameSite=strict` }
|
||||
: {},
|
||||
};
|
||||
};
|
||||
const providedPassword = this.authenticate(request);
|
||||
if (providedPassword && (request.method === "GET" || request.method === "POST")) {
|
||||
return redirect(providedPassword);
|
||||
}
|
||||
if (request.method === "POST") {
|
||||
const data = await this.getData<LoginPayload>(request);
|
||||
if (this.authenticate(request, data)) {
|
||||
return {
|
||||
redirect: "/",
|
||||
headers: { "Set-Cookie": `password=${data.password}` }
|
||||
};
|
||||
const password = this.authenticate(request, {
|
||||
key: typeof data.password === "string" ? [hash(data.password)] : undefined,
|
||||
});
|
||||
if (password) {
|
||||
return redirect(password);
|
||||
}
|
||||
console.error("Failed login attempt", JSON.stringify({
|
||||
xForwardedFor: request.headers["x-forwarded-for"],
|
||||
@@ -420,23 +440,33 @@ export abstract class Server {
|
||||
: Promise.resolve({} as T);
|
||||
}
|
||||
|
||||
private authenticate(request: http.IncomingMessage, payload?: LoginPayload): boolean {
|
||||
if (!this.options.auth) {
|
||||
private authenticate(request: http.IncomingMessage, payload?: AuthPayload): string | boolean {
|
||||
if (this.options.auth === "none") {
|
||||
return true;
|
||||
}
|
||||
const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index");
|
||||
if (typeof payload === "undefined") {
|
||||
payload = this.parseCookies<LoginPayload>(request);
|
||||
payload = this.parseCookies<AuthPayload>(request);
|
||||
}
|
||||
return !!this.options.password && safeCompare(payload.password || "", this.options.password);
|
||||
if (this.options.password && payload.key) {
|
||||
for (let i = 0; i < payload.key.length; ++i) {
|
||||
if (safeCompare(payload.key[i], this.options.password)) {
|
||||
return payload.key[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private parseCookies<T extends object>(request: http.IncomingMessage): T {
|
||||
const cookies: { [key: string]: string } = {};
|
||||
const cookies: { [key: string]: string[] } = {};
|
||||
if (request.headers.cookie) {
|
||||
request.headers.cookie.split(";").forEach((keyValue) => {
|
||||
const [key, value] = keyValue.split("=", 2);
|
||||
cookies[key.trim()] = decodeURI(value);
|
||||
const [key, value] = split(keyValue, "=");
|
||||
if (!cookies[key]) {
|
||||
cookies[key] = [];
|
||||
}
|
||||
cookies[key].push(decodeURI(value));
|
||||
});
|
||||
}
|
||||
return cookies as T;
|
||||
@@ -469,6 +499,9 @@ export class MainServer extends Server {
|
||||
private readonly proxyTimeout = 5000;
|
||||
|
||||
private settings: Settings = {};
|
||||
private heartbeatTimer?: NodeJS.Timeout;
|
||||
private heartbeatInterval = 60000;
|
||||
private lastHeartbeat = 0;
|
||||
|
||||
public constructor(options: ServerOptions, args: ParsedArgs) {
|
||||
super(options);
|
||||
@@ -486,6 +519,7 @@ export class MainServer extends Server {
|
||||
}
|
||||
|
||||
protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
|
||||
this.heartbeat();
|
||||
if (!parsedUrl.query.reconnectionToken) {
|
||||
throw new Error("Reconnection token is missing from query parameters");
|
||||
}
|
||||
@@ -509,12 +543,13 @@ export class MainServer extends Server {
|
||||
parsedUrl: url.UrlWithParsedQuery,
|
||||
request: http.IncomingMessage,
|
||||
): Promise<Response> {
|
||||
this.heartbeat();
|
||||
switch (base) {
|
||||
case "/": return this.getRoot(request, parsedUrl);
|
||||
case "/resource":
|
||||
case "/vscode-remote-resource":
|
||||
if (typeof parsedUrl.query.path === "string") {
|
||||
return this.getResource(parsedUrl.query.path);
|
||||
return this.getAnyResource(parsedUrl.query.path);
|
||||
}
|
||||
break;
|
||||
case "/tar":
|
||||
@@ -523,8 +558,8 @@ export class MainServer extends Server {
|
||||
}
|
||||
break;
|
||||
case "/webview":
|
||||
if (requestPath.indexOf("/vscode-resource") === 0) {
|
||||
return this.getResource(requestPath.replace(/^\/vscode-resource/, ""));
|
||||
if (/^\/vscode-resource/.test(requestPath)) {
|
||||
return this.getAnyResource(requestPath.replace(/^\/vscode-resource(\/file)?/, ""));
|
||||
}
|
||||
return this.getResource(
|
||||
this.rootPath,
|
||||
@@ -541,9 +576,9 @@ export class MainServer extends Server {
|
||||
util.promisify(fs.readFile)(filePath, "utf8"),
|
||||
this.getFirstValidPath([
|
||||
{ path: parsedUrl.query.workspace, workspace: true },
|
||||
{ path: parsedUrl.query.folder },
|
||||
{ path: parsedUrl.query.folder, workspace: false },
|
||||
(await this.readSettings()).lastVisited,
|
||||
{ path: this.options.folderUri }
|
||||
{ path: this.options.openUri }
|
||||
]),
|
||||
this.servicesPromise,
|
||||
]);
|
||||
@@ -587,7 +622,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> {
|
||||
const logger = this.services.get(ILogService) as ILogService;
|
||||
@@ -602,9 +639,8 @@ export class MainServer extends Server {
|
||||
const uri = URI.file(sanitizeFilePath(paths[j], cwd));
|
||||
try {
|
||||
const stat = await util.promisify(fs.stat)(uri.fsPath);
|
||||
// Workspace must be a file.
|
||||
if (!!startPath.workspace !== stat.isDirectory()) {
|
||||
return { uri, workspace: startPath.workspace };
|
||||
if (typeof startPath.workspace === "undefined" || startPath.workspace !== stat.isDirectory()) {
|
||||
return { uri, workspace: !stat.isDirectory() };
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(error.message);
|
||||
@@ -871,4 +907,48 @@ export class MainServer extends Server {
|
||||
(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IFileService } from "vs/platform/files/common/files";
|
||||
import { ILogService } from "vs/platform/log/common/log";
|
||||
import product from "vs/platform/product/common/product";
|
||||
import { asJson, IRequestService } from "vs/platform/request/common/request";
|
||||
import { AvailableForDownload, State, UpdateType } from "vs/platform/update/common/update";
|
||||
import { AvailableForDownload, State, UpdateType, StateType } from "vs/platform/update/common/update";
|
||||
import { AbstractUpdateService } from "vs/platform/update/electron-main/abstractUpdateService";
|
||||
import { ipcMain } from "vs/server/src/node/ipc";
|
||||
import { extract } from "vs/server/src/node/marketplace";
|
||||
@@ -37,6 +37,9 @@ export class UpdateService extends AbstractUpdateService {
|
||||
super(null, configurationService, environmentService, requestService, logService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the currently installed version is the latest.
|
||||
*/
|
||||
public async isLatestVersion(latest?: IUpdate | null): Promise<boolean | undefined> {
|
||||
if (!latest) {
|
||||
latest = await this.getLatestVersion();
|
||||
@@ -44,8 +47,12 @@ export class UpdateService extends AbstractUpdateService {
|
||||
if (latest) {
|
||||
const latestMajor = parseInt(latest.name);
|
||||
const currentMajor = parseInt(product.codeServerVersion);
|
||||
return !isNaN(latestMajor) && !isNaN(currentMajor) &&
|
||||
currentMajor <= latestMajor && latest.name === product.codeServerVersion;
|
||||
// If these are invalid versions we can't compare meaningfully.
|
||||
return isNaN(latestMajor) || isNaN(currentMajor) ||
|
||||
// This can happen when there is a pre-release for a new major version.
|
||||
currentMajor > latestMajor ||
|
||||
// Otherwise assume that if it's not the same then we're out of date.
|
||||
latest.name === product.codeServerVersion;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -55,14 +62,16 @@ export class UpdateService extends AbstractUpdateService {
|
||||
}
|
||||
|
||||
public async doQuitAndInstall(): Promise<void> {
|
||||
ipcMain.relaunch();
|
||||
if (this.state.type === StateType.Ready) {
|
||||
ipcMain.relaunch(this.state.update.version);
|
||||
}
|
||||
}
|
||||
|
||||
protected async doCheckForUpdates(context: any): Promise<void> {
|
||||
this.setState(State.CheckingForUpdates(context));
|
||||
try {
|
||||
const update = await this.getLatestVersion();
|
||||
if (!update || this.isLatestVersion(update)) {
|
||||
if (!update || await this.isLatestVersion(update)) {
|
||||
this.setState(State.Idle(UpdateType.Archive));
|
||||
} else {
|
||||
this.setState(State.AvailableForDownload({
|
||||
|
||||
@@ -4,22 +4,19 @@ module.exports = (remoteAuthority) => {
|
||||
return {
|
||||
transformIncoming: (uri) => {
|
||||
switch (uri.scheme) {
|
||||
case "code-server": return { scheme: "file", path: uri.path };
|
||||
case "file": return { scheme: "code-server", path: uri.path };
|
||||
case "vscode-remote": return { scheme: "file", path: uri.path };
|
||||
default: return uri;
|
||||
}
|
||||
},
|
||||
transformOutgoing: (uri) => {
|
||||
switch (uri.scheme) {
|
||||
case "code-server": return { scheme: "file", path: uri.path };
|
||||
case "file": return { scheme: "code-server", authority: remoteAuthority, path: uri.path };
|
||||
case "file": return { scheme: "vscode-remote", authority: remoteAuthority, path: uri.path };
|
||||
default: return uri;
|
||||
}
|
||||
},
|
||||
transformOutgoingScheme: (scheme) => {
|
||||
switch (scheme) {
|
||||
case "code-server": return "file";
|
||||
case "file": return "code-server";
|
||||
case "file": return "vscode-remote";
|
||||
default: return scheme;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import { mkdirp } from "vs/base/node/pfs";
|
||||
|
||||
export enum AuthType {
|
||||
Password = "password",
|
||||
None = "none",
|
||||
}
|
||||
|
||||
export enum FormatType {
|
||||
@@ -66,6 +67,10 @@ export const generatePassword = async (length: number = 24): Promise<string> =>
|
||||
return buffer.toString("hex").substring(0, length);
|
||||
};
|
||||
|
||||
export const hash = (str: string): string => {
|
||||
return crypto.createHash("sha256").update(str).digest("hex");
|
||||
};
|
||||
|
||||
export const getMediaMime = (filePath?: string): string => {
|
||||
return filePath && (vsGetMediaMime(filePath) || (<{[index: string]: string}>{
|
||||
".css": "text/css",
|
||||
@@ -127,7 +132,7 @@ export const enumToArray = (t: any): string[] => {
|
||||
|
||||
export const buildAllowedMessage = (t: any): string => {
|
||||
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(", ")}`;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
14
yarn.lock
14
yarn.lock
@@ -7,10 +7,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@coder/logger/-/logger-1.1.8.tgz#416a7221d84161ee35eca9cfa93ba9377639b4ee"
|
||||
integrity sha512-NJDC4rZTx0deVYqAxZtJWACq3IrVR59BjFeZebO3i7OfTZZMkkbLsGsCFMnJd5KnX6KjnvvFq4XXtwJ9yf8/YQ==
|
||||
|
||||
"@coder/nbin@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@coder/nbin/-/nbin-1.2.2.tgz#c5f9aaa2a0e84c2a13a4cce895547efbd66730b7"
|
||||
integrity sha512-1Z6aYBRZRY1AQ2xp0jmoz+TXR8M4WaHa9FfVkOPej0KPJjYtEp18I+/6CmffDtBLxSnIai0rc+AA0VhbjCN/rg==
|
||||
"@coder/nbin@^1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@coder/nbin/-/nbin-1.2.3.tgz#793061abc7e1f7e0a9d1b9f854fa8f4121ed4e90"
|
||||
integrity sha512-JGJhkaqCrAF9hQ8e7m29/gbbKqDrBAOJCdjNZv9LKF+67lmHUoJ2QS+eHN+KOtpO4EJeEs4/uq7LSEdT+g3t5w==
|
||||
dependencies:
|
||||
"@coder/logger" "^1.1.8"
|
||||
fs-extra "^7.0.1"
|
||||
@@ -1256,9 +1256,9 @@ minizlib@^1.1.1:
|
||||
minipass "^2.2.1"
|
||||
|
||||
mixin-deep@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
|
||||
integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
|
||||
integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
|
||||
dependencies:
|
||||
for-in "^1.0.2"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
Reference in New Issue
Block a user