Compare commits
32 Commits
2.1638-vsc
...
2.1688-vsc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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
|
||||||
@@ -61,7 +61,7 @@ deploy:
|
|||||||
|
|
||||||
- provider: script
|
- provider: script
|
||||||
skip_cleanup: true
|
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:
|
on:
|
||||||
repo: cdr/code-server
|
repo: cdr/code-server
|
||||||
branch: master
|
branch: master
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -22,10 +22,11 @@ docker run -it -p 127.0.0.1:8080:8080 -v "${HOME}/.local/share/code-server:/home
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- Minimum GLIBC version of 2.17 and a minimum version of GLIBCXX of 3.4.15.
|
- 64-bit host.
|
||||||
- This is the main requirement for building Visual Studio Code. We cannot go lower than this.
|
- At least 1GB of RAM.
|
||||||
- A 64-bit host with at least 1GB RAM and 2 cores.
|
- 2 cores or more are recommended (1 core works but not optimally).
|
||||||
- 1 core hosts would work 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`).
|
- Docker (for Docker versions of `code-server`).
|
||||||
|
|
||||||
### Run over SSH
|
### Run over SSH
|
||||||
@@ -59,14 +60,14 @@ arguments when launching code-server with Docker. See
|
|||||||
### Build
|
### Build
|
||||||
|
|
||||||
See
|
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.
|
before building.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
export OUT=/path/to/output/build # Optional if only building. Required if also developing.
|
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.
|
yarn build ${vscodeVersion} ${codeServerVersion} # See travis.yml for the VS Code version to use.
|
||||||
# The code-server version can be anything you want.
|
# 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.
|
yarn binary ${vscodeVersion} ${codeServerVersion} # Or you can package it into a binary.
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -134,7 +135,7 @@ data collected to improve code-server.
|
|||||||
### Development
|
### Development
|
||||||
|
|
||||||
See
|
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.
|
before developing.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -154,8 +155,7 @@ yarn start
|
|||||||
```
|
```
|
||||||
|
|
||||||
If you run into issues about a different version of Node being used, try running
|
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
|
`npm rebuild` in the VS Code directory.
|
||||||
`vscode-ripgrep`.
|
|
||||||
|
|
||||||
### Upgrading VS Code
|
### Upgrading VS Code
|
||||||
|
|
||||||
@@ -170,7 +170,6 @@ directory.
|
|||||||
|
|
||||||
Our changes include:
|
Our changes include:
|
||||||
|
|
||||||
- Change the remote schema to `code-server`.
|
|
||||||
- Allow multiple extension directories (both user and built-in).
|
- Allow multiple extension directories (both user and built-in).
|
||||||
- Modify the loader, websocket, webview, service worker, and asset requests to
|
- 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).
|
use the URL of the page as a base (and TLS if necessary for the websocket).
|
||||||
|
|||||||
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"
|
"patch:apply": "cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@coder/nbin": "^1.2.2",
|
"@coder/nbin": "^1.2.3",
|
||||||
"@types/fs-extra": "^8.0.1",
|
"@types/fs-extra": "^8.0.1",
|
||||||
"@types/node": "^10.12.12",
|
"@types/node": "^10.12.12",
|
||||||
"@types/pem": "^1.9.5",
|
"@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
|
// 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 });
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,8 @@
|
|||||||
diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts
|
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
|
--- a/src/vs/base/common/network.ts
|
||||||
+++ b/src/vs/base/common/network.ts
|
+++ b/src/vs/base/common/network.ts
|
||||||
@@ -48,7 +48,9 @@ export namespace Schemas {
|
@@ -96,12 +96,12 @@ class RemoteAuthoritiesImpl {
|
||||||
|
|
||||||
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 {
|
|
||||||
if (host && host.indexOf(':') !== -1) {
|
if (host && host.indexOf(':') !== -1) {
|
||||||
host = `[${host}]`;
|
host = `[${host}]`;
|
||||||
}
|
}
|
||||||
@@ -50,6 +39,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 +91,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 +103,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
|
||||||
@@ -606,6 +619,34 @@ index 84c46faa36..957e8412e1 100644
|
|||||||
|
|
||||||
if (!this.configuration.userDataProvider) {
|
if (!this.configuration.userDataProvider) {
|
||||||
const remoteUserDataUri = this.getRemoteUserDataUri();
|
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
|
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
|
index 1f4cd95f65..061931cbde 100644
|
||||||
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
|
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
|
||||||
@@ -771,6 +812,21 @@ index 3bdfa1a79f..ded21cf9c6 100644
|
|||||||
|
|
||||||
// register services that only throw errors
|
// register services that only throw errors
|
||||||
function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
|
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
|
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
|
index 99394090da..4891e0fece 100644
|
||||||
--- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
--- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import { Emitter } from "vs/base/common/event";
|
import { Emitter } from "vs/base/common/event";
|
||||||
import { URI } from "vs/base/common/uri";
|
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 { 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 { Registry } from "vs/platform/registry/common/platform";
|
||||||
|
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(
|
||||||
@@ -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 {
|
class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
|
||||||
private readonly _onClose = new Emitter<void>();
|
private readonly _onClose = new Emitter<void>();
|
||||||
public readonly onClose = this._onClose.event;
|
public readonly onClose = this._onClose.event;
|
||||||
@@ -79,7 +100,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
<link rel="manifest" href="./manifest.json">
|
<link rel="manifest" href="./manifest.json">
|
||||||
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/out/vs/server/src/media/code-server.png" />
|
<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">
|
<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 -->
|
<!-- Prefetch to avoid waterfall -->
|
||||||
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js">
|
<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, ""];
|
||||||
|
};
|
||||||
@@ -90,7 +90,7 @@ const startVscode = async (): Promise<void | void[]> => {
|
|||||||
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,
|
||||||
};
|
};
|
||||||
@@ -196,14 +196,17 @@ const startCli = (): boolean | Promise<void> => {
|
|||||||
export class WrapperProcess {
|
export class WrapperProcess {
|
||||||
private process?: cp.ChildProcess;
|
private process?: cp.ChildProcess;
|
||||||
private started?: Promise<void>;
|
private started?: Promise<void>;
|
||||||
|
private currentVersion = product.codeServerVersion;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
ipcMain.onMessage(async (message) => {
|
ipcMain.onMessage(async (message) => {
|
||||||
switch (message) {
|
switch (message.type) {
|
||||||
case "relaunch":
|
case "relaunch":
|
||||||
logger.info("Relaunching...");
|
logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`);
|
||||||
|
this.currentVersion = message.version;
|
||||||
this.started = undefined;
|
this.started = undefined;
|
||||||
if (this.process) {
|
if (this.process) {
|
||||||
|
this.process.removeAllListeners();
|
||||||
this.process.kill();
|
this.process.kill();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -223,17 +226,26 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private spawn(): cp.ChildProcess {
|
private spawn(): cp.ChildProcess {
|
||||||
return cp.spawn(process.argv[0], process.argv.slice(1), {
|
// 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: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
LAUNCH_VSCODE: "true",
|
LAUNCH_VSCODE: "true",
|
||||||
|
NBIN_BYPASS: undefined,
|
||||||
|
VSCODE_PARENT_PID: process.pid.toString(),
|
||||||
},
|
},
|
||||||
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
||||||
});
|
});
|
||||||
@@ -254,6 +266,20 @@ process.exit = function (code?: number) {
|
|||||||
console.warn(err.stack);
|
console.warn(err.stack);
|
||||||
} as (code?: number) => never;
|
} 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
|
// It's possible that the pipe has closed (for example if you run code-server
|
||||||
// --version | head -1). Assume that means we're done.
|
// --version | head -1). Assume that means we're done.
|
||||||
if (!process.stdout.isTTY) {
|
if (!process.stdout.isTTY) {
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ enum ControlMessage {
|
|||||||
okFromChild = "ok<",
|
okFromChild = "ok<",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Message = "relaunch";
|
interface RelaunchMessage {
|
||||||
|
type: "relaunch";
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Message = RelaunchMessage;
|
||||||
|
|
||||||
class IpcMain {
|
class IpcMain {
|
||||||
protected readonly _onMessage = new Emitter<Message>();
|
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) {
|
if (!process.send) {
|
||||||
throw new Error("Not a child process with IPC enabled");
|
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 { 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";
|
||||||
import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/src/node/nls";
|
import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/src/node/nls";
|
||||||
import { Protocol } from "vs/server/src/node/protocol";
|
import { Protocol } from "vs/server/src/node/protocol";
|
||||||
import { UpdateService } from "vs/server/src/node/update";
|
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 { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService";
|
||||||
import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api";
|
import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api";
|
||||||
|
|
||||||
@@ -100,6 +101,10 @@ export interface LoginPayload {
|
|||||||
password?: string;
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthPayload {
|
||||||
|
key?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export class HttpError extends Error {
|
export class HttpError extends Error {
|
||||||
public constructor(message: string, public readonly code: number) {
|
public constructor(message: string, public readonly code: number) {
|
||||||
super(message);
|
super(message);
|
||||||
@@ -115,7 +120,7 @@ export interface ServerOptions {
|
|||||||
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;
|
||||||
@@ -136,6 +141,7 @@ export abstract class Server {
|
|||||||
host: options.auth === "password" && 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(/\/+$/, "") : "",
|
||||||
|
password: options.password ? hash(options.password) : undefined,
|
||||||
};
|
};
|
||||||
this.protocol = this.options.cert ? "https" : "http";
|
this.protocol = this.options.cert ? "https" : "http";
|
||||||
if (this.protocol === "https") {
|
if (this.protocol === "https") {
|
||||||
@@ -193,6 +199,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 +218,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 {
|
||||||
@@ -351,16 +362,25 @@ export abstract class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async tryLogin(request: http.IncomingMessage): Promise<Response> {
|
private async tryLogin(request: http.IncomingMessage): Promise<Response> {
|
||||||
if (this.authenticate(request) && (request.method === "GET" || request.method === "POST")) {
|
const redirect = (password: string | true) => {
|
||||||
return { redirect: "/" };
|
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") {
|
if (request.method === "POST") {
|
||||||
const data = await this.getData<LoginPayload>(request);
|
const data = await this.getData<LoginPayload>(request);
|
||||||
if (this.authenticate(request, data)) {
|
const password = this.authenticate(request, {
|
||||||
return {
|
key: typeof data.password === "string" ? [hash(data.password)] : undefined,
|
||||||
redirect: "/",
|
});
|
||||||
headers: { "Set-Cookie": `password=${data.password}` }
|
if (password) {
|
||||||
};
|
return redirect(password);
|
||||||
}
|
}
|
||||||
console.error("Failed login attempt", JSON.stringify({
|
console.error("Failed login attempt", JSON.stringify({
|
||||||
xForwardedFor: request.headers["x-forwarded-for"],
|
xForwardedFor: request.headers["x-forwarded-for"],
|
||||||
@@ -420,23 +440,33 @@ export abstract class Server {
|
|||||||
: Promise.resolve({} as T);
|
: Promise.resolve({} as T);
|
||||||
}
|
}
|
||||||
|
|
||||||
private authenticate(request: http.IncomingMessage, payload?: LoginPayload): boolean {
|
private authenticate(request: http.IncomingMessage, payload?: AuthPayload): string | boolean {
|
||||||
if (this.options.auth !== "password") {
|
if (this.options.auth === "none") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index");
|
const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index");
|
||||||
if (typeof payload === "undefined") {
|
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 {
|
private parseCookies<T extends object>(request: http.IncomingMessage): T {
|
||||||
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);
|
if (!cookies[key]) {
|
||||||
|
cookies[key] = [];
|
||||||
|
}
|
||||||
|
cookies[key].push(decodeURI(value));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return cookies as T;
|
return cookies as T;
|
||||||
@@ -469,6 +499,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 +519,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 +543,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 +558,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 +576,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 +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> {
|
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 +639,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 +907,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { IFileService } from "vs/platform/files/common/files";
|
|||||||
import { ILogService } from "vs/platform/log/common/log";
|
import { ILogService } from "vs/platform/log/common/log";
|
||||||
import product from "vs/platform/product/common/product";
|
import product from "vs/platform/product/common/product";
|
||||||
import { asJson, IRequestService } from "vs/platform/request/common/request";
|
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 { AbstractUpdateService } from "vs/platform/update/electron-main/abstractUpdateService";
|
||||||
import { ipcMain } from "vs/server/src/node/ipc";
|
import { ipcMain } from "vs/server/src/node/ipc";
|
||||||
import { extract } from "vs/server/src/node/marketplace";
|
import { extract } from "vs/server/src/node/marketplace";
|
||||||
@@ -37,6 +37,9 @@ export class UpdateService extends AbstractUpdateService {
|
|||||||
super(null, configurationService, environmentService, requestService, logService);
|
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> {
|
public async isLatestVersion(latest?: IUpdate | null): Promise<boolean | undefined> {
|
||||||
if (!latest) {
|
if (!latest) {
|
||||||
latest = await this.getLatestVersion();
|
latest = await this.getLatestVersion();
|
||||||
@@ -44,8 +47,12 @@ export class UpdateService extends AbstractUpdateService {
|
|||||||
if (latest) {
|
if (latest) {
|
||||||
const latestMajor = parseInt(latest.name);
|
const latestMajor = parseInt(latest.name);
|
||||||
const currentMajor = parseInt(product.codeServerVersion);
|
const currentMajor = parseInt(product.codeServerVersion);
|
||||||
return !isNaN(latestMajor) && !isNaN(currentMajor) &&
|
// If these are invalid versions we can't compare meaningfully.
|
||||||
currentMajor <= latestMajor && latest.name === product.codeServerVersion;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -55,14 +62,16 @@ export class UpdateService extends AbstractUpdateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async doQuitAndInstall(): Promise<void> {
|
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> {
|
protected async doCheckForUpdates(context: any): Promise<void> {
|
||||||
this.setState(State.CheckingForUpdates(context));
|
this.setState(State.CheckingForUpdates(context));
|
||||||
try {
|
try {
|
||||||
const update = await this.getLatestVersion();
|
const update = await this.getLatestVersion();
|
||||||
if (!update || this.isLatestVersion(update)) {
|
if (!update || await this.isLatestVersion(update)) {
|
||||||
this.setState(State.Idle(UpdateType.Archive));
|
this.setState(State.Idle(UpdateType.Archive));
|
||||||
} else {
|
} else {
|
||||||
this.setState(State.AvailableForDownload({
|
this.setState(State.AvailableForDownload({
|
||||||
|
|||||||
@@ -4,22 +4,19 @@ module.exports = (remoteAuthority) => {
|
|||||||
return {
|
return {
|
||||||
transformIncoming: (uri) => {
|
transformIncoming: (uri) => {
|
||||||
switch (uri.scheme) {
|
switch (uri.scheme) {
|
||||||
case "code-server": return { scheme: "file", path: uri.path };
|
case "vscode-remote": return { scheme: "file", path: uri.path };
|
||||||
case "file": return { scheme: "code-server", path: uri.path };
|
|
||||||
default: return uri;
|
default: return uri;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transformOutgoing: (uri) => {
|
transformOutgoing: (uri) => {
|
||||||
switch (uri.scheme) {
|
switch (uri.scheme) {
|
||||||
case "code-server": return { scheme: "file", path: uri.path };
|
case "file": return { scheme: "vscode-remote", authority: remoteAuthority, path: uri.path };
|
||||||
case "file": return { scheme: "code-server", authority: remoteAuthority, path: uri.path };
|
|
||||||
default: return uri;
|
default: return uri;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transformOutgoingScheme: (scheme) => {
|
transformOutgoingScheme: (scheme) => {
|
||||||
switch (scheme) {
|
switch (scheme) {
|
||||||
case "code-server": return "file";
|
case "file": return "vscode-remote";
|
||||||
case "file": return "code-server";
|
|
||||||
default: return scheme;
|
default: return scheme;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ export const generatePassword = async (length: number = 24): Promise<string> =>
|
|||||||
return buffer.toString("hex").substring(0, length);
|
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 => {
|
export const getMediaMime = (filePath?: string): string => {
|
||||||
return filePath && (vsGetMediaMime(filePath) || (<{[index: string]: string}>{
|
return filePath && (vsGetMediaMime(filePath) || (<{[index: string]: string}>{
|
||||||
".css": "text/css",
|
".css": "text/css",
|
||||||
|
|||||||
14
yarn.lock
14
yarn.lock
@@ -7,10 +7,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@coder/logger/-/logger-1.1.8.tgz#416a7221d84161ee35eca9cfa93ba9377639b4ee"
|
resolved "https://registry.yarnpkg.com/@coder/logger/-/logger-1.1.8.tgz#416a7221d84161ee35eca9cfa93ba9377639b4ee"
|
||||||
integrity sha512-NJDC4rZTx0deVYqAxZtJWACq3IrVR59BjFeZebO3i7OfTZZMkkbLsGsCFMnJd5KnX6KjnvvFq4XXtwJ9yf8/YQ==
|
integrity sha512-NJDC4rZTx0deVYqAxZtJWACq3IrVR59BjFeZebO3i7OfTZZMkkbLsGsCFMnJd5KnX6KjnvvFq4XXtwJ9yf8/YQ==
|
||||||
|
|
||||||
"@coder/nbin@^1.2.2":
|
"@coder/nbin@^1.2.3":
|
||||||
version "1.2.2"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@coder/nbin/-/nbin-1.2.2.tgz#c5f9aaa2a0e84c2a13a4cce895547efbd66730b7"
|
resolved "https://registry.yarnpkg.com/@coder/nbin/-/nbin-1.2.3.tgz#793061abc7e1f7e0a9d1b9f854fa8f4121ed4e90"
|
||||||
integrity sha512-1Z6aYBRZRY1AQ2xp0jmoz+TXR8M4WaHa9FfVkOPej0KPJjYtEp18I+/6CmffDtBLxSnIai0rc+AA0VhbjCN/rg==
|
integrity sha512-JGJhkaqCrAF9hQ8e7m29/gbbKqDrBAOJCdjNZv9LKF+67lmHUoJ2QS+eHN+KOtpO4EJeEs4/uq7LSEdT+g3t5w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@coder/logger" "^1.1.8"
|
"@coder/logger" "^1.1.8"
|
||||||
fs-extra "^7.0.1"
|
fs-extra "^7.0.1"
|
||||||
@@ -1256,9 +1256,9 @@ minizlib@^1.1.1:
|
|||||||
minipass "^2.2.1"
|
minipass "^2.2.1"
|
||||||
|
|
||||||
mixin-deep@^1.2.0:
|
mixin-deep@^1.2.0:
|
||||||
version "1.3.1"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
|
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
|
||||||
integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
|
integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
|
||||||
dependencies:
|
dependencies:
|
||||||
for-in "^1.0.2"
|
for-in "^1.0.2"
|
||||||
is-extendable "^1.0.1"
|
is-extendable "^1.0.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user