Skip to content

Commit 77a5f2b

Browse files
committed
Combine web command with server. Allow JS interop.
1 parent 9e852b7 commit 77a5f2b

File tree

4 files changed

+110
-56
lines changed

4 files changed

+110
-56
lines changed

resources/web/code-web.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as http from 'http';
2+
import { IServerWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
3+
4+
export function requestHandler(req: http.IncomingMessage, res: http.ServerResponse, webConfigJSON?: IServerWorkbenchConstructionOptions): void;

resources/web/code-web.js

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Copyright (c) Microsoft Corporation. All rights reserved.
55
* Licensed under the MIT License. See License.txt in the project root for license information.
66
*--------------------------------------------------------------------------------------------*/
7+
Object.defineProperty(exports, '__esModule', { value: true });
78

89
// @ts-check
910

@@ -43,13 +44,14 @@ const WEB_PLAYGROUND_VERSION = '0.0.12';
4344

4445
const args = minimist(process.argv, {
4546
boolean: [
46-
'no-launch',
47+
'launch',
4748
'help',
4849
'verbose',
4950
'wrap-iframe',
5051
'enable-sync',
5152
],
5253
string: [
54+
'server',
5355
'scheme',
5456
'host',
5557
'remote-authority',
@@ -64,7 +66,7 @@ const args = minimist(process.argv, {
6466
if (args.help) {
6567
console.log(
6668
'yarn web [options]\n' +
67-
' --no-launch Do not open VSCode web in the browser\n' +
69+
' --launch Open VSCode web in the browser\n' +
6870
' --wrap-iframe Wrap the Web Worker Extension Host in an iframe\n' +
6971
' --enable-sync Enable sync by default\n' +
7072
' --scheme Protocol (https or http)\n' +
@@ -90,9 +92,6 @@ const SECONDARY_PORT = args['secondary-port'] || (parseInt(PORT, 10) + 1);
9092
const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http';
9193
const HOST = args.host || 'localhost';
9294
const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`;
93-
const REMOTE_AUTHORITY = args['remote-authority'] || `${HOST}:${(parseInt(PORT, 10) + 2)}`;
94-
// TODO: config
95-
const USER_DATA_PATH = '/Users/teffen/.local/share/code-server';
9695

9796
const exists = (path) => util.promisify(fs.exists)(path);
9897
const readFile = (path) => util.promisify(fs.readFile)(path);
@@ -228,8 +227,10 @@ const mapCallbackUriToRequestId = new Map();
228227
/**
229228
* @param req {http.IncomingMessage}
230229
* @param res {http.ServerResponse}
230+
* @param webConfigJSON {import('../../src/vs/workbench/workbench.web.api').IServerWorkbenchConstructionOptions}
231+
* @param webConfigJSON undefined
231232
*/
232-
const requestHandler = (req, res) => {
233+
const requestHandler = (req, res, webConfigJSON) => {
233234
const parsedUrl = url.parse(req.url, true);
234235
const pathname = parsedUrl.pathname;
235236

@@ -260,6 +261,9 @@ const requestHandler = (req, res) => {
260261
return handleExtension(req, res, parsedUrl);
261262
}
262263
if (pathname === '/') {
264+
if (args.server) {
265+
return handleRootFromServer(req, res, webConfigJSON);
266+
}
263267
// main web
264268
return handleRoot(req, res);
265269
} else if (pathname === '/callback') {
@@ -281,26 +285,28 @@ const requestHandler = (req, res) => {
281285
}
282286
};
283287

284-
const server = http.createServer(requestHandler);
285-
server.listen(LOCAL_PORT, () => {
286-
if (LOCAL_PORT !== PORT) {
287-
console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`);
288-
}
289-
console.log(`Web UI available at ${SCHEME}://${AUTHORITY}`);
290-
});
291-
server.on('error', err => {
292-
console.error(`Error occurred in server:`);
293-
console.error(err);
294-
});
288+
if (!args.server) {
289+
const server = http.createServer(requestHandler);
290+
server.listen(LOCAL_PORT, () => {
291+
if (LOCAL_PORT !== PORT) {
292+
console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`);
293+
}
294+
console.log(`Web UI available at ${SCHEME}://${AUTHORITY}`);
295+
});
296+
server.on('error', err => {
297+
console.error(`Error occurred in server:`);
298+
console.error(err);
299+
});
295300

296-
const secondaryServer = http.createServer(requestHandler);
297-
secondaryServer.listen(SECONDARY_PORT, () => {
298-
console.log(`Secondary server available at ${SCHEME}://${HOST}:${SECONDARY_PORT}`);
299-
});
300-
secondaryServer.on('error', err => {
301-
console.error(`Error occurred in server:`);
302-
console.error(err);
303-
});
301+
const secondaryServer = http.createServer(requestHandler);
302+
secondaryServer.listen(SECONDARY_PORT, () => {
303+
console.log(`Secondary server available at ${SCHEME}://${HOST}:${SECONDARY_PORT}`);
304+
});
305+
secondaryServer.on('error', err => {
306+
console.error(`Error occurred in server:`);
307+
console.error(err);
308+
});
309+
}
304310

305311
/**
306312
* @param {import('http').IncomingMessage} req
@@ -403,12 +409,6 @@ async function handleRoot(req, res) {
403409
}
404410
}
405411

406-
folderUri = {
407-
scheme: 'vscode-remote',
408-
authority: REMOTE_AUTHORITY,
409-
path: '/Users/teffen/Projects/testing/'
410-
};
411-
412412
const { extensions: builtInExtensions } = await builtInExtensionsPromise;
413413
const { extensions: additionalBuiltinExtensions, locations: staticLocations } = await commandlineProvidedExtensionsPromise;
414414

@@ -434,15 +434,9 @@ async function handleRoot(req, res) {
434434
: `${HOST}:${SECONDARY_PORT}`
435435
);
436436

437-
console.log('>>>', REMOTE_AUTHORITY);
438-
439437
const webConfigJSON = {
440438
folderUri: folderUri,
441-
remoteAuthority: REMOTE_AUTHORITY,
442439
additionalBuiltinExtensions,
443-
workspaceProvider: {
444-
payload: [['userDataPath', USER_DATA_PATH]]
445-
},
446440
webWorkerExtensionHostIframeSrc: `${SCHEME}://${secondaryHost}/static/out/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html`
447441
};
448442
if (args['wrap-iframe']) {
@@ -474,6 +468,52 @@ async function handleRoot(req, res) {
474468
return res.end(data);
475469
}
476470

471+
/**
472+
* @param {import('http').IncomingMessage} req
473+
* @param {import('http').ServerResponse} res
474+
* @param webConfigJSON {import('../../src/vs/workbench/workbench.web.api').IServerWorkbenchConstructionOptions}
475+
*/
476+
async function handleRootFromServer(req, res, webConfigJSON) {
477+
const { extensions: builtInExtensions } = await builtInExtensionsPromise;
478+
const { extensions: additionalBuiltinExtensions, locations: staticLocations } = await commandlineProvidedExtensionsPromise;
479+
480+
const dedupedBuiltInExtensions = [];
481+
for (const builtInExtension of builtInExtensions) {
482+
const extensionId = `${builtInExtension.packageJSON.publisher}.${builtInExtension.packageJSON.name}`;
483+
if (staticLocations[extensionId]) {
484+
fancyLog(`${ansiColors.magenta('BuiltIn extensions')}: Ignoring built-in ${extensionId} because it was overridden via --extension argument`);
485+
continue;
486+
}
487+
488+
dedupedBuiltInExtensions.push(builtInExtension);
489+
}
490+
491+
if (args.verbose) {
492+
fancyLog(`${ansiColors.magenta('BuiltIn extensions')}: ${dedupedBuiltInExtensions.map(e => path.basename(e.extensionPath)).join(', ')}`);
493+
fancyLog(`${ansiColors.magenta('Additional extensions')}: ${additionalBuiltinExtensions.map(e => path.basename(e.extensionLocation.path)).join(', ') || 'None'}`);
494+
}
495+
496+
if (req.headers['x-forwarded-host']) {
497+
// support for running in codespace => no iframe wrapping
498+
delete webConfigJSON.webWorkerExtensionHostIframeSrc;
499+
}
500+
const authSessionInfo = undefined;
501+
502+
const data = (await readFile(WEB_MAIN)).toString()
503+
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => escapeAttribute(JSON.stringify(webConfigJSON))) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied
504+
.replace('{{WORKBENCH_BUILTIN_EXTENSIONS}}', () => escapeAttribute(JSON.stringify(dedupedBuiltInExtensions)))
505+
.replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '')
506+
.replace('{{WEBVIEW_ENDPOINT}}', '');
507+
508+
const headers = {
509+
'Content-Type': 'text/html',
510+
'Content-Security-Policy': 'require-trusted-types-for \'script\';'
511+
};
512+
513+
res.writeHead(200, headers);
514+
return res.end(data);
515+
}
516+
477517
/**
478518
* Handle HTTP requests for /callback
479519
* @param {import('http').IncomingMessage} req
@@ -675,3 +715,5 @@ async function serveFile(req, res, filePath, responseHeaders = Object.create(nul
675715
if (args.launch !== false) {
676716
opn(`${SCHEME}://${HOST}:${PORT}`);
677717
}
718+
719+
exports.requestHandler = requestHandler;

src/vs/server/entry.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,34 @@ import { Server as WebSocketServer } from 'ws';
1111
import { CodeServer, VscodeServerArgs as ServerArgs } from 'vs/server/server';
1212
import { createServer, IncomingMessage } from 'http';
1313
import * as net from 'net';
14+
// eslint-disable-next-line code-import-patterns
15+
import { requestHandler as defaultRequestHandler } from '../../../resources/web/code-web';
1416

1517
const logger = new ConsoleMainLogger();
1618

1719
setUnexpectedErrorHandler((error) => {
1820
logger.warn('Uncaught error', error instanceof Error ? error.message : error);
1921
});
22+
2023
enableCustomMarketplace();
2124
proxyAgent.monkeyPatch(true);
2225

2326
type UpgradeHandler = (request: IncomingMessage, socket: net.Socket, upgradeHead: Buffer) => void;
2427

2528
export async function main(args: ServerArgs) {
26-
const httpServer = createServer();
29+
const serverUrl = new URL(`http://${args.server}`);
30+
31+
const codeServer = new CodeServer();
32+
const workbenchConstructionOptions = await codeServer.startup(serverUrl);
33+
34+
const httpServer = createServer((req, res) => defaultRequestHandler(req, res, workbenchConstructionOptions));
2735

28-
const serverUrl = new URL(`ws://${args.server}`);
29-
logger.info('server', serverUrl.toJSON());
3036

3137
const wss = new WebSocketServer({
3238
noServer: true,
3339
perMessageDeflate: true,
3440
});
3541

36-
const codeServer = new CodeServer();
37-
const workbenchConstructionOptions = await codeServer.startup();
38-
39-
4042
logger.info(JSON.stringify(workbenchConstructionOptions.folderUri));
4143
wss.on('error', (error) => logger.error(error.message));
4244

@@ -58,9 +60,9 @@ export async function main(args: ServerArgs) {
5860

5961
httpServer.on('upgrade', upgrade);
6062

61-
httpServer.listen(parseInt(serverUrl.port, 10));
62-
6363
return new Promise((resolve, reject) => {
64-
64+
httpServer.listen(parseInt(serverUrl.port, 10), serverUrl.hostname, () => {
65+
logger.info('Code Server active listening at:', serverUrl.toString());
66+
});
6567
});
6668
}

src/vs/server/server.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { ConsoleLogger, ConsoleMainLogger, getLogLevel, ILogger, ILoggerService,
3434
import { LogLevelChannel } from 'vs/platform/log/common/logIpc';
3535
import { LoggerService } from 'vs/platform/log/node/loggerService';
3636
import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog';
37-
import product from 'vs/platform/product/common/product';
37+
import productConfiguration from 'vs/platform/product/common/product';
3838
import { IProductService } from 'vs/platform/product/common/productService';
3939
import { ConnectionType, ConnectionTypeRequest } from 'vs/platform/remote/common/remoteAgentConnection';
4040
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
@@ -67,7 +67,7 @@ import { toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
6767
import * as WebSocket from 'ws';
6868
import { ServerSocket } from 'vs/platform/remote/node/serverWebSocket';
6969

70-
const commit = product.commit || 'development';
70+
const commit = productConfiguration.commit || 'development';
7171
const logger = new ConsoleMainLogger();
7272

7373
export type VscodeServerArgs = NativeParsedArgs & Complete<Pick<NativeParsedArgs, 'server'>>;
@@ -99,7 +99,7 @@ export class CodeServer extends ArgumentParser {
9999
})));
100100
}
101101

102-
public async startup(): Promise<IServerWorkbenchConstructionOptions> {
102+
public async startup(serverUrl: URL): Promise<IServerWorkbenchConstructionOptions> {
103103
const parsedArgs = this.resolveArgs();
104104

105105
if (!parsedArgs.server) {
@@ -133,9 +133,12 @@ export class CodeServer extends ArgumentParser {
133133
folderUri: workbenchURIs[0].uri,
134134
};
135135

136+
const webEndpointUrl = new URL(serverUrl.toString());
137+
webEndpointUrl.pathname = '/static';
138+
136139
return {
137140
...workspace,
138-
remoteAuthority: parsedArgs.remote,
141+
remoteAuthority: parsedArgs.remote || serverUrl.toJSON(),
139142
logLevel: getLogLevel(environment),
140143
workspaceProvider: {
141144
workspace,
@@ -146,7 +149,10 @@ export class CodeServer extends ArgumentParser {
146149
],
147150
},
148151
remoteUserDataUri: transformer.transformOutgoing(URI.file(environment.userDataPath)),
149-
productConfiguration: product,
152+
productConfiguration: {
153+
...productConfiguration,
154+
webEndpointUrl: webEndpointUrl.toJSON()
155+
},
150156
nlsConfiguration: await getNlsConfiguration(environment.args.locale || await getLocaleFromConfig(environment.userDataPath), environment.userDataPath),
151157
commit,
152158
};
@@ -174,8 +180,8 @@ export class CodeServer extends ArgumentParser {
174180
}
175181

176182
private async connect(message: ConnectionTypeRequest, protocol: ServerProtocol, { reconnectionToken, reconnection }: ServerProtocolOptions): Promise<void> {
177-
if (product.commit && message.commit !== product.commit) {
178-
logger.warn(`Version mismatch (${message.commit} instead of ${product.commit})`);
183+
if (productConfiguration.commit && message.commit !== productConfiguration.commit) {
184+
logger.warn(`Version mismatch (${message.commit} instead of ${productConfiguration.commit})`);
179185
}
180186

181187
switch (message.desiredConnectionType) {
@@ -250,7 +256,7 @@ export class CodeServer extends ArgumentParser {
250256
// ../../electron-browser/sharedProcess/sharedProcessMain.ts#L148
251257
// ../../../code/electron-main/app.ts
252258
private async initializeServices(args: NativeParsedArgs): Promise<void> {
253-
const productService = { _serviceBrand: undefined, ...product };
259+
const productService = { _serviceBrand: undefined, ...productConfiguration };
254260
const environmentService = new NativeEnvironmentService(args, productService);
255261

256262
await Promise.all([
@@ -325,7 +331,7 @@ export class CodeServer extends ArgumentParser {
325331
),
326332
sendErrorTelemetry: true,
327333
commonProperties: resolveCommonProperties(
328-
fileService, release(), hostname(), process.arch, commit, product.version, machineId,
334+
fileService, release(), hostname(), process.arch, commit, productConfiguration.version, machineId,
329335
undefined, environmentService.installSourcePath, 'code-server',
330336
),
331337
piiPaths,

0 commit comments

Comments
 (0)