@eclipse-glsp/theia-integration
Version:
Glue code to integrate GLSP clients into Eclipse Theia
262 lines • 12.7 kB
JavaScript
;
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-2026 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(`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(`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