@theia/core 1.26.0 → 1.27.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/lib/browser/messaging/ws-connection-provider.d.ts +5 -4
- package/lib/browser/messaging/ws-connection-provider.d.ts.map +1 -1
- package/lib/browser/messaging/ws-connection-provider.js +30 -23
- package/lib/browser/messaging/ws-connection-provider.js.map +1 -1
- package/lib/browser/progress-status-bar-item.d.ts +1 -1
- package/lib/browser/progress-status-bar-item.d.ts.map +1 -1
- package/lib/browser/tree/tree-compression/compressed-tree-widget.js +2 -2
- package/lib/browser/tree/tree-compression/compressed-tree-widget.js.map +1 -1
- package/lib/browser/widgets/select-component.d.ts +4 -1
- package/lib/browser/widgets/select-component.d.ts.map +1 -1
- package/lib/browser/widgets/select-component.js +30 -16
- package/lib/browser/widgets/select-component.js.map +1 -1
- package/lib/common/cancellation.d.ts +1 -0
- package/lib/common/cancellation.d.ts.map +1 -1
- package/lib/common/cancellation.js +8 -0
- package/lib/common/cancellation.js.map +1 -1
- package/lib/common/index.d.ts +2 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +2 -0
- package/lib/common/index.js.map +1 -1
- package/lib/common/message-rpc/channel.d.ts +106 -0
- package/lib/common/message-rpc/channel.d.ts.map +1 -0
- package/lib/common/message-rpc/channel.js +195 -0
- package/lib/common/message-rpc/channel.js.map +1 -0
- package/lib/common/message-rpc/channel.spec.d.ts +9 -0
- package/lib/common/message-rpc/channel.spec.d.ts.map +1 -0
- package/lib/common/message-rpc/channel.spec.js +80 -0
- package/lib/common/message-rpc/channel.spec.js.map +1 -0
- package/lib/common/message-rpc/index.d.ts +4 -0
- package/lib/common/message-rpc/index.d.ts.map +1 -0
- package/lib/{node/messaging/logger.js → common/message-rpc/index.js} +6 -19
- package/lib/common/message-rpc/index.js.map +1 -0
- package/lib/common/message-rpc/message-buffer.d.ts +50 -0
- package/lib/common/message-rpc/message-buffer.d.ts.map +1 -0
- package/lib/common/message-rpc/message-buffer.js +56 -0
- package/lib/common/message-rpc/message-buffer.js.map +1 -0
- package/lib/common/message-rpc/rpc-message-encoder.d.ts +159 -0
- package/lib/common/message-rpc/rpc-message-encoder.d.ts.map +1 -0
- package/lib/common/message-rpc/rpc-message-encoder.js +362 -0
- package/lib/common/message-rpc/rpc-message-encoder.js.map +1 -0
- package/lib/common/message-rpc/rpc-message-encoder.spec.d.ts +2 -0
- package/lib/common/message-rpc/rpc-message-encoder.spec.d.ts.map +1 -0
- package/lib/common/message-rpc/rpc-message-encoder.spec.js +37 -0
- package/lib/common/message-rpc/rpc-message-encoder.spec.js.map +1 -0
- package/lib/common/message-rpc/rpc-protocol.d.ts +61 -0
- package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -0
- package/lib/common/message-rpc/rpc-protocol.js +183 -0
- package/lib/common/message-rpc/rpc-protocol.js.map +1 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.d.ts +52 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.d.ts.map +1 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.js +169 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.js.map +1 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.spec.d.ts +2 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.spec.d.ts.map +1 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.spec.js +39 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.spec.js.map +1 -0
- package/lib/common/messaging/abstract-connection-provider.d.ts +9 -8
- package/lib/common/messaging/abstract-connection-provider.d.ts.map +1 -1
- package/lib/common/messaging/abstract-connection-provider.js +20 -35
- package/lib/common/messaging/abstract-connection-provider.js.map +1 -1
- package/lib/common/messaging/connection-error-handler.d.ts +1 -2
- package/lib/common/messaging/connection-error-handler.d.ts.map +1 -1
- package/lib/common/messaging/connection-error-handler.js +1 -1
- package/lib/common/messaging/connection-error-handler.js.map +1 -1
- package/lib/common/messaging/handler.d.ts +2 -2
- package/lib/common/messaging/handler.d.ts.map +1 -1
- package/lib/common/messaging/proxy-factory.d.ts +13 -7
- package/lib/common/messaging/proxy-factory.d.ts.map +1 -1
- package/lib/common/messaging/proxy-factory.js +18 -13
- package/lib/common/messaging/proxy-factory.js.map +1 -1
- package/lib/common/messaging/proxy-factory.spec.js +4 -15
- package/lib/common/messaging/proxy-factory.spec.js.map +1 -1
- package/lib/common/messaging/web-socket-channel.d.ts +54 -48
- package/lib/common/messaging/web-socket-channel.d.ts.map +1 -1
- package/lib/common/messaging/web-socket-channel.js +41 -105
- package/lib/common/messaging/web-socket-channel.js.map +1 -1
- package/lib/electron-browser/messaging/electron-ipc-connection-provider.d.ts +2 -2
- package/lib/electron-browser/messaging/electron-ipc-connection-provider.d.ts.map +1 -1
- package/lib/electron-browser/messaging/electron-ipc-connection-provider.js +18 -7
- package/lib/electron-browser/messaging/electron-ipc-connection-provider.js.map +1 -1
- package/lib/electron-browser/messaging/electron-ws-connection-provider.d.ts +2 -2
- package/lib/electron-browser/messaging/electron-ws-connection-provider.d.ts.map +1 -1
- package/lib/electron-browser/messaging/electron-ws-connection-provider.js +5 -7
- package/lib/electron-browser/messaging/electron-ws-connection-provider.js.map +1 -1
- package/lib/electron-main/messaging/electron-messaging-contribution.d.ts +37 -9
- package/lib/electron-main/messaging/electron-messaging-contribution.d.ts.map +1 -1
- package/lib/electron-main/messaging/electron-messaging-contribution.js +83 -68
- package/lib/electron-main/messaging/electron-messaging-contribution.js.map +1 -1
- package/lib/electron-main/messaging/electron-messaging-service.d.ts +2 -8
- package/lib/electron-main/messaging/electron-messaging-service.d.ts.map +1 -1
- package/lib/electron-main/messaging/electron-messaging-service.js.map +1 -1
- package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
- package/lib/electron-main/theia-electron-window.js +11 -8
- package/lib/electron-main/theia-electron-window.js.map +1 -1
- package/lib/node/messaging/binary-message-pipe.d.ts +45 -0
- package/lib/node/messaging/binary-message-pipe.d.ts.map +1 -0
- package/lib/node/messaging/binary-message-pipe.js +152 -0
- package/lib/node/messaging/binary-message-pipe.js.map +1 -0
- package/lib/node/messaging/ipc-bootstrap.js +2 -11
- package/lib/node/messaging/ipc-bootstrap.js.map +1 -1
- package/lib/node/messaging/ipc-channel.d.ts +26 -0
- package/lib/node/messaging/ipc-channel.d.ts.map +1 -0
- package/lib/node/messaging/ipc-channel.js +86 -0
- package/lib/node/messaging/ipc-channel.js.map +1 -0
- package/lib/node/messaging/ipc-connection-provider.d.ts +3 -5
- package/lib/node/messaging/ipc-connection-provider.d.ts.map +1 -1
- package/lib/node/messaging/ipc-connection-provider.js +14 -31
- package/lib/node/messaging/ipc-connection-provider.js.map +1 -1
- package/lib/node/messaging/ipc-protocol.d.ts +2 -2
- package/lib/node/messaging/ipc-protocol.d.ts.map +1 -1
- package/lib/node/messaging/messaging-contribution.d.ts +6 -9
- package/lib/node/messaging/messaging-contribution.d.ts.map +1 -1
- package/lib/node/messaging/messaging-contribution.js +23 -68
- package/lib/node/messaging/messaging-contribution.js.map +1 -1
- package/lib/node/messaging/messaging-service.d.ts +4 -23
- package/lib/node/messaging/messaging-service.d.ts.map +1 -1
- package/lib/node/messaging/messaging-service.js +1 -15
- package/lib/node/messaging/messaging-service.js.map +1 -1
- package/lib/node/messaging/test/test-web-socket-channel.d.ts +4 -2
- package/lib/node/messaging/test/test-web-socket-channel.d.ts.map +1 -1
- package/lib/node/messaging/test/test-web-socket-channel.js +25 -12
- package/lib/node/messaging/test/test-web-socket-channel.js.map +1 -1
- package/package.json +5 -8
- package/src/browser/messaging/ws-connection-provider.ts +34 -25
- package/src/browser/progress-status-bar-item.ts +1 -1
- package/src/browser/style/menus.css +1 -0
- package/src/browser/tree/tree-compression/compressed-tree-widget.tsx +2 -2
- package/src/browser/widgets/select-component.tsx +37 -17
- package/src/common/cancellation.ts +8 -0
- package/src/common/index.ts +2 -0
- package/src/common/message-rpc/channel.spec.ts +88 -0
- package/src/common/message-rpc/channel.ts +260 -0
- package/src/{node/messaging/logger.ts → common/message-rpc/index.ts} +4 -23
- package/src/common/message-rpc/message-buffer.ts +99 -0
- package/src/common/message-rpc/rpc-message-encoder.spec.ts +42 -0
- package/src/common/message-rpc/rpc-message-encoder.ts +497 -0
- package/src/common/message-rpc/rpc-protocol.ts +217 -0
- package/src/common/message-rpc/uint8-array-message-buffer.spec.ts +41 -0
- package/src/common/message-rpc/uint8-array-message-buffer.ts +206 -0
- package/src/common/messaging/abstract-connection-provider.ts +28 -37
- package/src/common/messaging/connection-error-handler.ts +1 -2
- package/src/common/messaging/handler.ts +2 -2
- package/src/common/messaging/proxy-factory.spec.ts +4 -17
- package/src/common/messaging/proxy-factory.ts +27 -16
- package/src/common/messaging/web-socket-channel.ts +79 -135
- package/src/electron-browser/messaging/electron-ipc-connection-provider.ts +21 -7
- package/src/electron-browser/messaging/electron-ws-connection-provider.ts +5 -8
- package/src/electron-main/messaging/electron-messaging-contribution.ts +87 -65
- package/src/electron-main/messaging/electron-messaging-service.ts +2 -8
- package/src/electron-main/theia-electron-window.ts +12 -9
- package/src/node/messaging/binary-message-pipe.ts +168 -0
- package/src/node/messaging/ipc-bootstrap.ts +3 -11
- package/src/node/messaging/ipc-channel.ts +97 -0
- package/src/node/messaging/ipc-connection-provider.ts +18 -35
- package/src/node/messaging/ipc-protocol.ts +2 -2
- package/src/node/messaging/messaging-contribution.ts +29 -74
- package/src/node/messaging/messaging-service.ts +4 -31
- package/src/node/messaging/test/test-web-socket-channel.ts +26 -17
- package/lib/node/messaging/logger.d.ts +0 -8
- package/lib/node/messaging/logger.d.ts.map +0 -1
- package/lib/node/messaging/logger.js.map +0 -1
- package/shared/vscode-ws-jsonrpc/index.d.ts +0 -1
- package/shared/vscode-ws-jsonrpc/index.js +0 -1
|
@@ -20,8 +20,10 @@ import { APPLICATION_STATE_CHANGE_SIGNAL, CLOSE_REQUESTED_SIGNAL, RELOAD_REQUEST
|
|
|
20
20
|
import { BrowserWindow, BrowserWindowConstructorOptions, ipcMain, IpcMainEvent } from '../../electron-shared/electron';
|
|
21
21
|
import { inject, injectable, postConstruct } from '../../shared/inversify';
|
|
22
22
|
import { ElectronMainApplicationGlobals } from './electron-main-constants';
|
|
23
|
-
import { DisposableCollection, Emitter, Event
|
|
23
|
+
import { DisposableCollection, Emitter, Event } from '../common';
|
|
24
24
|
import { createDisposableListener } from './event-utils';
|
|
25
|
+
import { URI } from '../common/uri';
|
|
26
|
+
import { FileUri } from '../node/file-uri';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Theia tracks the maximized state of Electron Browser Windows.
|
|
@@ -114,14 +116,15 @@ export class TheiaElectronWindow {
|
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
protected async handleStopRequest(onSafeCallback: () => unknown, reason: StopReason): Promise<boolean> {
|
|
117
|
-
// Only confirm close to windows that have loaded our
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
// Only confirm close to windows that have loaded our frontend.
|
|
120
|
+
// Both the windows's URL and the FS path of the `index.html` should be converted to the "same" format to be able to compare them. (#11226)
|
|
121
|
+
// Notes:
|
|
122
|
+
// - Windows: file:///C:/path/to/somewhere vs file:///c%3A/path/to/somewhere
|
|
123
|
+
// - macOS: file:///Applications/App%20Name.app/Contents vs /Applications/App Name.app/Contents
|
|
124
|
+
// This URL string comes from electron, we can expect that this is properly encoded URL. For example, a space is `%20`
|
|
125
|
+
const currentUrl = new URI(this.window.webContents.getURL()).toString();
|
|
126
|
+
// THEIA_FRONTEND_HTML_PATH is an FS path, we have to covert to an encoded URI string.
|
|
127
|
+
const frontendUri = FileUri.create(this.globals.THEIA_FRONTEND_HTML_PATH).toString();
|
|
125
128
|
const safeToClose = !currentUrl.includes(frontendUri) || await this.checkSafeToStop(reason);
|
|
126
129
|
if (safeToClose) {
|
|
127
130
|
try {
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2022 STMicroelectronics and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { Duplex } from 'stream';
|
|
18
|
+
import { Disposable, Emitter, Event } from '../../common';
|
|
19
|
+
import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '../../common/message-rpc/uint8-array-message-buffer';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A `BinaryMessagePipe` is capable of sending and retrieving binary messages i.e. {@link Uint8Array}s over
|
|
23
|
+
* and underlying streamed process pipe/fd. The message length of individual messages is encoding at the beginning of
|
|
24
|
+
* a new message. This makes it possible to extract messages from the streamed data.
|
|
25
|
+
*/
|
|
26
|
+
export class BinaryMessagePipe implements Disposable {
|
|
27
|
+
static readonly MESSAGE_START_IDENTIFIER = '<MessageStart>';
|
|
28
|
+
protected dataHandler = (chunk: Uint8Array) => this.handleChunk(chunk);
|
|
29
|
+
|
|
30
|
+
protected onMessageEmitter = new Emitter<Uint8Array>();
|
|
31
|
+
protected cachedMessageData: StreamedMessageData = {
|
|
32
|
+
chunks: [],
|
|
33
|
+
missingBytes: 0
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
get onMessage(): Event<Uint8Array> {
|
|
37
|
+
return this.onMessageEmitter.event;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
constructor(protected readonly underlyingPipe: Duplex) {
|
|
41
|
+
underlyingPipe.on('data', this.dataHandler);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
send(message: Uint8Array): void {
|
|
45
|
+
this.underlyingPipe.write(this.encodeMessageStart(message));
|
|
46
|
+
this.underlyingPipe.write(message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected handleChunk(chunk: Uint8Array): void {
|
|
50
|
+
if (this.cachedMessageData.missingBytes === 0) {
|
|
51
|
+
// There is no currently streamed message => We expect that the beginning of the chunk is the message start for a new message
|
|
52
|
+
this.handleNewMessage(chunk);
|
|
53
|
+
} else {
|
|
54
|
+
// The chunk contains message data intended for the currently cached message
|
|
55
|
+
this.handleMessageContentChunk(chunk);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected handleNewMessage(chunk: Uint8Array): void {
|
|
60
|
+
if (chunk.byteLength < this.messageStartByteLength) {
|
|
61
|
+
// The chunk only contains a part of the encoded message start
|
|
62
|
+
this.cachedMessageData.partialMessageStart = chunk;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const messageLength = this.readMessageStart(chunk);
|
|
67
|
+
|
|
68
|
+
if (chunk.length - this.messageStartByteLength > messageLength) {
|
|
69
|
+
// The initial chunk contains more than one binary message => Fire `onMessage` for first message and handle remaining content
|
|
70
|
+
const firstMessage = chunk.slice(this.messageStartByteLength, this.messageStartByteLength + messageLength);
|
|
71
|
+
this.onMessageEmitter.fire(firstMessage);
|
|
72
|
+
this.handleNewMessage(chunk.slice(this.messageStartByteLength + messageLength));
|
|
73
|
+
|
|
74
|
+
} else if (chunk.length - this.messageStartByteLength === messageLength) {
|
|
75
|
+
// The initial chunk contains exactly one complete message. => Directly fire the `onMessage` event.
|
|
76
|
+
this.onMessageEmitter.fire(chunk.slice(this.messageStartByteLength));
|
|
77
|
+
} else {
|
|
78
|
+
// The initial chunk contains only part of the message content => Cache message data
|
|
79
|
+
this.cachedMessageData.chunks = [chunk.slice(this.messageStartByteLength)];
|
|
80
|
+
this.cachedMessageData.missingBytes = messageLength - chunk.byteLength + this.messageStartByteLength;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected handleMessageContentChunk(chunk: Uint8Array): void {
|
|
85
|
+
if (this.cachedMessageData) {
|
|
86
|
+
if (chunk.byteLength < this.cachedMessageData.missingBytes) {
|
|
87
|
+
// The chunk only contains parts of the missing bytes for the cached message.
|
|
88
|
+
this.cachedMessageData.chunks.push(chunk);
|
|
89
|
+
this.cachedMessageData.missingBytes -= chunk.byteLength;
|
|
90
|
+
} else if (chunk.byteLength === this.cachedMessageData.missingBytes) {
|
|
91
|
+
// Chunk contains exactly the missing data for the cached message
|
|
92
|
+
this.cachedMessageData.chunks.push(chunk);
|
|
93
|
+
this.emitCachedMessage();
|
|
94
|
+
} else {
|
|
95
|
+
// Chunk contains missing data for the cached message + data for the next message
|
|
96
|
+
const messageEnd = this.cachedMessageData.missingBytes;
|
|
97
|
+
const missingData = chunk.slice(0, messageEnd);
|
|
98
|
+
this.cachedMessageData.chunks.push(missingData);
|
|
99
|
+
this.emitCachedMessage();
|
|
100
|
+
this.handleNewMessage(chunk.slice(messageEnd));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected emitCachedMessage(): void {
|
|
107
|
+
const message = Buffer.concat(this.cachedMessageData.chunks);
|
|
108
|
+
this.onMessageEmitter.fire(message);
|
|
109
|
+
this.cachedMessageData.chunks = [];
|
|
110
|
+
this.cachedMessageData.missingBytes = 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Encodes the start of a new message into a {@link Uint8Array}.
|
|
115
|
+
* The message start consists of a identifier string and the length of the following message.
|
|
116
|
+
* @returns the buffer contains the encoded message start
|
|
117
|
+
*/
|
|
118
|
+
protected encodeMessageStart(message: Uint8Array): Uint8Array {
|
|
119
|
+
const writer = new Uint8ArrayWriteBuffer()
|
|
120
|
+
.writeString(BinaryMessagePipe.MESSAGE_START_IDENTIFIER)
|
|
121
|
+
.writeUint32(message.length);
|
|
122
|
+
const messageStart = writer.getCurrentContents();
|
|
123
|
+
writer.dispose();
|
|
124
|
+
return messageStart;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
protected get messageStartByteLength(): number {
|
|
128
|
+
// 4 bytes for length of id + id string length + 4 bytes for length of message
|
|
129
|
+
return 4 + BinaryMessagePipe.MESSAGE_START_IDENTIFIER.length + 4;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reads the start of a new message from a stream chunk (or cached message) received from the underlying pipe.
|
|
134
|
+
* The message start is expected to consist of an identifier string and the length of the message.
|
|
135
|
+
* @param chunk The stream chunk.
|
|
136
|
+
* @returns The length of the message content to read.
|
|
137
|
+
* @throws An error if the message start can not be read successfully.
|
|
138
|
+
*/
|
|
139
|
+
protected readMessageStart(chunk: Uint8Array): number {
|
|
140
|
+
const messageData = this.cachedMessageData.partialMessageStart ? Buffer.concat([this.cachedMessageData.partialMessageStart, chunk]) : chunk;
|
|
141
|
+
this.cachedMessageData.partialMessageStart = undefined;
|
|
142
|
+
|
|
143
|
+
const reader = new Uint8ArrayReadBuffer(messageData);
|
|
144
|
+
const identifier = reader.readString();
|
|
145
|
+
|
|
146
|
+
if (identifier !== BinaryMessagePipe.MESSAGE_START_IDENTIFIER) {
|
|
147
|
+
throw new Error(`Could not read message start. The start identifier should be '${BinaryMessagePipe.MESSAGE_START_IDENTIFIER}' but was '${identifier}`);
|
|
148
|
+
}
|
|
149
|
+
const length = reader.readUint32();
|
|
150
|
+
return length;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
dispose(): void {
|
|
154
|
+
this.underlyingPipe.removeListener('data', this.dataHandler);
|
|
155
|
+
this.underlyingPipe.end();
|
|
156
|
+
this.onMessageEmitter.dispose();
|
|
157
|
+
this.cachedMessageData = {
|
|
158
|
+
chunks: [],
|
|
159
|
+
missingBytes: 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface StreamedMessageData {
|
|
165
|
+
chunks: Uint8Array[];
|
|
166
|
+
missingBytes: number;
|
|
167
|
+
partialMessageStart?: Uint8Array;
|
|
168
|
+
}
|
|
@@ -16,20 +16,12 @@
|
|
|
16
16
|
|
|
17
17
|
import 'reflect-metadata';
|
|
18
18
|
import { dynamicRequire } from '../dynamic-require';
|
|
19
|
-
import {
|
|
20
|
-
import { createMessageConnection, IPCMessageReader, IPCMessageWriter, Trace } from 'vscode-ws-jsonrpc';
|
|
19
|
+
import { IPCChannel } from './ipc-channel';
|
|
21
20
|
import { checkParentAlive, IPCEntryPoint } from './ipc-protocol';
|
|
22
21
|
|
|
23
22
|
checkParentAlive();
|
|
24
23
|
|
|
25
24
|
const entryPoint = IPCEntryPoint.getScriptFromEnv();
|
|
26
|
-
const reader = new IPCMessageReader(process);
|
|
27
|
-
const writer = new IPCMessageWriter(process);
|
|
28
|
-
const logger = new ConsoleLogger();
|
|
29
|
-
const connection = createMessageConnection(reader, writer, logger);
|
|
30
|
-
connection.trace(Trace.Off, {
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
-
log: (message: any, data?: string) => console.log(message, data)
|
|
33
|
-
});
|
|
34
25
|
|
|
35
|
-
dynamicRequire<{ default: IPCEntryPoint }>(entryPoint).default(
|
|
26
|
+
dynamicRequire<{ default: IPCEntryPoint }>(entryPoint).default(new IPCChannel());
|
|
27
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2022 STMicroelectronics and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
17
|
+
|
|
18
|
+
import * as cp from 'child_process';
|
|
19
|
+
import { Socket } from 'net';
|
|
20
|
+
import { Duplex } from 'stream';
|
|
21
|
+
import { Channel, ChannelCloseEvent, Disposable, DisposableCollection, Emitter, Event, MessageProvider, WriteBuffer } from '../../common';
|
|
22
|
+
import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '../../common/message-rpc/uint8-array-message-buffer';
|
|
23
|
+
import { BinaryMessagePipe } from './binary-message-pipe';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A {@link Channel} to send messages between two processes using a dedicated pipe/fd for binary messages.
|
|
27
|
+
* This fd is opened as 5th channel in addition to the default stdios (stdin,stdout,stderr, ipc). This means the default channels
|
|
28
|
+
* are not blocked and can be used by the respective process for additional custom message handling.
|
|
29
|
+
*/
|
|
30
|
+
export class IPCChannel implements Channel {
|
|
31
|
+
|
|
32
|
+
protected readonly onCloseEmitter: Emitter<ChannelCloseEvent> = new Emitter();
|
|
33
|
+
get onClose(): Event<ChannelCloseEvent> {
|
|
34
|
+
return this.onCloseEmitter.event;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected readonly onMessageEmitter: Emitter<MessageProvider> = new Emitter();
|
|
38
|
+
get onMessage(): Event<MessageProvider> {
|
|
39
|
+
return this.onMessageEmitter.event;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected readonly onErrorEmitter: Emitter<unknown> = new Emitter();
|
|
43
|
+
get onError(): Event<unknown> {
|
|
44
|
+
return this.onErrorEmitter.event;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected messagePipe: BinaryMessagePipe;
|
|
48
|
+
protected toDispose = new DisposableCollection();
|
|
49
|
+
|
|
50
|
+
protected ipcErrorListener: (error: Error) => void = error => this.onErrorEmitter.fire(error);
|
|
51
|
+
|
|
52
|
+
constructor(childProcess?: cp.ChildProcess) {
|
|
53
|
+
if (childProcess) {
|
|
54
|
+
this.setupChildProcess(childProcess);
|
|
55
|
+
} else {
|
|
56
|
+
this.setupProcess();
|
|
57
|
+
}
|
|
58
|
+
this.messagePipe.onMessage(message => {
|
|
59
|
+
this.onMessageEmitter.fire(() => new Uint8ArrayReadBuffer(message));
|
|
60
|
+
});
|
|
61
|
+
this.toDispose.pushAll([this.onCloseEmitter, this.onMessageEmitter, this.onErrorEmitter]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected setupChildProcess(childProcess: cp.ChildProcess): void {
|
|
65
|
+
childProcess.once('exit', code => this.onCloseEmitter.fire({ reason: 'Child process has been terminated', code: code ?? undefined }));
|
|
66
|
+
this.messagePipe = new BinaryMessagePipe(childProcess.stdio[4] as Duplex);
|
|
67
|
+
childProcess.on('error', this.ipcErrorListener);
|
|
68
|
+
this.toDispose.push(Disposable.create(() => {
|
|
69
|
+
childProcess.removeListener('error', this.ipcErrorListener);
|
|
70
|
+
this.messagePipe.dispose();
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected setupProcess(): void {
|
|
75
|
+
process.once('beforeExit', code => this.onCloseEmitter.fire({ reason: 'Process is about to be terminated', code }));
|
|
76
|
+
this.messagePipe = new BinaryMessagePipe(new Socket({ fd: 4 }));
|
|
77
|
+
process.on('uncaughtException', this.ipcErrorListener);
|
|
78
|
+
this.toDispose.push(Disposable.create(() => {
|
|
79
|
+
(process as NodeJS.EventEmitter).removeListener('uncaughtException', this.ipcErrorListener);
|
|
80
|
+
this.messagePipe.dispose();
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getWriteBuffer(): WriteBuffer {
|
|
85
|
+
const result = new Uint8ArrayWriteBuffer();
|
|
86
|
+
result.onCommit(buffer => {
|
|
87
|
+
this.messagePipe.send(buffer);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
close(): void {
|
|
94
|
+
this.toDispose.dispose();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import * as cp from 'child_process';
|
|
18
|
+
import { inject, injectable } from 'inversify';
|
|
18
19
|
import * as path from 'path';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
20
|
+
import { createInterface } from 'readline';
|
|
21
|
+
import { Channel, ConnectionErrorHandler, Disposable, DisposableCollection, ILogger } from '../../common';
|
|
22
|
+
import { IPCChannel } from './ipc-channel';
|
|
22
23
|
import { createIpcEnv } from './ipc-protocol';
|
|
23
24
|
|
|
24
25
|
export interface ResolvedIPCConnectionOptions {
|
|
@@ -40,7 +41,7 @@ export class IPCConnectionProvider {
|
|
|
40
41
|
@inject(ILogger)
|
|
41
42
|
protected readonly logger: ILogger;
|
|
42
43
|
|
|
43
|
-
listen(options: IPCConnectionOptions, acceptor: (connection:
|
|
44
|
+
listen(options: IPCConnectionOptions, acceptor: (connection: Channel) => void): Disposable {
|
|
44
45
|
return this.doListen({
|
|
45
46
|
logger: this.logger,
|
|
46
47
|
args: [],
|
|
@@ -48,19 +49,21 @@ export class IPCConnectionProvider {
|
|
|
48
49
|
}, acceptor);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
protected doListen(options: ResolvedIPCConnectionOptions, acceptor: (connection:
|
|
52
|
+
protected doListen(options: ResolvedIPCConnectionOptions, acceptor: (connection: Channel) => void): Disposable {
|
|
52
53
|
const childProcess = this.fork(options);
|
|
53
|
-
const
|
|
54
|
+
const channel = new IPCChannel(childProcess);
|
|
54
55
|
const toStop = new DisposableCollection();
|
|
55
56
|
const toCancelStop = toStop.push(Disposable.create(() => childProcess.kill()));
|
|
56
57
|
const errorHandler = options.errorHandler;
|
|
57
58
|
if (errorHandler) {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
let errorCount = 0;
|
|
60
|
+
channel.onError((err: Error) => {
|
|
61
|
+
errorCount++;
|
|
62
|
+
if (errorHandler.shouldStop(err, errorCount)) {
|
|
60
63
|
toStop.dispose();
|
|
61
64
|
}
|
|
62
65
|
});
|
|
63
|
-
|
|
66
|
+
channel.onClose(() => {
|
|
64
67
|
if (toStop.disposed) {
|
|
65
68
|
return;
|
|
66
69
|
}
|
|
@@ -70,36 +73,15 @@ export class IPCConnectionProvider {
|
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
}
|
|
73
|
-
acceptor(
|
|
76
|
+
acceptor(channel);
|
|
74
77
|
return toStop;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
protected createConnection(childProcess: cp.ChildProcess, options: ResolvedIPCConnectionOptions): MessageConnection {
|
|
78
|
-
const reader = new IPCMessageReader(childProcess);
|
|
79
|
-
const writer = new IPCMessageWriter(childProcess);
|
|
80
|
-
const connection = createMessageConnection(reader, writer, {
|
|
81
|
-
error: (message: string) => this.logger.error(`[${options.serverName}: ${childProcess.pid}] ${message}`),
|
|
82
|
-
warn: (message: string) => this.logger.warn(`[${options.serverName}: ${childProcess.pid}] ${message}`),
|
|
83
|
-
info: (message: string) => this.logger.info(`[${options.serverName}: ${childProcess.pid}] ${message}`),
|
|
84
|
-
log: (message: string) => this.logger.info(`[${options.serverName}: ${childProcess.pid}] ${message}`)
|
|
85
|
-
});
|
|
86
|
-
const tracer: Tracer = {
|
|
87
|
-
log: (message: unknown, data?: string) => this.logger.debug(`[${options.serverName}: ${childProcess.pid}] ${message}` + (typeof data === 'string' ? ' ' + data : ''))
|
|
88
|
-
};
|
|
89
|
-
connection.trace(Trace.Verbose, tracer);
|
|
90
|
-
this.logger.isDebug().then(isDebug => {
|
|
91
|
-
if (!isDebug) {
|
|
92
|
-
connection.trace(Trace.Off, tracer);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
return connection;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
80
|
protected fork(options: ResolvedIPCConnectionOptions): cp.ChildProcess {
|
|
99
81
|
const forkOptions: cp.ForkOptions = {
|
|
100
|
-
silent: true,
|
|
101
82
|
env: createIpcEnv(options),
|
|
102
|
-
execArgv: []
|
|
83
|
+
execArgv: [],
|
|
84
|
+
stdio: ['pipe', 'pipe', 'pipe', 'ipc', 'pipe']
|
|
103
85
|
};
|
|
104
86
|
const inspectArgPrefix = `--${options.serverName}-inspect`;
|
|
105
87
|
const inspectArg = process.argv.find(v => v.startsWith(inspectArgPrefix));
|
|
@@ -108,8 +90,9 @@ export class IPCConnectionProvider {
|
|
|
108
90
|
}
|
|
109
91
|
|
|
110
92
|
const childProcess = cp.fork(path.join(__dirname, 'ipc-bootstrap'), options.args, forkOptions);
|
|
111
|
-
|
|
112
|
-
childProcess.
|
|
93
|
+
|
|
94
|
+
createInterface(childProcess.stdout!).on('line', line => this.logger.info(`[${options.serverName}: ${childProcess.pid}] ${line}`));
|
|
95
|
+
createInterface(childProcess.stderr!).on('line', line => this.logger.error(`[${options.serverName}: ${childProcess.pid}] ${line}`));
|
|
113
96
|
|
|
114
97
|
this.logger.debug(`[${options.serverName}: ${childProcess.pid}] IPC started`);
|
|
115
98
|
childProcess.once('exit', () => this.logger.debug(`[${options.serverName}: ${childProcess.pid}] IPC exited`));
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
16
16
|
// *****************************************************************************
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { Channel } from '../../common/message-rpc/channel';
|
|
19
19
|
|
|
20
20
|
const THEIA_PARENT_PID = 'THEIA_PARENT_PID';
|
|
21
21
|
const THEIA_ENTRY_POINT = 'THEIA_ENTRY_POINT';
|
|
22
22
|
|
|
23
23
|
export const ipcEntryPoint: string | undefined = process.env[THEIA_ENTRY_POINT];
|
|
24
24
|
|
|
25
|
-
export type IPCEntryPoint = (connection:
|
|
25
|
+
export type IPCEntryPoint = (connection: Channel) => void;
|
|
26
26
|
export namespace IPCEntryPoint {
|
|
27
27
|
/**
|
|
28
28
|
* Throws if `THEIA_ENTRY_POINT` is undefined or empty.
|
|
@@ -18,19 +18,15 @@ import * as http from 'http';
|
|
|
18
18
|
import * as https from 'https';
|
|
19
19
|
import { Server, Socket } from 'socket.io';
|
|
20
20
|
import { injectable, inject, named, postConstruct, interfaces, Container } from 'inversify';
|
|
21
|
-
import { MessageConnection } from 'vscode-ws-jsonrpc';
|
|
22
|
-
import { createWebSocketConnection } from 'vscode-ws-jsonrpc/lib/socket/connection';
|
|
23
|
-
import { IConnection } from 'vscode-ws-jsonrpc/lib/server/connection';
|
|
24
|
-
import * as launch from 'vscode-ws-jsonrpc/lib/server/launch';
|
|
25
21
|
import { ContributionProvider, ConnectionHandler, bindContributionProvider } from '../../common';
|
|
26
|
-
import { WebSocketChannel } from '../../common/messaging/web-socket-channel';
|
|
22
|
+
import { IWebSocket, WebSocketChannel } from '../../common/messaging/web-socket-channel';
|
|
27
23
|
import { BackendApplicationContribution } from '../backend-application';
|
|
28
|
-
import { MessagingService
|
|
29
|
-
import { ConsoleLogger } from './logger';
|
|
24
|
+
import { MessagingService } from './messaging-service';
|
|
30
25
|
import { ConnectionContainerModule } from './connection-container-module';
|
|
31
26
|
import Route = require('route-parser');
|
|
32
27
|
import { WsRequestValidator } from '../ws-request-validators';
|
|
33
28
|
import { MessagingListener } from './messaging-listeners';
|
|
29
|
+
import { Channel, ChannelMultiplexer } from '../../common/message-rpc/channel';
|
|
34
30
|
|
|
35
31
|
export const MessagingContainer = Symbol('MessagingContainer');
|
|
36
32
|
|
|
@@ -53,7 +49,7 @@ export class MessagingContribution implements BackendApplicationContribution, Me
|
|
|
53
49
|
protected readonly messagingListener: MessagingListener;
|
|
54
50
|
|
|
55
51
|
protected readonly wsHandlers = new MessagingContribution.ConnectionHandlers<Socket>();
|
|
56
|
-
protected readonly channelHandlers = new MessagingContribution.ConnectionHandlers<
|
|
52
|
+
protected readonly channelHandlers = new MessagingContribution.ConnectionHandlers<Channel>();
|
|
57
53
|
|
|
58
54
|
@postConstruct()
|
|
59
55
|
protected init(): void {
|
|
@@ -63,21 +59,7 @@ export class MessagingContribution implements BackendApplicationContribution, Me
|
|
|
63
59
|
}
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
this.wsChannel(spec, (params, channel) => {
|
|
68
|
-
const connection = createWebSocketConnection(channel, new ConsoleLogger());
|
|
69
|
-
callback(params, connection);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
forward(spec: string, callback: (params: MessagingService.PathParams, connection: IConnection) => void): void {
|
|
74
|
-
this.wsChannel(spec, (params, channel) => {
|
|
75
|
-
const connection = launch.createWebSocketConnection(channel);
|
|
76
|
-
callback(params, WebSocketChannelConnection.create(connection, channel));
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
wsChannel(spec: string, callback: (params: MessagingService.PathParams, channel: WebSocketChannel) => void): void {
|
|
62
|
+
wsChannel(spec: string, callback: (params: MessagingService.PathParams, channel: Channel) => void): void {
|
|
81
63
|
this.channelHandlers.push(spec, (params, channel) => callback(params, channel));
|
|
82
64
|
}
|
|
83
65
|
|
|
@@ -125,49 +107,31 @@ export class MessagingContribution implements BackendApplicationContribution, Me
|
|
|
125
107
|
}
|
|
126
108
|
|
|
127
109
|
protected handleChannels(socket: Socket): void {
|
|
110
|
+
const socketChannel = new WebSocketChannel(this.toIWebSocket(socket));
|
|
111
|
+
const mulitplexer = new ChannelMultiplexer(socketChannel);
|
|
128
112
|
const channelHandlers = this.getConnectionChannelHandlers(socket);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (message.kind === 'open') {
|
|
134
|
-
const { id, path } = message;
|
|
135
|
-
const channel = this.createChannel(id, socket);
|
|
136
|
-
if (channelHandlers.route(path, channel)) {
|
|
137
|
-
channel.ready();
|
|
138
|
-
console.debug(`Opening channel for service path '${path}'. [ID: ${id}]`);
|
|
139
|
-
channels.set(id, channel);
|
|
140
|
-
channel.onClose(() => {
|
|
141
|
-
console.debug(`Closing channel on service path '${path}'. [ID: ${id}]`);
|
|
142
|
-
channels.delete(id);
|
|
143
|
-
});
|
|
144
|
-
} else {
|
|
145
|
-
console.error('Cannot find a service for the path: ' + path);
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
const { id } = message;
|
|
149
|
-
const channel = channels.get(id);
|
|
150
|
-
if (channel) {
|
|
151
|
-
channel.handleMessage(message);
|
|
152
|
-
} else {
|
|
153
|
-
console.error('The ws channel does not exist', id);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
} catch (error) {
|
|
157
|
-
console.error('Failed to handle message', { error, data });
|
|
113
|
+
mulitplexer.onDidOpenChannel(event => {
|
|
114
|
+
if (channelHandlers.route(event.id, event.channel)) {
|
|
115
|
+
console.debug(`Opening channel for service path '${event.id}'.`);
|
|
116
|
+
event.channel.onClose(() => console.debug(`Closing channel on service path '${event.id}'.`));
|
|
158
117
|
}
|
|
159
118
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected toIWebSocket(socket: Socket): IWebSocket {
|
|
122
|
+
return {
|
|
123
|
+
close: () => {
|
|
124
|
+
socket.removeAllListeners('disconnect');
|
|
125
|
+
socket.removeAllListeners('error');
|
|
126
|
+
socket.removeAllListeners('message');
|
|
127
|
+
socket.disconnect();
|
|
128
|
+
},
|
|
129
|
+
isConnected: () => socket.connected,
|
|
130
|
+
onClose: cb => socket.on('disconnect', reason => cb(reason)),
|
|
131
|
+
onError: cb => socket.on('error', error => cb(error)),
|
|
132
|
+
onMessage: cb => socket.on('message', data => cb(data)),
|
|
133
|
+
send: message => socket.emit('message', message)
|
|
134
|
+
};
|
|
171
135
|
}
|
|
172
136
|
|
|
173
137
|
protected createSocketContainer(socket: Socket): Container {
|
|
@@ -176,7 +140,7 @@ export class MessagingContribution implements BackendApplicationContribution, Me
|
|
|
176
140
|
return connectionContainer;
|
|
177
141
|
}
|
|
178
142
|
|
|
179
|
-
protected getConnectionChannelHandlers(socket: Socket): MessagingContribution.ConnectionHandlers<
|
|
143
|
+
protected getConnectionChannelHandlers(socket: Socket): MessagingContribution.ConnectionHandlers<Channel> {
|
|
180
144
|
const connectionContainer = this.createSocketContainer(socket);
|
|
181
145
|
bindContributionProvider(connectionContainer, ConnectionHandler);
|
|
182
146
|
connectionContainer.load(...this.connectionModules.getContributions());
|
|
@@ -184,21 +148,12 @@ export class MessagingContribution implements BackendApplicationContribution, Me
|
|
|
184
148
|
const connectionHandlers = connectionContainer.getNamed<ContributionProvider<ConnectionHandler>>(ContributionProvider, ConnectionHandler);
|
|
185
149
|
for (const connectionHandler of connectionHandlers.getContributions(true)) {
|
|
186
150
|
connectionChannelHandlers.push(connectionHandler.path, (_, channel) => {
|
|
187
|
-
|
|
188
|
-
connectionHandler.onConnection(connection);
|
|
151
|
+
connectionHandler.onConnection(channel);
|
|
189
152
|
});
|
|
190
153
|
}
|
|
191
154
|
return connectionChannelHandlers;
|
|
192
155
|
}
|
|
193
156
|
|
|
194
|
-
protected createChannel(id: number, socket: Socket): WebSocketChannel {
|
|
195
|
-
return new WebSocketChannel(id, content => {
|
|
196
|
-
if (socket.connected) {
|
|
197
|
-
socket.send(content);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
157
|
}
|
|
203
158
|
|
|
204
159
|
export namespace MessagingContribution {
|