UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

177 lines • 8.83 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2023 STMicroelectronics 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-only WITH Classpath-exception-2.0 Object.defineProperty(exports, "__esModule", { value: true }); exports.WebsocketFrontendConnectionService = void 0; const tslib_1 = require("tslib"); const inversify_1 = require("inversify"); const default_messaging_service_1 = require("./default-messaging-service"); const socket_write_buffer_1 = require("../../common/messaging/socket-write-buffer"); const channel_1 = require("../../common/message-rpc/channel"); const uint8_array_message_buffer_1 = require("../../common/message-rpc/uint8-array-message-buffer"); const backend_application_config_provider_1 = require("../backend-application-config-provider"); const websocket_endpoint_1 = require("./websocket-endpoint"); const connection_management_1 = require("../../common/messaging/connection-management"); const common_1 = require("../../common"); let WebsocketFrontendConnectionService = class WebsocketFrontendConnectionService { constructor() { this.wsHandlers = new default_messaging_service_1.ConnectionHandlers(); this.connectionsByFrontend = new Map(); this.closeTimeouts = new Map(); this.channelsMarkedForClose = new Set(); } registerConnectionHandler(spec, callback) { this.websocketServer.registerConnectionHandler(spec, (params, socket) => this.handleConnection(socket, channel => callback(params, channel))); } async handleConnection(socket, channelCreatedHandler) { // eslint-disable-next-line prefer-const let reconnectListener; const initialConnectListener = (frontEndId) => { socket.off(connection_management_1.ConnectionManagementMessages.INITIAL_CONNECT, initialConnectListener); socket.off(connection_management_1.ConnectionManagementMessages.RECONNECT, reconnectListener); if (this.connectionsByFrontend.has(frontEndId)) { this.closeConnection(frontEndId, 'reconnecting same front end'); } const channel = this.createConnection(socket, frontEndId); this.handleSocketDisconnect(socket, channel, frontEndId); channelCreatedHandler(channel); socket.emit(connection_management_1.ConnectionManagementMessages.INITIAL_CONNECT); }; reconnectListener = (frontEndId) => { socket.off(connection_management_1.ConnectionManagementMessages.INITIAL_CONNECT, initialConnectListener); socket.off(connection_management_1.ConnectionManagementMessages.RECONNECT, reconnectListener); const channel = this.connectionsByFrontend.get(frontEndId); if (channel) { console.info(`Reconnecting to front end ${frontEndId}`); socket.emit(connection_management_1.ConnectionManagementMessages.RECONNECT, true); channel.connect(socket); this.handleSocketDisconnect(socket, channel, frontEndId); const pendingTimeout = this.closeTimeouts.get(frontEndId); clearTimeout(pendingTimeout); this.closeTimeouts.delete(frontEndId); } else { console.info(`Reconnecting failed for ${frontEndId}`); socket.emit(connection_management_1.ConnectionManagementMessages.RECONNECT, false); } }; socket.on(connection_management_1.ConnectionManagementMessages.INITIAL_CONNECT, initialConnectListener); socket.on(connection_management_1.ConnectionManagementMessages.RECONNECT, reconnectListener); } closeConnection(frontEndId, reason) { console.info(`closing connection for ${frontEndId}`); const connection = this.connectionsByFrontend.get(frontEndId); // not called when no connection is present this.connectionsByFrontend.delete(frontEndId); const pendingTimeout = this.closeTimeouts.get(frontEndId); clearTimeout(pendingTimeout); this.closeTimeouts.delete(frontEndId); connection.onCloseEmitter.fire({ reason }); connection.close(); } createConnection(socket, frontEndId) { console.info(`creating connection for ${frontEndId}`); const channel = new ReconnectableSocketChannel(); channel.connect(socket); this.connectionsByFrontend.set(frontEndId, channel); return channel; } handleSocketDisconnect(socket, channel, frontEndId) { socket.on('disconnect', evt => { console.info('socket closed'); channel.disconnect(); const timeout = this.frontendConnectionTimeout(); const isMarkedForClose = this.channelsMarkedForClose.delete(frontEndId); if (timeout === 0 || isMarkedForClose) { this.closeConnection(frontEndId, evt); } else if (timeout > 0) { console.info(`setting close timeout for id ${frontEndId} to ${timeout}`); const handle = setTimeout(() => { this.closeConnection(frontEndId, evt); }, timeout); this.closeTimeouts.set(frontEndId, handle); } else { // timeout < 0: never close the back end } }); } markForClose(channelId) { this.channelsMarkedForClose.add(channelId); } frontendConnectionTimeout() { const envValue = Number(process.env['FRONTEND_CONNECTION_TIMEOUT']); if (!isNaN(envValue)) { return envValue; } return backend_application_config_provider_1.BackendApplicationConfigProvider.get().frontendConnectionTimeout; } }; exports.WebsocketFrontendConnectionService = WebsocketFrontendConnectionService; tslib_1.__decorate([ (0, inversify_1.inject)(websocket_endpoint_1.WebsocketEndpoint), tslib_1.__metadata("design:type", websocket_endpoint_1.WebsocketEndpoint) ], WebsocketFrontendConnectionService.prototype, "websocketServer", void 0); exports.WebsocketFrontendConnectionService = WebsocketFrontendConnectionService = tslib_1.__decorate([ (0, inversify_1.injectable)() ], WebsocketFrontendConnectionService); class ReconnectableSocketChannel extends channel_1.AbstractChannel { constructor() { super(...arguments); this.socketBuffer = new socket_write_buffer_1.SocketWriteBuffer(); this.disposables = new common_1.DisposableCollection(); } connect(socket) { this.disposables = new common_1.DisposableCollection(); this.socket = socket; const errorHandler = (err) => { this.onErrorEmitter.fire(err); }; this.disposables.push(common_1.Disposable.create(() => { socket.off('error', errorHandler); })); socket.on('error', errorHandler); // eslint-disable-next-line @typescript-eslint/no-explicit-any const dataListener = (data) => { // In the browser context socketIO receives binary messages as ArrayBuffers. // So we have to convert them to a Uint8Array before delegating the message to the read buffer. const buffer = data instanceof ArrayBuffer ? new Uint8Array(data) : data; this.onMessageEmitter.fire(() => new uint8_array_message_buffer_1.Uint8ArrayReadBuffer(buffer)); }; this.disposables.push(common_1.Disposable.create(() => { socket.off('message', dataListener); })); socket.on('message', dataListener); this.socketBuffer.flush(socket); } disconnect() { this.disposables.dispose(); this.socket = undefined; } getWriteBuffer() { const writeBuffer = new uint8_array_message_buffer_1.Uint8ArrayWriteBuffer(); writeBuffer.onCommit(data => { var _a; if ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connected) { this.socket.send(data); } else { this.socketBuffer.buffer(data); } }); return writeBuffer; } } //# sourceMappingURL=websocket-frontend-connection-service.js.map