Target a recent commit for VS Code

This is so we can try out the web worker extension host.
This commit is contained in:
Asher 2019-08-29 19:05:48 -05:00
parent 624a4c08b9
commit b901043bfc
No known key found for this signature in database
GPG key ID: D63C1EF81242354A
12 changed files with 443 additions and 580 deletions

View file

@ -8,14 +8,14 @@ matrix:
- os: linux
dist: trusty
env:
- VSCODE_VERSION="1.37.0" MAJOR_VERSION="2" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER" TARGET="linux"
- VSCODE_VERSION="20dd80d91a76cbe80fb1d1979c3a9f02d94ba161" MAJOR_VERSION="2" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER" TARGET="linux"
- os: linux
dist: trusty
env:
- VSCODE_VERSION="1.37.0" MAJOR_VERSION="2" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER" TARGET="alpine"
- VSCODE_VERSION="20dd80d91a76cbe80fb1d1979c3a9f02d94ba161" MAJOR_VERSION="2" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER" TARGET="alpine"
- os: osx
env:
- VSCODE_VERSION="1.37.0" MAJOR_VERSION="2" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER"
- VSCODE_VERSION="20dd80d91a76cbe80fb1d1979c3a9f02d94ba161" MAJOR_VERSION="2" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER"
before_install:
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then export MINIFY="true"; fi
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then export PACKAGE="true"; fi

View file

@ -81,7 +81,7 @@ data collected to improve code-server.
```shell
git clone https://github.com/microsoft/vscode
cd vscode
git checkout 1.37.0
git checkout <see travis.yml for the VS Code version to use here>
git clone https://github.com/cdr/code-server src/vs/server
cd src/vs/server
yarn patch:apply
@ -98,30 +98,28 @@ If you run into issues about a different version of Node being used, try running
`vscode-ripgrep`.
### Upgrading VS Code
We have to patch VS Code to provide and fix some functionality. As the web
portion of VS Code matures, we'll be able to shrink and maybe even entirely
eliminate our patch. In the meantime, however, upgrading the VS Code version
requires ensuring that the patch still applies and has the intended effects.
We patch VS Code to provide and fix some functionality. As the web portion of VS
Code matures, we'll be able to shrink and maybe even entirely eliminate our
patch. In the meantime, however, upgrading the VS Code version requires ensuring
that the patch still applies and has the intended effects.
To generate a new patch, **stage all the changes** you want to be included in
the patch in the VS Code source, then run `yarn patch:generate` in this
directory.
Our changes include:
- Add a `code-server` schema.
- 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).
- Send client-side telemetry through the server.
- Add a file prefix to ignore for temporary files created during upload.
- Insert our upload service for use in editor windows and explorer.
- Modify the log level to get its initial setting from the server.
- Change a regular expression used for mnemonics so it works on Firefox.
- Send client-side telemetry through the server and get the initial log level
from the server.
- Add an upload service for use in editor windows and the explorer along with a
file prefix to ignore for temporary files created during upload.
- Make changing the display language work.
- Make hiding or toggling the menu bar possible.
- Make it possible for us to load code on the client.
- Modify the build process to include our code.
- Fix a CSP issue within webviews.
- Fix an issue displaying extension contributions.
- Make changing the display language work.
## License
[MIT](LICENSE)

View file

@ -133,7 +133,7 @@ function build-task() {
mkdir -p "${stagingPath}"
if [[ ! -d "${sourcePath}" ]] ; then
if ! download-pre-built "vscode-${vscodeVersion}.tar.gz" ; then
git clone https://github.com/microsoft/vscode --quiet \
git clone https://github.com/microsoft/vscode --quiet \
--branch "${vscodeVersion}" --single-branch --depth=1 \
"${sourcePath}"
fi

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ import { OS } from "vs/base/common/platform";
import { URI, UriComponents } from "vs/base/common/uri";
import { transformOutgoingURIs } from "vs/base/common/uriIpc";
import { IServerChannel } from "vs/base/parts/ipc/common/ipc";
import { IDiagnosticInfo } from "vs/platform/diagnostics/common/diagnosticsService";
import { IDiagnosticInfo } from "vs/platform/diagnostics/common/diagnostics";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
import { ExtensionIdentifier, IExtensionDescription } from "vs/platform/extensions/common/extensions";
import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileType, IStat, IWatchOptions } from "vs/platform/files/common/files";
@ -163,11 +163,10 @@ export class FileProviderChannel implements IServerChannel, IDisposable {
}
private transform(resource: UriComponents): URI {
// HACK: for now assume /out is relative to the build (used for the
// walkthrough content).
if (resource.path.indexOf("/out") === 0) {
return URI.file(this.environmentService.appRoot + resource.path);
// This is used by the webview service worker to load resources.
// Used for walkthrough content.
if (resource.path.indexOf("/static") === 0) {
return URI.file(this.environmentService.appRoot + resource.path.replace(/^\/static/, ""));
// Used by the webview service worker to load resources.
} else if (resource.path === "/vscode-resource" && resource.query) {
try {
const query = JSON.parse(resource.query);
@ -185,6 +184,7 @@ export class ExtensionEnvironmentChannel implements IServerChannel {
private readonly environment: IEnvironmentService,
private readonly log: ILogService,
private readonly telemetry: ITelemetryService,
private readonly connectionToken: string,
) {}
public listen(_: unknown, event: string): Event<any> {
@ -207,6 +207,7 @@ export class ExtensionEnvironmentChannel implements IServerChannel {
private async getEnvironmentData(locale: string): Promise<IRemoteAgentEnvironment> {
return {
pid: process.pid,
connectionToken: this.connectionToken,
appRoot: URI.file(this.environment.appRoot),
appSettingsHome: this.environment.appSettingsHome,
settingsPath: this.environment.machineSettingsHome,

View file

@ -7,6 +7,7 @@ import { LocalizationsService } from "vs/platform/localizations/electron-browser
import { IUpdateService } from "vs/platform/update/common/update";
import { UpdateService } from "vs/platform/update/electron-browser/updateService";
import { TelemetryChannelClient } from "vs/server/src/telemetry";
import { IUploadService, UploadService } from 'vs/server/src/upload';
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
class TelemetryService extends TelemetryChannelClient {
@ -18,12 +19,13 @@ class TelemetryService extends TelemetryChannelClient {
}
registerSingleton(ILocalizationsService, LocalizationsService);
registerSingleton(IUpdateService, UpdateService);
registerSingleton(ITelemetryService, TelemetryService);
registerSingleton(IUpdateService, UpdateService);
registerSingleton(IUploadService, UploadService, true);
import "vs/workbench/contrib/update/electron-browser/update.contribution";
import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
import "vs/css!./media/firefox";
import { coderApi, vscodeApi } from "vs/server/src/api";
/**

View file

@ -1,104 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'unsafe-inline'; manifest-src 'self'; img-src 'self';">
<title>Authenticate: code-server</title>
<style>
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
html, body {
background-color: #FFFFFF;
height: 100%;
min-height: 100%;
}
body {
align-items: center;
display: flex;
font-family: "monospace";
justify-content: center;
margin: 0;
padding: 10px;
}
.login-form {
border-radius: 5px;
box-shadow: 0 18px 80px 10px rgba(69, 65, 78, 0.08);
color: #575962;
margin-top: -10%;
max-width: 328px;
padding: 40px;
position: relative;
width: 100%;
}
.login-form > .title {
text-align: center;
text-transform: uppercase;
font-size: 12px;
font-weight: 500;
letter-spacing: 1.5px;
line-height: 15px;
margin-bottom: 0px;
margin-bottom: 5px;
margin-top: 0px;
}
.login-form > .subtitle {
font-size: 19px;
font-weight: bold;
line-height: 25px;
margin-bottom: 45px;
margin: 0;
text-align: center;
}
.login-form > .field {
text-align: left;
font-size: 12px;
color: #797E84;
margin: 16px 0;
}
.login-form > .field > .input {
background: none !important;
border: 1px solid #ccc;
border-radius: 2px;
padding: 5px;
width: 100%;
}
.login-form > .button {
border: none;
border-radius: 24px;
box-shadow: 0 12px 17px 2px rgba(171,173,163,0.14), 0 5px 22px 4px rgba(171,173,163,0.12), 0 7px 8px -4px rgba(171,173,163,0.2);
cursor: pointer;
display: block;
padding: 15px 5px;
width: 100%;
}
.login-form > .button:hover {
background-color: rgb(0, 122, 204);
color: #fff;
}
.error-display {
box-sizing: border-box;
color: #bb2d0f;
font-size: 14px;
font-weight: 400;
line-height: 12px;
padding: 20px 8px 0;
text-align: center;
}
</style>
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json">
<link href="./static/out/vs/server/src/media/login.css" rel="stylesheet">
</head>
<body>
<form class="login-form" method="post">

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1,14 +0,0 @@
@supports (-moz-appearance:none) {
/* Firefox doesn't support -webkit-margin-{before/after} so use margin. */
/* These are the collapsible section headings in a sidebar panel. */
.monaco-panel-view .panel > .panel-header h3.title {
margin-bottom: 0;
margin-top: 0;
}
/* Firefox doesn't seem to support fit-content. */
/* These are the file tabs. */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit {
min-width: -moz-fit-content;
}
}

94
src/media/login.css Normal file
View file

@ -0,0 +1,94 @@
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
html, body {
background-color: #FFFFFF;
height: 100%;
min-height: 100%;
}
body {
align-items: center;
display: flex;
font-family: "monospace";
justify-content: center;
margin: 0;
padding: 10px;
}
.login-form {
border-radius: 5px;
box-shadow: 0 18px 80px 10px rgba(69, 65, 78, 0.08);
color: #575962;
margin-top: -10%;
max-width: 328px;
padding: 40px;
position: relative;
width: 100%;
}
.login-form > .title {
text-align: center;
text-transform: uppercase;
font-size: 12px;
font-weight: 500;
letter-spacing: 1.5px;
line-height: 15px;
margin-bottom: 0px;
margin-bottom: 5px;
margin-top: 0px;
}
.login-form > .subtitle {
font-size: 19px;
font-weight: bold;
line-height: 25px;
margin-bottom: 45px;
margin: 0;
text-align: center;
}
.login-form > .field {
text-align: left;
font-size: 12px;
color: #797E84;
margin: 16px 0;
}
.login-form > .field > .input {
background: none !important;
border: 1px solid #ccc;
border-radius: 2px;
padding: 5px;
width: 100%;
}
.login-form > .button {
border: none;
border-radius: 24px;
box-shadow: 0 12px 17px 2px rgba(171,173,163,0.14), 0 5px 22px 4px rgba(171,173,163,0.12), 0 7px 8px -4px rgba(171,173,163,0.2);
cursor: pointer;
display: block;
padding: 15px 5px;
width: 100%;
}
.login-form > .button:hover {
background-color: rgb(0, 122, 204);
color: #fff;
}
.error-display {
box-sizing: border-box;
color: #bb2d0f;
font-size: 14px;
font-weight: 400;
line-height: 12px;
padding: 20px 8px 0;
text-align: center;
}

13
src/media/manifest.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "code-server",
"short_name": "code-server",
"start_url": ".",
"display": "standalone",
"background-color": "#fff",
"description": "Run VS Code on a remote server.",
"icons": [{
"src": "static/code-server.png",
"sizes": "128x128",
"type": "image/png"
}]
}

View file

@ -41,10 +41,9 @@ import { LocalizationsChannel } from "vs/platform/localizations/node/localizatio
import { getLogLevel, ILogService } from "vs/platform/log/common/log";
import { LogLevelSetterChannel } from "vs/platform/log/common/logIpc";
import { SpdLogService } from "vs/platform/log/node/spdlogService";
import { IProductConfiguration, IProductService } from "vs/platform/product/common/product";
import { IProductService } from "vs/platform/product/common/product";
import pkg from "vs/platform/product/node/package";
import product from "vs/platform/product/node/product";
import { ProductService } from "vs/platform/product/node/productService";
import { ConnectionType, ConnectionTypeRequest } from "vs/platform/remote/common/remoteAgentConnection";
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/platform/remote/common/remoteAgentFileSystemChannel";
import { IRequestService } from "vs/platform/request/common/request";
@ -81,8 +80,6 @@ export enum HttpCode {
export interface Options {
WORKBENCH_WEB_CONGIGURATION: IWorkbenchConstructionOptions;
REMOTE_USER_DATA_URI: UriComponents | URI;
PRODUCT_CONFIGURATION: IProductConfiguration | null;
CONNECTION_AUTH_TOKEN: string;
NLS_CONFIGURATION: NLSConfiguration;
}
@ -110,6 +107,7 @@ export class HttpError extends Error {
export interface ServerOptions {
readonly auth?: AuthType;
readonly basePath?: string;
readonly connectionToken?: string;
readonly cert?: string;
readonly certKey?: string;
readonly folderUri?: string;
@ -122,6 +120,8 @@ export interface ServerOptions {
export abstract class Server {
protected readonly server: http.Server | https.Server;
protected rootPath = path.resolve(__dirname, "../../../..");
protected serverRoot = path.join(this.rootPath, "/out/vs/server/src");
protected readonly allowedRequestPaths: string[] = [this.rootPath];
private listenPromise: Promise<string> | undefined;
public readonly protocol: "http" | "https";
public readonly options: ServerOptions;
@ -185,6 +185,9 @@ export abstract class Server {
protected async getResource(...parts: string[]): Promise<Response> {
const filePath = path.join(...parts);
if (!this.isAllowedRequestPath(filePath)) {
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
}
return { content: await util.promisify(fs.readFile)(filePath), filePath };
}
@ -193,10 +196,20 @@ export abstract class Server {
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${split.length === 2 ? `?${split[1]}` : ""}`;
}
private isAllowedRequestPath(path: string): boolean {
for (let i = 0; i < this.allowedRequestPaths.length; ++i) {
if (path.indexOf(this.allowedRequestPaths[i]) === 0) {
return true;
}
}
return false;
}
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
try {
const payload = await this.preHandleRequest(request);
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
// "Cache-Control": "public, max-age=31536000",
"Content-Type": getMediaMime(payload.filePath),
...(payload.redirect ? { Location: this.withBase(request, payload.redirect) } : {}),
...(request.headers["service-worker"] ? { "Service-Worker-Allowed": this.options.basePath || "/" } : {}),
@ -233,25 +246,29 @@ export abstract class Server {
base = path.normalize(base);
requestPath = path.normalize(requestPath || "/index.html");
if (base !== "/login" || !this.options.auth || requestPath !== "/index.html") {
this.ensureGet(request);
}
switch (base) {
case "/":
this.ensureGet(request);
if (requestPath === "/favicon.ico") {
return this.getResource(this.rootPath, "/out/vs/server/src/favicon", requestPath);
} else if (!this.authenticate(request)) {
switch (requestPath) {
case "/favicon.ico":
case "/manifest.json":
return this.getResource(this.serverRoot, "media", requestPath);
}
if (!this.authenticate(request)) {
return { redirect: "/login" };
}
break;
case "/static":
return this.getResource(this.rootPath, requestPath);
case "/login":
if (!this.options.auth) {
if (!this.options.auth || requestPath !== "/index.html") {
throw new HttpError("Not found", HttpCode.NotFound);
} else if (requestPath === "/index.html") {
return this.tryLogin(request);
}
this.ensureGet(request);
return this.getResource(this.rootPath, "/out/vs/server/src/login", requestPath);
return this.tryLogin(request);
default:
this.ensureGet(request);
if (!this.authenticate(request)) {
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
}
@ -274,6 +291,7 @@ export abstract class Server {
socket.on("error", () => socket.destroy());
socket.on("end", () => socket.destroy());
this.ensureGet(request);
if (!this.authenticate(request)) {
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
} else if (request.headers.upgrade !== "websocket") {
@ -297,8 +315,7 @@ export abstract class Server {
}
private async tryLogin(request: http.IncomingMessage): Promise<Response> {
if (this.authenticate(request)) {
this.ensureGet(request);
if (this.authenticate(request) && (request.method === "GET" || request.method === "POST")) {
return { redirect: "/" };
}
if (request.method === "POST") {
@ -322,7 +339,7 @@ export abstract class Server {
}
private async getLogin(error: string = "", payload?: LoginPayload): Promise<Response> {
const filePath = path.join(this.rootPath, "out/vs/server/src/login/index.html");
const filePath = path.join(this.serverRoot, "login/index.html");
const content = (await util.promisify(fs.readFile)(filePath, "utf8"))
.replace("{{ERROR}}", error)
.replace("display:none", error ? "display:block" : "display:none")
@ -446,24 +463,12 @@ export class MainServer extends Server {
): Promise<Response> {
switch (base) {
case "/": return this.getRoot(request, parsedUrl);
case "/resources":
case "/vscode-resources":
if (requestPath === "/fetch") {
if (typeof parsedUrl.query.u === "string") {
return this.getResource(JSON.parse(parsedUrl.query.u).path);
}
// For some reason VS Code encodes the = so the query doesn't parse
// correctly. We'll look through what's available and try to find it.
for (let value in parsedUrl.query) {
if (value && typeof value === "string") {
const query = querystring.parse(value);
if (typeof query.u === "string") {
return this.getResource(JSON.parse(query.u).path);
}
}
}
case "/resource":
case "/vscode-remote-resource":
if (typeof parsedUrl.query.path === "string") {
return this.getResource(parsedUrl.query.path);
}
throw new HttpError("Not found", HttpCode.NotFound);
break;
case "/webview":
if (requestPath.indexOf("/vscode-resource") === 0) {
return this.getResource(requestPath.replace(/^\/vscode-resource/, ""));
@ -473,9 +478,8 @@ export class MainServer extends Server {
"out/vs/workbench/contrib/webview/browser/pre",
requestPath
);
default:
return this.getResource(this.rootPath, base, requestPath);
}
throw new HttpError("Not found", HttpCode.NotFound);
}
private async getRoot(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise<Response> {
@ -502,16 +506,11 @@ export class MainServer extends Server {
? transformer.transformOutgoing(URI.file(sanitizeFilePath(folderPath, cwd)))
: undefined,
remoteAuthority,
productConfiguration: product,
},
REMOTE_USER_DATA_URI: transformer.transformOutgoing(
(this.services.get(IEnvironmentService) as EnvironmentService).webUserDataHome,
),
PRODUCT_CONFIGURATION: {
...product,
// @ts-ignore workaround for getting the VS Code version to the browser.
version: pkg.version,
},
CONNECTION_AUTH_TOKEN: "",
NLS_CONFIGURATION: await getNlsConfiguration(locale, environment.userDataPath),
};
@ -586,6 +585,14 @@ export class MainServer extends Server {
const fileService = new FileService(logService);
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService));
this.allowedRequestPaths.push(
path.join(environmentService.userDataPath, "clp"), // Language packs.
environmentService.extensionsPath,
environmentService.builtinExtensionsPath,
...environmentService.extraExtensionPaths,
...environmentService.extraBuiltinExtensionPaths,
);
this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService));
this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());
@ -595,7 +602,7 @@ export class MainServer extends Server {
this.services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.machineSettingsResource]));
this.services.set(IRequestService, new SyncDescriptor(RequestService));
this.services.set(IFileService, fileService);
this.services.set(IProductService, new SyncDescriptor(ProductService));
this.services.set(IProductService, { _serviceBrand: undefined, ...product });
this.services.set(IDialogService, new DialogChannelClient(this.ipc.getChannel("dialog", router)));
this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
@ -608,14 +615,9 @@ export class MainServer extends Server {
),
commonProperties: resolveCommonProperties(
product.commit, pkg.codeServerVersion, await getMachineId(),
environmentService.installSourcePath, "code-server",
[], environmentService.installSourcePath, "code-server",
),
piiPaths: [
environmentService.appRoot,
environmentService.extensionsPath,
...environmentService.extraExtensionPaths,
...environmentService.extraBuiltinExtensionPaths,
],
piiPaths: this.allowedRequestPaths,
} as ITelemetryServiceConfig]));
} else {
this.services.set(ITelemetryService, NullTelemetryService);
@ -633,7 +635,7 @@ export class MainServer extends Server {
const telemetryService = this.services.get(ITelemetryService) as ITelemetryService;
const extensionsChannel = new ExtensionManagementChannel(extensionsService, (context) => getUriTransformer(context.remoteAuthority));
const extensionsEnvironmentChannel = new ExtensionEnvironmentChannel(environmentService, logService, telemetryService);
const extensionsEnvironmentChannel = new ExtensionEnvironmentChannel(environmentService, logService, telemetryService, this.options.connectionToken || "");
const fileChannel = new FileProviderChannel(environmentService, logService);
const requestChannel = new RequestChannel(this.services.get(IRequestService) as IRequestService);
const telemetryChannel = new TelemetryChannel(telemetryService);