UNPKG

@eclipse-glsp/theia-integration

Version:

Glue code to integrate GLSP clients into Eclipse Theia

266 lines 12.9 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GLSPSocketServerContribution = exports.GLSPSocketServerContributionOptions = exports.START_UP_COMPLETE_MSG = void 0; exports.getPort = getPort; exports.getWebSocketPath = getWebSocketPath; /******************************************************************************** * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the Eclipse * Public License v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is available at * https://www.gnu.org/software/classpath/license.html. * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ const core_1 = require("@theia/core"); const channel_1 = require("@theia/core/lib/common/message-rpc/channel"); const promise_util_1 = require("@theia/core/lib/common/promise-util"); const inversify_1 = require("@theia/core/shared/inversify"); const fs = require("fs"); const net = require("net"); const ws_1 = require("ws"); const common_1 = require("../common"); const glsp_server_contribution_1 = require("./glsp-server-contribution"); const socket_connection_forwarder_1 = require("./socket-connection-forwarder"); const websocket_connection_forwarder_1 = require("./websocket-connection-forwarder"); /** * Message that is expected to be printed by the embedded server process to the stdout once the * server process startup routine has been completed and is ready to accept incoming connections. */ exports.START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed'; var GLSPSocketServerContributionOptions; (function (GLSPSocketServerContributionOptions) { /** Default values for {@link JavaGLSPServerLaunchOptions }**/ function createDefaultOptions() { return { ...glsp_server_contribution_1.GLSPServerContributionOptions.createDefaultOptions(), socketConnectionOptions: { port: NaN, host: '127.0.0.1' } }; } GLSPSocketServerContributionOptions.createDefaultOptions = createDefaultOptions; /** * Utility function to partially set the launch options. Default values (from 'defaultOptions') are used for * options that are not specified. * @param options (partial) launch options that should be extended with default values (if necessary) */ function configure(options) { return { ...createDefaultOptions(), ...options }; } GLSPSocketServerContributionOptions.configure = configure; })(GLSPSocketServerContributionOptions || (exports.GLSPSocketServerContributionOptions = GLSPSocketServerContributionOptions = {})); /** * A reusable base implementation for {@link GLSPServerContribution}s that are using a socket connection to communicate * with a Java or Node based GLSP server. **/ let GLSPSocketServerContribution = class GLSPSocketServerContribution extends glsp_server_contribution_1.BaseGLSPServerContribution { constructor() { super(...arguments); this.onReadyDeferred = new promise_util_1.Deferred(); } initialize() { this.options = GLSPSocketServerContributionOptions.configure(this.createContributionOptions()); } doConnect(clientChannel) { const webSocketAddress = (0, common_1.getWebSocketAddress)(this.options.socketConnectionOptions); if (webSocketAddress) { if (!(0, common_1.isValidWebSocketAddress)(webSocketAddress)) { throw new Error(`Could not connect to server. The given websocket address is not valid: ${webSocketAddress}`); } return this.connectToWebSocketServer(clientChannel, webSocketAddress); } return this.connectToSocketServer(clientChannel); } async launch() { try { if (!this.options.executable) { throw new Error('Could not launch GLSP server. No executable path is provided via the contribution options'); } if (!fs.existsSync(this.options.executable)) { throw new Error(`Could not launch GLSP server. The given server executable path is not valid: ${this.options.executable}`); } if (isNaN(this.options.socketConnectionOptions.port)) { throw new Error(`Could not launch GLSP Server. The given server port is not a number: ${this.options.socketConnectionOptions.port}`); } if (this.options.executable.endsWith('.jar')) { const process = await this.launchJavaProcess(); this.toDispose.push(core_1.Disposable.create(() => process.kill())); } else if (this.options.executable.endsWith('.js')) { const process = await this.launchNodeProcess(); this.toDispose.push(core_1.Disposable.create(() => process.kill())); } else { throw new Error(`Could not launch GLSP Server. Invalid executable path ${this.options.executable}`); } } catch (error) { this.onReadyDeferred.reject(error); } return this.onReadyDeferred.promise; } getPortFromStartupMessage(message) { if (message.includes(exports.START_UP_COMPLETE_MSG)) { const port = message.substring(message.lastIndexOf(':') + 1); return parseInt(port, 10); } return undefined; } launchJavaProcess() { const args = [ ...this.getJavaProcessJvmArgs(), '-jar', this.options.executable, '--port', `${this.options.socketConnectionOptions.port}` ]; if (this.useWebSocket()) { args.push('--websocket'); } if (this.options.socketConnectionOptions.host) { args.push('--host', `${this.options.socketConnectionOptions.host}`); } if (this.options.additionalArgs) { args.push(...this.options.additionalArgs); } return this.spawnProcessAsync('java', args); } getJavaProcessJvmArgs() { return ['--add-opens', 'java.base/java.util=ALL-UNNAMED']; } launchNodeProcess() { const args = [this.options.executable, '--port', `${this.options.socketConnectionOptions.port}`]; if (this.useWebSocket()) { args.push('--webSocket'); } if (this.options.socketConnectionOptions.host) { args.push('--host', `${this.options.socketConnectionOptions.host}`); } if (this.options.additionalArgs) { args.push(...this.options.additionalArgs); } return this.spawnProcessAsync('node', args); } processLogInfo(line) { if (line.startsWith(exports.START_UP_COMPLETE_MSG)) { const port = this.getPortFromStartupMessage(line); if (port) { if (this.options.socketConnectionOptions.port === 0) { this.options.socketConnectionOptions.port = port; } else { if (this.options.socketConnectionOptions.port !== port) { throw new Error( // eslint-disable-next-line max-len `The configured port ${this.options.socketConnectionOptions.port} does not match the port in the startup message: ${line}`); } } } else { throw new Error(`Could not find listening port in startup message of GLSP server: ${line}`); } this.onReadyDeferred.resolve(); } } useWebSocket() { return 'path' in this.options.socketConnectionOptions && this.options.socketConnectionOptions.path !== undefined; } checkServerPort() { if (isNaN(this.options.socketConnectionOptions.port)) { throw new Error( // eslint-disable-next-line max-len `Could not connect to to GLSP Server. The given server port is not a number: ${this.options.socketConnectionOptions.port}`); } } async connectToSocketServer(clientChannel) { const clientDisposable = new core_1.DisposableCollection(); this.checkServerPort(); const socket = new net.Socket(); clientDisposable.push(this.forwardToSocketConnection(clientChannel, socket)); if (clientChannel instanceof channel_1.ForwardingChannel) { socket.on('error', error => clientChannel.onErrorEmitter.fire(error)); } socket.connect(this.options.socketConnectionOptions); clientDisposable.push(core_1.Disposable.create(() => socket.destroy())); return clientDisposable; } forwardToSocketConnection(clientChannel, socket) { return new socket_connection_forwarder_1.SocketConnectionForwarder(clientChannel, socket); } async connectToWebSocketServer(clientChannel, webSocketAddress) { const clientDisposable = new core_1.DisposableCollection(); this.checkServerPort(); if (!webSocketAddress) { throw new Error('Could not connect to to GLSP Server. The WebSocket path cannot be empty'); } const webSocket = new ws_1.WebSocket(webSocketAddress); clientDisposable.push(core_1.Disposable.create(() => webSocket.close())); clientDisposable.push(this.forwardToWebSocketConnection(clientChannel, webSocket)); if (clientChannel instanceof channel_1.ForwardingChannel) { webSocket.onerror = error => clientChannel.onErrorEmitter.fire(error); } return clientDisposable; } forwardToWebSocketConnection(clientChannel, webSocket) { return new websocket_connection_forwarder_1.WebSocketConnectionForwarder(clientChannel, webSocket); } }; exports.GLSPSocketServerContribution = GLSPSocketServerContribution; __decorate([ (0, inversify_1.postConstruct)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], GLSPSocketServerContribution.prototype, "initialize", null); exports.GLSPSocketServerContribution = GLSPSocketServerContribution = __decorate([ (0, inversify_1.injectable)() ], GLSPSocketServerContribution); /** * Utility function to parse a server port that is defined via command line arg. * @param argsKey Name/Key of the commandLine arg * @param defaultPort Default port that should be returned if no (valid) port was passed via CLI# * @returns the path value of the given argument if set, default value or `NaN` instead. */ function getPort(argsKey, defaultPort) { argsKey = `--${argsKey.replace('--', '').replace('=', '')}=`; const args = process.argv.filter(a => a.startsWith(argsKey)); if (args.length > 0) { return Number.parseInt(args[0].substring(argsKey.length), 10); } return defaultPort !== null && defaultPort !== void 0 ? defaultPort : NaN; } /** * Utility function to parse the WebSocket Server path if the GLSP Server should be started in WebSocket mode.# * @param argsKey Name/Key of the commandLine arg * @param defaultPath Default path that should be returned if no path was passed via CLI * @returns the path value of the given argument if set, default value or `undefined` instead. */ function getWebSocketPath(argsKey, defaultPath) { argsKey = `--${argsKey.replace('--', '').replace('=', '')}=`; const args = process.argv.filter(a => a.startsWith(argsKey)); if (args.length > 0) { return args[0].substring(argsKey.length); } return defaultPath; } //# sourceMappingURL=glsp-socket-server-contribution.js.map