@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
225 lines • 8.44 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2021 Red Hat, Inc. 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
// *****************************************************************************
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChannelMultiplexer = exports.MessageTypes = exports.ForwardingChannel = exports.BasicChannel = exports.AbstractChannel = void 0;
const disposable_1 = require("../disposable");
const event_1 = require("../event");
;
/**
* Reusable abstract {@link Channel} implementation that sets up
* the basic channel event listeners and offers a generic close method.
*/
class AbstractChannel {
constructor() {
this.onCloseEmitter = new event_1.Emitter();
this.onErrorEmitter = new event_1.Emitter();
this.onMessageEmitter = new event_1.Emitter();
this.toDispose = new disposable_1.DisposableCollection();
this.toDispose.pushAll([this.onCloseEmitter, this.onErrorEmitter, this.onMessageEmitter]);
}
get onClose() {
return this.onCloseEmitter.event;
}
;
get onError() {
return this.onErrorEmitter.event;
}
;
get onMessage() {
return this.onMessageEmitter.event;
}
;
close() {
this.toDispose.dispose();
}
}
exports.AbstractChannel = AbstractChannel;
/**
* A very basic {@link AbstractChannel} implementation which takes a function
* for retrieving the {@link WriteBuffer} as constructor argument.
*/
class BasicChannel extends AbstractChannel {
constructor(writeBufferProvider) {
super();
this.writeBufferProvider = writeBufferProvider;
}
getWriteBuffer() {
return this.writeBufferProvider();
}
}
exports.BasicChannel = BasicChannel;
/**
* Helper class to implement the single channels on a {@link ChannelMultiplexer}. Simply forwards write requests to
* the given write buffer source i.e. the main channel of the {@link ChannelMultiplexer}.
*/
class ForwardingChannel extends AbstractChannel {
constructor(id, closeHandler, writeBufferSource) {
super();
this.id = id;
this.closeHandler = closeHandler;
this.writeBufferSource = writeBufferSource;
}
getWriteBuffer() {
return this.writeBufferSource();
}
close() {
super.close();
this.closeHandler();
}
}
exports.ForwardingChannel = ForwardingChannel;
/**
* The different message types used in the messaging protocol of the {@link ChannelMultiplexer}
*/
var MessageTypes;
(function (MessageTypes) {
MessageTypes[MessageTypes["Open"] = 1] = "Open";
MessageTypes[MessageTypes["Close"] = 2] = "Close";
MessageTypes[MessageTypes["AckOpen"] = 3] = "AckOpen";
MessageTypes[MessageTypes["Data"] = 4] = "Data";
})(MessageTypes = exports.MessageTypes || (exports.MessageTypes = {}));
/**
* The write buffers in this implementation immediately write to the underlying
* channel, so we rely on writers to the multiplexed channels to always commit their
* messages and always in one go.
*/
class ChannelMultiplexer {
constructor(underlyingChannel) {
this.underlyingChannel = underlyingChannel;
this.pendingOpen = new Map();
this.openChannels = new Map();
this.onOpenChannelEmitter = new event_1.Emitter();
this.toDispose = new disposable_1.DisposableCollection();
this.toDispose.pushAll([
this.underlyingChannel.onMessage(buffer => this.handleMessage(buffer())),
this.underlyingChannel.onClose(event => this.onUnderlyingChannelClose(event)),
this.underlyingChannel.onError(error => this.handleError(error)),
this.onOpenChannelEmitter
]);
}
get onDidOpenChannel() {
return this.onOpenChannelEmitter.event;
}
handleError(error) {
this.openChannels.forEach(channel => {
channel.onErrorEmitter.fire(error);
});
}
onUnderlyingChannelClose(event) {
if (!this.toDispose.disposed) {
this.toDispose.push(disposable_1.Disposable.create(() => {
this.pendingOpen.clear();
this.openChannels.forEach(channel => {
channel.onCloseEmitter.fire(event !== null && event !== void 0 ? event : { reason: 'Multiplexer main channel has been closed from the remote side!' });
});
this.openChannels.clear();
}));
this.dispose();
}
}
handleMessage(buffer) {
const type = buffer.readUint8();
const id = buffer.readString();
switch (type) {
case MessageTypes.AckOpen: {
return this.handleAckOpen(id);
}
case MessageTypes.Open: {
return this.handleOpen(id);
}
case MessageTypes.Close: {
return this.handleClose(id);
}
case MessageTypes.Data: {
return this.handleData(id, buffer);
}
}
}
handleAckOpen(id) {
// edge case: both side try to open a channel at the same time.
const resolve = this.pendingOpen.get(id);
if (resolve) {
const channel = this.createChannel(id);
this.pendingOpen.delete(id);
this.openChannels.set(id, channel);
resolve(channel);
this.onOpenChannelEmitter.fire({ id, channel });
}
}
handleOpen(id) {
if (!this.openChannels.has(id)) {
const channel = this.createChannel(id);
this.openChannels.set(id, channel);
const resolve = this.pendingOpen.get(id);
if (resolve) {
// edge case: both side try to open a channel at the same time.
resolve(channel);
}
this.underlyingChannel.getWriteBuffer().writeUint8(MessageTypes.AckOpen).writeString(id).commit();
this.onOpenChannelEmitter.fire({ id, channel });
}
}
handleClose(id) {
const channel = this.openChannels.get(id);
if (channel) {
channel.onCloseEmitter.fire({ reason: 'Channel has been closed from the remote side' });
this.openChannels.delete(id);
}
}
handleData(id, data) {
const channel = this.openChannels.get(id);
if (channel) {
channel.onMessageEmitter.fire(() => data.sliceAtReadPosition());
}
}
createChannel(id) {
return new ForwardingChannel(id, () => this.closeChannel(id), () => this.prepareWriteBuffer(id));
}
// Prepare the write buffer for the channel with the give, id. The channel id has to be encoded
// and written to the buffer before the actual message.
prepareWriteBuffer(id) {
const underlying = this.underlyingChannel.getWriteBuffer();
underlying.writeUint8(MessageTypes.Data);
underlying.writeString(id);
return underlying;
}
closeChannel(id) {
this.underlyingChannel.getWriteBuffer()
.writeUint8(MessageTypes.Close)
.writeString(id)
.commit();
this.openChannels.delete(id);
}
open(id) {
if (this.openChannels.has(id)) {
throw new Error(`Another channel with the id '${id}' is already open.`);
}
const result = new Promise((resolve, reject) => {
this.pendingOpen.set(id, resolve);
});
this.underlyingChannel.getWriteBuffer().writeUint8(MessageTypes.Open).writeString(id).commit();
return result;
}
getOpenChannel(id) {
return this.openChannels.get(id);
}
dispose() {
this.toDispose.dispose();
}
}
exports.ChannelMultiplexer = ChannelMultiplexer;
//# sourceMappingURL=channel.js.map