diff --git a/docs/FAQ.md b/docs/FAQ.md index 779bd83d..9b570ff3 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -2,36 +2,35 @@ # FAQ -- [FAQ](#faq) - - [Questions?](#questions) - - [iPad Status?](#ipad-status) - - [Community Projects (awesome-code-server)](#community-projects-awesome-code-server) - - [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration) - - [Differences compared to VS Code?](#differences-compared-to-vs-code) - - [How can I request a missing extension?](#how-can-i-request-a-missing-extension) - - [How do I configure the marketplace URL?](#how-do-i-configure-the-marketplace-url) - - [Where are extensions stored?](#where-are-extensions-stored) - - [How is this different from VS Code Codespaces?](#how-is-this-different-from-vs-code-codespaces) - - [How should I expose code-server to the internet?](#how-should-i-expose-code-server-to-the-internet) - - [Can I store my password hashed?](#can-i-store-my-password-hashed) - - [How do I securely access web services?](#how-do-i-securely-access-web-services) - - [Sub-paths](#sub-paths) - - [Sub-domains](#sub-domains) - - [Why does the code-server proxy strip `/proxy/` from the request path?](#why-does-the-code-server-proxy-strip-proxyport-from-the-request-path) - - [Proxying to Create React App](#proxying-to-create-react-app) - - [Multi-tenancy](#multi-tenancy) - - [Docker in code-server container?](#docker-in-code-server-container) - - [How can I disable telemetry?](#how-can-i-disable-telemetry) - - [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open) - - [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server) - - [Heartbeat File](#heartbeat-file) - - [Healthz endpoint](#healthz-endpoint) - - [How does the config file work?](#how-does-the-config-file-work) - - [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure) - - [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work) - - [Differences compared to Theia?](#differences-compared-to-theia) - - [`$HTTP_PROXY`, `$HTTPS_PROXY`, `$NO_PROXY`](#http_proxy-https_proxy-no_proxy) - - [Enterprise](#enterprise) +- [Questions?](#questions) +- [iPad Status?](#ipad-status) +- [Community Projects (awesome-code-server)](#community-projects-awesome-code-server) +- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration) +- [Differences compared to VS Code?](#differences-compared-to-vs-code) +- [How can I request a missing extension?](#how-can-i-request-a-missing-extension) +- [How do I configure the marketplace URL?](#how-do-i-configure-the-marketplace-url) +- [Where are extensions stored?](#where-are-extensions-stored) +- [How is this different from VS Code Codespaces?](#how-is-this-different-from-vs-code-codespaces) +- [How should I expose code-server to the internet?](#how-should-i-expose-code-server-to-the-internet) +- [Can I store my password hashed?](#can-i-store-my-password-hashed) +- [How do I securely access web services?](#how-do-i-securely-access-web-services) + - [Sub-paths](#sub-paths) + - [Sub-domains](#sub-domains) +- [Why does the code-server proxy strip `/proxy/` from the request path?](#why-does-the-code-server-proxy-strip-proxyport-from-the-request-path) + - [Proxying to Create React App](#proxying-to-create-react-app) +- [Multi-tenancy](#multi-tenancy) +- [Docker in code-server container?](#docker-in-code-server-container) +- [How can I disable telemetry?](#how-can-i-disable-telemetry) +- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open) +- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server) +- [Heartbeat File](#heartbeat-file) +- [Healthz endpoint](#healthz-endpoint) +- [How does the config file work?](#how-does-the-config-file-work) +- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure) +- [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work) +- [Differences compared to Theia?](#differences-compared-to-theia) +- [`$HTTP_PROXY`, `$HTTPS_PROXY`, `$NO_PROXY`](#http_proxy-https_proxy-no_proxy) +- [Enterprise](#enterprise) diff --git a/lib/vscode/src/vs/server/node/channel.ts b/lib/vscode/src/vs/server/node/channel.ts index 639f077c..347bccc4 100644 --- a/lib/vscode/src/vs/server/node/channel.ts +++ b/lib/vscode/src/vs/server/node/channel.ts @@ -330,11 +330,7 @@ export class ExtensionEnvironmentChannel implements IServerChannel { } } -/* - NOTE@coder: - Reference: - ../../workbench/api/common/extHostDebugService.ts - 3/16/21 jsjoeio -*/ +// Reference: - ../../workbench/api/common/extHostDebugService.ts class VariableResolverService extends AbstractVariableResolverService { constructor( remoteAuthority: string, @@ -356,10 +352,6 @@ class VariableResolverService extends AbstractVariableResolverService { return args.resolvedVariables[`config:${section}`]; }, getAppRoot: (): string | undefined => { - /* - NOTE@coder: not sure where we could get this from. This is new. - @jsjoeio 3/11/21 - */ return (args.resolverEnv && args.resolverEnv['VSCODE_CWD']) || env['VSCODE_CWD'] || process.cwd(); }, getExecPath: (): string | undefined => { diff --git a/lib/vscode/src/vs/server/node/server.ts b/lib/vscode/src/vs/server/node/server.ts index d2a508af..d79c4ff0 100644 --- a/lib/vscode/src/vs/server/node/server.ts +++ b/lib/vscode/src/vs/server/node/server.ts @@ -1,108 +1,99 @@ -import { field } from "@coder/logger" -import { release } from "os" -import * as fs from "fs" -import * as net from "net" -import * as path from "path" -import { Emitter } from "vs/base/common/event" -import { Schemas } from "vs/base/common/network" -import { URI } from "vs/base/common/uri" -import { getMachineId } from "vs/base/node/id" -import { ClientConnectionEvent, IPCServer, IServerChannel, ProxyChannel } from "vs/base/parts/ipc/common/ipc" -import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner" -import { main } from "vs/code/node/cliProcessMain" -import { IConfigurationService } from "vs/platform/configuration/common/configuration" -import { ConfigurationService } from "vs/platform/configuration/common/configurationService" -import { ExtensionHostDebugBroadcastChannel } from "vs/platform/debug/common/extensionHostDebugIpc" -import { NativeParsedArgs } from "vs/platform/environment/common/argv" -import { IEnvironmentService, INativeEnvironmentService } from "vs/platform/environment/common/environment" -import { NativeEnvironmentService } from "vs/platform/environment/node/environmentService" -import { ExtensionGalleryService } from "vs/platform/extensionManagement/common/extensionGalleryService" -import { - IExtensionGalleryService, - IExtensionManagementService, -} from "vs/platform/extensionManagement/common/extensionManagement" -import { ExtensionManagementChannel } from "vs/platform/extensionManagement/common/extensionManagementIpc" -import { ExtensionManagementService } from "vs/platform/extensionManagement/node/extensionManagementService" -import { IFileService } from "vs/platform/files/common/files" -import { FileService } from "vs/platform/files/common/fileService" -import { DiskFileSystemProvider } from "vs/platform/files/node/diskFileSystemProvider" -import { SyncDescriptor } from "vs/platform/instantiation/common/descriptors" -import { InstantiationService } from "vs/platform/instantiation/common/instantiationService" -import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection" -import { ILocalizationsService } from "vs/platform/localizations/common/localizations" -import { LocalizationsService } from "vs/platform/localizations/node/localizations" -import { - ConsoleLogger, - getLogLevel, - ILoggerService, - ILogService, - MultiplexLogService, -} from "vs/platform/log/common/log" -import { LogLevelChannel } from "vs/platform/log/common/logIpc" -import { LoggerService } from "vs/platform/log/node/loggerService" -import { SpdLogLogger } from "vs/platform/log/node/spdlogLog" -import product from "vs/platform/product/common/product" -import { IProductService } from "vs/platform/product/common/productService" -import { ConnectionType, ConnectionTypeRequest } from "vs/platform/remote/common/remoteAgentConnection" -import { RemoteAgentConnectionContext } from "vs/platform/remote/common/remoteAgentEnvironment" -import { IRequestService } from "vs/platform/request/common/request" -import { RequestChannel } from "vs/platform/request/common/requestIpc" -import { RequestService } from "vs/platform/request/node/requestService" -import ErrorTelemetry from "vs/platform/telemetry/browser/errorTelemetry" -import { ITelemetryService } from "vs/platform/telemetry/common/telemetry" -import { TelemetryLogAppender } from "vs/platform/telemetry/common/telemetryLogAppender" -import { TelemetryService } from "vs/platform/telemetry/common/telemetryService" -import { combinedAppender, NullTelemetryService } from "vs/platform/telemetry/common/telemetryUtils" -import { AppInsightsAppender } from "vs/platform/telemetry/node/appInsightsAppender" -import { resolveCommonProperties } from "vs/platform/telemetry/common/commonProperties" -import { TelemetryChannel } from "vs/server/common/telemetry" -import { Query, VscodeOptions, WorkbenchOptions } from "vs/server/ipc" -import { ExtensionEnvironmentChannel, FileProviderChannel, TerminalProviderChannel } from "vs/server/node/channel" -import { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/node/connection" -import { TelemetryClient } from "vs/server/node/insights" -import { logger } from "vs/server/node/logger" -import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/node/nls" -import { Protocol } from "vs/server/node/protocol" -import { getUriTransformer } from "vs/server/node/util" -import { REMOTE_TERMINAL_CHANNEL_NAME } from "vs/workbench/contrib/terminal/common/remoteTerminalChannel" -import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/workbench/services/remote/common/remoteAgentFileSystemChannel" -import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService" +import { field } from '@coder/logger'; +import { release } from 'os'; +import * as fs from 'fs'; +import * as net from 'net'; +import * as path from 'path'; +import { Emitter } from 'vs/base/common/event'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { getMachineId } from 'vs/base/node/id'; +import { ClientConnectionEvent, IPCServer, IServerChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; +import { main } from 'vs/code/node/cliProcessMain'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; +import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ConsoleLogger, getLogLevel, ILoggerService, ILogService, MultiplexLogService } from 'vs/platform/log/common/log'; +import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; +import { LoggerService } from 'vs/platform/log/node/loggerService'; +import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ConnectionType, ConnectionTypeRequest } from 'vs/platform/remote/common/remoteAgentConnection'; +import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { RequestChannel } from 'vs/platform/request/common/requestIpc'; +import { RequestService } from 'vs/platform/request/node/requestService'; +import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; +import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; +import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; +import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; +import { TelemetryChannel } from 'vs/server/common/telemetry'; +import { Query, VscodeOptions, WorkbenchOptions } from 'vs/server/ipc'; +import { ExtensionEnvironmentChannel, FileProviderChannel, TerminalProviderChannel } from 'vs/server/node/channel'; +import { Connection, ExtensionHostConnection, ManagementConnection } from 'vs/server/node/connection'; +import { TelemetryClient } from 'vs/server/node/insights'; +import { logger } from 'vs/server/node/logger'; +import { getLocaleFromConfig, getNlsConfiguration } from 'vs/server/node/nls'; +import { Protocol } from 'vs/server/node/protocol'; +import { getUriTransformer } from 'vs/server/node/util'; +import { REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; +import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel'; +import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; export class Vscode { - public readonly _onDidClientConnect = new Emitter() - public readonly onDidClientConnect = this._onDidClientConnect.event - private readonly ipc = new IPCServer(this.onDidClientConnect) + public readonly _onDidClientConnect = new Emitter(); + public readonly onDidClientConnect = this._onDidClientConnect.event; + private readonly ipc = new IPCServer(this.onDidClientConnect); - private readonly maxExtraOfflineConnections = 0 - private readonly connections = new Map>() + private readonly maxExtraOfflineConnections = 0; + private readonly connections = new Map>(); - private readonly services = new ServiceCollection() - private servicesPromise?: Promise + private readonly services = new ServiceCollection(); + private servicesPromise?: Promise; public async cli(args: NativeParsedArgs): Promise { - return main(args) + return main(args); } public async initialize(options: VscodeOptions): Promise { - const transformer = getUriTransformer(options.remoteAuthority) + const transformer = getUriTransformer(options.remoteAuthority); if (!this.servicesPromise) { - this.servicesPromise = this.initializeServices(options.args) + this.servicesPromise = this.initializeServices(options.args); } - await this.servicesPromise - const environment = this.services.get(IEnvironmentService) as INativeEnvironmentService - const startPath = options.startPath + await this.servicesPromise; + const environment = this.services.get(IEnvironmentService) as INativeEnvironmentService; + const startPath = options.startPath; const parseUrl = (url: string): URI => { // This might be a fully-specified URL or just a path. try { - return URI.parse(url, true) + return URI.parse(url, true); } catch (error) { return URI.from({ scheme: Schemas.vscodeRemote, authority: options.remoteAuthority, path: url, - }) + }); } - } + }; return { workbenchWebConfiguration: { workspaceUri: startPath && startPath.workspace ? parseUrl(startPath.url) : undefined, @@ -111,112 +102,106 @@ export class Vscode { logLevel: getLogLevel(environment), workspaceProvider: { payload: [ - ["userDataPath", environment.userDataPath], - ["enableProposedApi", JSON.stringify(options.args["enable-proposed-api"] || [])], + ['userDataPath', environment.userDataPath], + ['enableProposedApi', JSON.stringify(options.args['enable-proposed-api'] || [])] ], }, }, remoteUserDataUri: transformer.transformOutgoing(URI.file(environment.userDataPath)), productConfiguration: product, - nlsConfiguration: await getNlsConfiguration( - environment.args.locale || (await getLocaleFromConfig(environment.userDataPath)), - environment.userDataPath, - ), - commit: product.commit || "development", - } + nlsConfiguration: await getNlsConfiguration(environment.args.locale || await getLocaleFromConfig(environment.userDataPath), environment.userDataPath), + commit: product.commit || 'development', + }; } public async handleWebSocket(socket: net.Socket, query: Query, permessageDeflate: boolean): Promise { if (!query.reconnectionToken) { - throw new Error("Reconnection token is missing from query parameters") + throw new Error('Reconnection token is missing from query parameters'); } const protocol = new Protocol(socket, { reconnectionToken: query.reconnectionToken, - reconnection: query.reconnection === "true", - skipWebSocketFrames: query.skipWebSocketFrames === "true", + reconnection: query.reconnection === 'true', + skipWebSocketFrames: query.skipWebSocketFrames === 'true', permessageDeflate, recordInflateBytes: permessageDeflate, - }) + }); try { - await this.connect(await protocol.handshake(), protocol) + await this.connect(await protocol.handshake(), protocol); } catch (error) { - protocol.sendMessage({ type: "error", reason: error.message }) - protocol.dispose() - protocol.getSocket().dispose() + protocol.sendMessage({ type: 'error', reason: error.message }); + protocol.dispose(); + protocol.getSocket().dispose(); } - return true + return true; } private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise { if (product.commit && message.commit !== product.commit) { - logger.warn(`Version mismatch (${message.commit} instead of ${product.commit})`) + logger.warn(`Version mismatch (${message.commit} instead of ${product.commit})`); } switch (message.desiredConnectionType) { case ConnectionType.ExtensionHost: case ConnectionType.Management: if (!this.connections.has(message.desiredConnectionType)) { - this.connections.set(message.desiredConnectionType, new Map()) + this.connections.set(message.desiredConnectionType, new Map()); } - const connections = this.connections.get(message.desiredConnectionType)! + const connections = this.connections.get(message.desiredConnectionType)!; const ok = async () => { return message.desiredConnectionType === ConnectionType.ExtensionHost ? { debugPort: await this.getDebugPort() } - : { type: "ok" } - } + : { type: 'ok' }; + }; - const token = protocol.options.reconnectionToken + const token = protocol.options.reconnectionToken; if (protocol.options.reconnection && connections.has(token)) { - protocol.sendMessage(await ok()) - const buffer = protocol.readEntireBuffer() - protocol.dispose() - return connections.get(token)!.reconnect(protocol.getSocket(), buffer) + protocol.sendMessage(await ok()); + const buffer = protocol.readEntireBuffer(); + protocol.dispose(); + return connections.get(token)!.reconnect(protocol.getSocket(), buffer); } else if (protocol.options.reconnection || connections.has(token)) { - throw new Error( - protocol.options.reconnection ? "Unrecognized reconnection token" : "Duplicate reconnection token", - ) + throw new Error(protocol.options.reconnection + ? 'Unrecognized reconnection token' + : 'Duplicate reconnection token' + ); } - logger.debug("New connection", field("token", token)) - protocol.sendMessage(await ok()) + logger.debug('New connection', field('token', token)); + protocol.sendMessage(await ok()); - let connection: Connection + let connection: Connection; if (message.desiredConnectionType === ConnectionType.Management) { - connection = new ManagementConnection(protocol, token) + connection = new ManagementConnection(protocol, token); this._onDidClientConnect.fire({ - protocol, - onDidClientDisconnect: connection.onClose, - }) + protocol, onDidClientDisconnect: connection.onClose, + }); } else { - const buffer = protocol.readEntireBuffer() + const buffer = protocol.readEntireBuffer(); connection = new ExtensionHostConnection( - message.args ? message.args.language : "en", - protocol, - buffer, - token, + message.args ? message.args.language : 'en', + protocol, buffer, token, this.services.get(IEnvironmentService) as INativeEnvironmentService, - ) + ); } - connections.set(token, connection) + connections.set(token, connection); connection.onClose(() => { - logger.debug("Connection closed", field("token", token)) - connections.delete(token) - }) - this.disposeOldOfflineConnections(connections) - break - case ConnectionType.Tunnel: - return protocol.tunnel() - default: - throw new Error("Unrecognized connection type") + logger.debug('Connection closed', field('token', token)); + connections.delete(token); + }); + this.disposeOldOfflineConnections(connections); + break; + case ConnectionType.Tunnel: return protocol.tunnel(); + default: throw new Error('Unrecognized connection type'); } } private disposeOldOfflineConnections(connections: Map): void { - const offline = Array.from(connections.values()).filter((connection) => typeof connection.offline !== "undefined") + const offline = Array.from(connections.values()) + .filter((connection) => typeof connection.offline !== 'undefined'); for (let i = 0, max = offline.length - this.maxExtraOfflineConnections; i < max; ++i) { - logger.debug("Disposing offline connection", field("token", offline[i].token)) - offline[i].dispose() + logger.debug('Disposing offline connection', field('token', offline[i].token)); + offline[i].dispose(); } } @@ -228,9 +213,9 @@ export class Vscode { If upstream changes cause conflicts, look there ^. 3/11/21 @jsjoeio */ - const environmentService = new NativeEnvironmentService(args) + const environmentService = new NativeEnvironmentService(args); // https://github.com/cdr/code-server/issues/1693 - fs.mkdirSync(environmentService.globalStorageHome.fsPath, { recursive: true }) + fs.mkdirSync(environmentService.globalStorageHome.fsPath, { recursive: true }); /* NOTE@coder: Made these updates on based on this file (and lines): Reference: - ../../electron-browser/sharedProcess/sharedProcessMain.ts#L144-L149 @@ -243,133 +228,91 @@ export class Vscode { */ const logService = new MultiplexLogService([ new ConsoleLogger(getLogLevel(environmentService)), - new SpdLogLogger( - RemoteExtensionLogFileName, - path.join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), - false, - getLogLevel(environmentService), - ), - ]) - const fileService = new FileService(logService) - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService)) + new SpdLogLogger(RemoteExtensionLogFileName, path.join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), false, getLogLevel(environmentService)) + ]); + const fileService = new FileService(logService); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService)); - const loggerService = new LoggerService(logService, fileService) + const loggerService = new LoggerService(logService, fileService); const piiPaths = [ - path.join(environmentService.userDataPath, "clp"), // Language packs. + path.join(environmentService.userDataPath, 'clp'), // Language packs. environmentService.appRoot, environmentService.extensionsPath, environmentService.builtinExtensionsPath, ...environmentService.extraExtensionPaths, ...environmentService.extraBuiltinExtensionPaths, - ] + ]; - /* - NOTE@coder: we changed this channel registration from LogLevel to LogLevelChannel - because it changed upstream. + this.ipc.registerChannel('logger', new LogLevelChannel(logService)); + this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()); - 3/15/21 jsjoeio - */ - this.ipc.registerChannel("logger", new LogLevelChannel(logService)) - this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()) + this.services.set(ILogService, logService); + this.services.set(IEnvironmentService, environmentService); + this.services.set(INativeEnvironmentService, environmentService); + this.services.set(ILoggerService, loggerService); - this.services.set(ILogService, logService) - this.services.set(IEnvironmentService, environmentService) - this.services.set(INativeEnvironmentService, environmentService) - /* - NOTE@coder: we changed this from LoggerService to the loggerService defined above. - 3/11/21 @jsjoeio - */ - this.services.set(ILoggerService, loggerService) + const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); + await configurationService.initialize(); + this.services.set(IConfigurationService, configurationService); - const configurationService = new ConfigurationService(environmentService.settingsResource, fileService) - await configurationService.initialize() - this.services.set(IConfigurationService, configurationService) + this.services.set(IRequestService, new SyncDescriptor(RequestService)); + this.services.set(IFileService, fileService); + this.services.set(IProductService, { _serviceBrand: undefined, ...product }); - this.services.set(IRequestService, new SyncDescriptor(RequestService)) - this.services.set(IFileService, fileService) - this.services.set(IProductService, { _serviceBrand: undefined, ...product }) - - const machineId = await getMachineId() + const machineId = await getMachineId(); await new Promise((resolve) => { - const instantiationService = new InstantiationService(this.services) + const instantiationService = new InstantiationService(this.services); instantiationService.invokeFunction((accessor) => { - instantiationService.createInstance(LogsDataCleaner) + instantiationService.createInstance(LogsDataCleaner); - let telemetryService: ITelemetryService + let telemetryService: ITelemetryService; if (!environmentService.disableTelemetry) { - telemetryService = new TelemetryService( - { - appender: combinedAppender( - new AppInsightsAppender("code-server", null, () => new TelemetryClient() as any), - new TelemetryLogAppender(accessor.get(ILoggerService), environmentService), - ), - sendErrorTelemetry: true, - commonProperties: resolveCommonProperties( - fileService, - release(), - process.arch, - product.commit, - product.version, - machineId, - [], - environmentService.installSourcePath, - "code-server", - ), - piiPaths, - }, - configurationService, - ) + telemetryService = new TelemetryService({ + appender: combinedAppender( + new AppInsightsAppender('code-server', null, () => new TelemetryClient() as any), + new TelemetryLogAppender(accessor.get(ILoggerService), environmentService) + ), + sendErrorTelemetry: true, + commonProperties: resolveCommonProperties( + fileService, release(), process.arch, product.commit, product.version, machineId, + [], environmentService.installSourcePath, 'code-server', + ), + piiPaths, + }, configurationService); } else { - telemetryService = NullTelemetryService + telemetryService = NullTelemetryService; } - this.services.set(ITelemetryService, telemetryService) + this.services.set(ITelemetryService, telemetryService); - this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)) - this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)) - this.services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)) + this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); + this.services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); - this.ipc.registerChannel( - "extensions", - new ExtensionManagementChannel(accessor.get(IExtensionManagementService), (context) => - getUriTransformer(context.remoteAuthority), - ), - ) - this.ipc.registerChannel( - "remoteextensionsenvironment", - new ExtensionEnvironmentChannel(environmentService, logService, telemetryService, ""), - ) - this.ipc.registerChannel("request", new RequestChannel(accessor.get(IRequestService))) - this.ipc.registerChannel("telemetry", new TelemetryChannel(telemetryService)) - /* - NOTE@coder: they renamed createChannelReceiver and made it part of the ProxyChannel namespace - See: https://github.com/microsoft/vscode/commit/e371faebfb679ca0dcdb61f4f2f33b3d69922a77 - - And see this as an example similar to our code below: - https://github.com/microsoft/vscode/blob/e371faebfb679ca0dcdb61f4f2f33b3d69922a77/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts#L273 - 3/11/2021 by @jsjoeio - */ - this.ipc.registerChannel( - "localizations", - >ProxyChannel.fromService(accessor.get(ILocalizationsService)), - ) - this.ipc.registerChannel( - REMOTE_FILE_SYSTEM_CHANNEL_NAME, - new FileProviderChannel(environmentService, logService), - ) - this.ipc.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new TerminalProviderChannel(logService)) - resolve(new ErrorTelemetry(telemetryService)) - }) - }) + this.ipc.registerChannel('extensions', new ExtensionManagementChannel( + accessor.get(IExtensionManagementService), + (context) => getUriTransformer(context.remoteAuthority), + )); + this.ipc.registerChannel('remoteextensionsenvironment', new ExtensionEnvironmentChannel( + environmentService, logService, telemetryService, '', + )); + this.ipc.registerChannel('request', new RequestChannel(accessor.get(IRequestService))); + this.ipc.registerChannel('telemetry', new TelemetryChannel(telemetryService)); + this.ipc.registerChannel('localizations', >ProxyChannel.fromService(accessor.get(ILocalizationsService))); + this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService)); + this.ipc.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new TerminalProviderChannel(logService)); + resolve(new ErrorTelemetry(telemetryService)); + }); + }); } /** * TODO: implement. */ private async getDebugPort(): Promise { - return undefined + return undefined; } } diff --git a/test/e2e/openHelpAbout.test.ts b/test/e2e/openHelpAbout.test.ts new file mode 100644 index 00000000..2a6ac57b --- /dev/null +++ b/test/e2e/openHelpAbout.test.ts @@ -0,0 +1,91 @@ +import { chromium, Page, Browser, BrowserContext, Cookie } from "playwright" +import { hash } from "../../src/node/util" +import { CODE_SERVER_ADDRESS, PASSWORD, STORAGE, E2E_VIDEO_DIR } from "../utils/constants" +import { createCookieIfDoesntExist } from "../utils/helpers" + +describe("Open Help > About", () => { + let browser: Browser + let page: Page + let context: BrowserContext + + beforeAll(async () => { + browser = await chromium.launch() + // Create a new context with the saved storage state + const storageState = JSON.parse(STORAGE) || {} + + const cookieToStore = { + sameSite: "Lax" as const, + name: "key", + value: hash(PASSWORD), + domain: "localhost", + path: "/", + expires: -1, + httpOnly: false, + secure: false, + } + + // For some odd reason, the login method used in globalSetup.ts doesn't always work + // I don't know if it's on playwright clearing our cookies by accident + // or if it's our cookies disappearing. + // This means we need an additional check to make sure we're logged in. + // We do this by manually adding the cookie to the browser environment + // if it's not there at the time the test starts + const cookies: Cookie[] = storageState.cookies || [] + // If the cookie exists in cookies then + // this will return the cookies with no changes + // otherwise if it doesn't exist, it will create it + // hence the name maybeUpdatedCookies + // + // TODO(@jsjoeio) + // The playwright storage thing sometimes works and sometimes doesn't. We should investigate this further + // at some point. + // See discussion: https://github.com/cdr/code-server/pull/2648#discussion_r575434946 + + const maybeUpdatedCookies = createCookieIfDoesntExist(cookies, cookieToStore) + + context = await browser.newContext({ + storageState: { cookies: maybeUpdatedCookies }, + recordVideo: { dir: E2E_VIDEO_DIR }, + }) + }) + + afterAll(async () => { + // Remove password from local storage + await context.clearCookies() + + await context.close() + await browser.close() + }) + + beforeEach(async () => { + page = await context.newPage() + }) + + it("should see a 'Help' then 'About' button in the Application Menu that opens a dialog", async () => { + // waitUntil: "domcontentloaded" + // In case the page takes a long time to load + await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "domcontentloaded" }) + + // Make sure the editor actually loaded + expect(await page.isVisible("div.monaco-workbench")) + + // Click the Application menu + await page.click("[aria-label='Application Menu']") + // See the Help button + const helpButton = "a.action-menu-item span[aria-label='Help']" + expect(await page.isVisible(helpButton)) + + // Hover the helpButton + await page.hover(helpButton) + + // see the About button and click it + const aboutButton = "a.action-menu-item span[aria-label='About']" + expect(await page.isVisible(aboutButton)) + // NOTE: it won't work unless you hover it first + await page.hover(aboutButton) + await page.click(aboutButton) + + const codeServerText = "text=code-server" + expect(await page.isVisible(codeServerText)) + }) +}) diff --git a/test/jest.e2e.config.ts b/test/jest.e2e.config.ts index 01f9b607..9c678acb 100644 --- a/test/jest.e2e.config.ts +++ b/test/jest.e2e.config.ts @@ -7,7 +7,7 @@ const config: Config.InitialOptions = { }, globalSetup: "/utils/globalSetup.ts", testEnvironment: "node", - testPathIgnorePatterns: ["node_modules", "lib", "out", "test/unit"], + testPathIgnorePatterns: ["/node_modules/", "/lib/", "/out/", "test/unit"], testTimeout: 30000, modulePathIgnorePatterns: [ "/../lib/vscode", diff --git a/test/package.json b/test/package.json index 4a408464..dfd9f9f1 100644 --- a/test/package.json +++ b/test/package.json @@ -1,4 +1,5 @@ { + "license": "MIT", "#": "We must put jest in a sub-directory otherwise VS Code somehow picks up the types and generates conflicts with mocha.", "devDependencies": { "@types/jest": "^26.0.20",