mirror of
https://github.com/coder/code-server.git
synced 2026-05-12 23:37:25 +02:00
chore(vscode): update to 1.53.2
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
This commit is contained in:
22
lib/vscode/src/vs/base/parts/ipc/browser/ipc.mp.ts
Normal file
22
lib/vscode/src/vs/base/parts/ipc/browser/ipc.mp.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
|
||||
|
||||
/**
|
||||
* An implementation of a `IPCClient` on top of DOM `MessagePort`.
|
||||
*/
|
||||
export class Client extends MessagePortClient implements IDisposable {
|
||||
|
||||
/**
|
||||
* @param clientId a way to uniquely identify this client among
|
||||
* other clients. this is important for routing because every
|
||||
* client can also be a server
|
||||
*/
|
||||
constructor(port: MessagePort, clientId: string) {
|
||||
super(port, clientId);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,11 @@ export interface Sender {
|
||||
send(channel: string, msg: unknown): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Electron `Protocol` leverages Electron style IPC communication (`ipcRenderer`, `ipcMain`)
|
||||
* for the implementation of the `IMessagePassingProtocol`. That style of API requires a channel
|
||||
* name for sending data.
|
||||
*/
|
||||
export class Protocol implements IMessagePassingProtocol {
|
||||
|
||||
constructor(private sender: Sender, readonly onMessage: Event<VSBuffer>) { }
|
||||
@@ -23,7 +28,7 @@ export class Protocol implements IMessagePassingProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
disconnect(): void {
|
||||
this.sender.send('vscode:disconnect', null);
|
||||
}
|
||||
}
|
||||
|
||||
78
lib/vscode/src/vs/base/parts/ipc/common/ipc.mp.ts
Normal file
78
lib/vscode/src/vs/base/parts/ipc/common/ipc.mp.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
/**
|
||||
* Declare minimal `MessageEvent` and `MessagePort` interfaces here
|
||||
* so that this utility can be used both from `browser` and
|
||||
* `electron-main` namespace where message ports are available.
|
||||
*/
|
||||
|
||||
export interface MessageEvent {
|
||||
|
||||
/**
|
||||
* For our use we only consider `Uint8Array` a valid data transfer
|
||||
* via message ports because our protocol implementation is buffer based.
|
||||
*/
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
export interface MessagePort {
|
||||
|
||||
addEventListener(type: 'message', listener: (this: MessagePort, e: MessageEvent) => unknown): void;
|
||||
removeEventListener(type: 'message', listener: (this: MessagePort, e: MessageEvent) => unknown): void;
|
||||
|
||||
postMessage(message: Uint8Array): void;
|
||||
|
||||
start(): void;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The MessagePort `Protocol` leverages MessagePort style IPC communication
|
||||
* for the implementation of the `IMessagePassingProtocol`. That style of API
|
||||
* is a simple `onmessage` / `postMessage` pattern.
|
||||
*/
|
||||
export class Protocol implements IMessagePassingProtocol {
|
||||
|
||||
readonly onMessage = Event.fromDOMEventEmitter<VSBuffer>(this.port, 'message', (e: MessageEvent) => VSBuffer.wrap(e.data));
|
||||
|
||||
constructor(private port: MessagePort) {
|
||||
|
||||
// we must call start() to ensure messages are flowing
|
||||
port.start();
|
||||
}
|
||||
|
||||
send(message: VSBuffer): void {
|
||||
this.port.postMessage(message.buffer);
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.port.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a `IPCClient` on top of MessagePort style IPC communication.
|
||||
*/
|
||||
export class Client extends IPCClient implements IDisposable {
|
||||
|
||||
private protocol: Protocol;
|
||||
|
||||
constructor(port: MessagePort, clientId: string) {
|
||||
const protocol = new Protocol(port);
|
||||
super(protocol, clientId);
|
||||
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.protocol.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -365,8 +365,8 @@ export class Protocol extends Disposable implements IMessagePassingProtocol {
|
||||
private readonly _onMessage = new Emitter<VSBuffer>();
|
||||
readonly onMessage: Event<VSBuffer> = this._onMessage.event;
|
||||
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
readonly onClose: Event<void> = this._onClose.event;
|
||||
private readonly _onDidDispose = new Emitter<void>();
|
||||
readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
||||
|
||||
constructor(socket: ISocket) {
|
||||
super();
|
||||
@@ -380,7 +380,7 @@ export class Protocol extends Disposable implements IMessagePassingProtocol {
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._socket.onClose(() => this._onClose.fire()));
|
||||
this._register(this._socket.onClose(() => this._onDidDispose.fire()));
|
||||
}
|
||||
|
||||
drain(): Promise<void> {
|
||||
@@ -406,7 +406,7 @@ export class Client<TContext = string> extends IPCClient<TContext> {
|
||||
return new Client(new Protocol(socket), id);
|
||||
}
|
||||
|
||||
get onClose(): Event<void> { return this.protocol.onClose; }
|
||||
get onDidDispose(): Event<void> { return this.protocol.onDidDispose; }
|
||||
|
||||
constructor(private protocol: Protocol | PersistentProtocol, id: TContext, ipcLogger: IIPCLogger | null = null) {
|
||||
super(protocol, id, ipcLogger);
|
||||
@@ -621,8 +621,8 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
private readonly _onMessage = new BufferedEmitter<VSBuffer>();
|
||||
readonly onMessage: Event<VSBuffer> = this._onMessage.event;
|
||||
|
||||
private readonly _onClose = new BufferedEmitter<void>();
|
||||
readonly onClose: Event<void> = this._onClose.event;
|
||||
private readonly _onDidDispose = new BufferedEmitter<void>();
|
||||
readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
||||
|
||||
private readonly _onSocketClose = new BufferedEmitter<void>();
|
||||
readonly onSocketClose: Event<void> = this._onSocketClose.event;
|
||||
@@ -747,6 +747,10 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
return this._socket;
|
||||
}
|
||||
|
||||
public getMillisSinceLastIncomingData(): number {
|
||||
return Date.now() - this._socketReader.lastReadTime;
|
||||
}
|
||||
|
||||
public beginAcceptReconnection(socket: ISocket, initialDataChunk: VSBuffer | null): void {
|
||||
this._isReconnecting = true;
|
||||
|
||||
@@ -783,7 +787,7 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
}
|
||||
|
||||
public acceptDisconnect(): void {
|
||||
this._onClose.fire();
|
||||
this._onDidDispose.fire();
|
||||
}
|
||||
|
||||
private _receiveMessage(msg: ProtocolMessage): void {
|
||||
@@ -820,7 +824,7 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
} else if (msg.type === ProtocolMessageType.Control) {
|
||||
this._onControlMessage.fire(msg.data);
|
||||
} else if (msg.type === ProtocolMessageType.Disconnect) {
|
||||
this._onClose.fire();
|
||||
this._onDidDispose.fire();
|
||||
} else if (msg.type === ProtocolMessageType.ReplayRequest) {
|
||||
// Send again all unacknowledged messages
|
||||
const toSend = this._outgoingUnackMsg.toArray();
|
||||
|
||||
@@ -505,6 +505,7 @@ export interface IIPCLogger {
|
||||
|
||||
export class ChannelClient implements IChannelClient, IDisposable {
|
||||
|
||||
private isDisposed: boolean = false;
|
||||
private state: State = State.Uninitialized;
|
||||
private activeRequests = new Set<IDisposable>();
|
||||
private handlers = new Map<number, IHandler>();
|
||||
@@ -525,9 +526,15 @@ export class ChannelClient implements IChannelClient, IDisposable {
|
||||
|
||||
return {
|
||||
call(command: string, arg?: any, cancellationToken?: CancellationToken) {
|
||||
if (that.isDisposed) {
|
||||
return Promise.reject(errors.canceled());
|
||||
}
|
||||
return that.requestPromise(channelName, command, arg, cancellationToken);
|
||||
},
|
||||
listen(event: string, arg: any) {
|
||||
if (that.isDisposed) {
|
||||
return Promise.reject(errors.canceled());
|
||||
}
|
||||
return that.requestEvent(channelName, event, arg);
|
||||
}
|
||||
} as T;
|
||||
@@ -725,6 +732,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.isDisposed = true;
|
||||
if (this.protocolListener) {
|
||||
this.protocolListener.dispose();
|
||||
this.protocolListener = null;
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcMain, WebContents } from 'electron';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IPCServer, ClientConnectionEvent } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { ipcMain, WebContents } from 'electron';
|
||||
import { Protocol as ElectronProtocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
@@ -18,9 +18,13 @@ interface IIPCEvent {
|
||||
function createScopedOnMessageEvent(senderId: number, eventName: string): Event<VSBuffer | null> {
|
||||
const onMessage = Event.fromNodeEventEmitter<IIPCEvent>(ipcMain, eventName, (event, message) => ({ event, message }));
|
||||
const onMessageFromSender = Event.filter(onMessage, ({ event }) => event.sender.id === senderId);
|
||||
|
||||
return Event.map(onMessageFromSender, ({ message }) => message ? VSBuffer.wrap(message) : message);
|
||||
}
|
||||
|
||||
/**
|
||||
* An implemention of `IPCServer` on top of Electron `ipcMain` API.
|
||||
*/
|
||||
export class Server extends IPCServer {
|
||||
|
||||
private static readonly Clients = new Map<number, IDisposable>();
|
||||
@@ -41,7 +45,7 @@ export class Server extends IPCServer {
|
||||
|
||||
const onMessage = createScopedOnMessageEvent(id, 'vscode:message') as Event<VSBuffer>;
|
||||
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'vscode:disconnect')), onDidClientReconnect.event);
|
||||
const protocol = new Protocol(webContents, onMessage);
|
||||
const protocol = new ElectronProtocol(webContents, onMessage);
|
||||
|
||||
return { protocol, onDidClientDisconnect };
|
||||
});
|
||||
57
lib/vscode/src/vs/base/parts/ipc/electron-main/ipc.mp.ts
Normal file
57
lib/vscode/src/vs/base/parts/ipc/electron-main/ipc.mp.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BrowserWindow, ipcMain, IpcMainEvent, MessagePortMain } from 'electron';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
|
||||
|
||||
/**
|
||||
* An implementation of a `IPCClient` on top of Electron `MessagePortMain`.
|
||||
*/
|
||||
export class Client extends MessagePortClient implements IDisposable {
|
||||
|
||||
/**
|
||||
* @param clientId a way to uniquely identify this client among
|
||||
* other clients. this is important for routing because every
|
||||
* client can also be a server
|
||||
*/
|
||||
constructor(port: MessagePortMain, clientId: string) {
|
||||
super({
|
||||
addEventListener: (type, listener) => port.addListener(type, listener),
|
||||
removeEventListener: (type, listener) => port.removeListener(type, listener),
|
||||
postMessage: message => port.postMessage(message),
|
||||
start: () => port.start(),
|
||||
close: () => port.close()
|
||||
}, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method opens a message channel connection
|
||||
* in the target window. The target window needs
|
||||
* to use the `Server` from `electron-sandbox/ipc.mp`.
|
||||
*/
|
||||
export async function connect(window: BrowserWindow): Promise<MessagePortMain> {
|
||||
|
||||
// Assert healthy window to talk to
|
||||
if (window.isDestroyed() || window.webContents.isDestroyed()) {
|
||||
throw new Error('ipc.mp#connect: Cannot talk to window because it is closed or destroyed');
|
||||
}
|
||||
|
||||
// Ask to create message channel inside the window
|
||||
// and send over a UUID to correlate the response
|
||||
const nonce = generateUuid();
|
||||
window.webContents.send('vscode:createMessageChannel', nonce);
|
||||
|
||||
// Wait until the window has returned the `MessagePort`
|
||||
// We need to filter by the `nonce` to ensure we listen
|
||||
// to the right response.
|
||||
const onMessageChannelResult = Event.fromNodeEventEmitter<{ nonce: string, port: MessagePortMain }>(ipcMain, 'vscode:createMessageChannelResult', (e: IpcMainEvent, nonce: string) => ({ nonce, port: e.ports[0] }));
|
||||
const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce)));
|
||||
|
||||
return port;
|
||||
}
|
||||
@@ -5,28 +5,34 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { Protocol as ElectronProtocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
|
||||
/**
|
||||
* An implemention of `IPCClient` on top of Electron `ipcRenderer` IPC communication
|
||||
* provided from sandbox globals (via preload script).
|
||||
*/
|
||||
export class Client extends IPCClient implements IDisposable {
|
||||
|
||||
private protocol: Protocol;
|
||||
private protocol: ElectronProtocol;
|
||||
|
||||
private static createProtocol(): Protocol {
|
||||
private static createProtocol(): ElectronProtocol {
|
||||
const onMessage = Event.fromNodeEventEmitter<VSBuffer>(ipcRenderer, 'vscode:message', (_, message) => VSBuffer.wrap(message));
|
||||
ipcRenderer.send('vscode:hello');
|
||||
return new Protocol(ipcRenderer, onMessage);
|
||||
|
||||
return new ElectronProtocol(ipcRenderer, onMessage);
|
||||
}
|
||||
|
||||
constructor(id: string) {
|
||||
const protocol = Client.createProtocol();
|
||||
super(protocol, id);
|
||||
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.protocol.dispose();
|
||||
this.protocol.disconnect();
|
||||
}
|
||||
}
|
||||
51
lib/vscode/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts
Normal file
51
lib/vscode/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol as MessagePortProtocol } from 'vs/base/parts/ipc/common/ipc.mp';
|
||||
|
||||
/**
|
||||
* An implementation of a `IPCServer` on top of MessagePort style IPC communication.
|
||||
* The clients register themselves via Electron IPC transfer.
|
||||
*/
|
||||
export class Server extends IPCServer {
|
||||
|
||||
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
|
||||
|
||||
// Clients connect via `vscode:createMessageChannel` to get a
|
||||
// `MessagePort` that is ready to be used. For every connection
|
||||
// we create a pair of message ports and send it back.
|
||||
//
|
||||
// The `nonce` is included so that the main side has a chance to
|
||||
// correlate the response back to the sender.
|
||||
const onCreateMessageChannel = Event.fromNodeEventEmitter<string>(ipcRenderer, 'vscode:createMessageChannel', (_, nonce: string) => nonce);
|
||||
|
||||
return Event.map(onCreateMessageChannel, nonce => {
|
||||
|
||||
// Create a new pair of ports and protocol for this connection
|
||||
const { port1: incomingPort, port2: outgoingPort } = new MessageChannel();
|
||||
const protocol = new MessagePortProtocol(incomingPort);
|
||||
|
||||
const result: ClientConnectionEvent = {
|
||||
protocol,
|
||||
// Not part of the standard spec, but in Electron we get a `close` event
|
||||
// when the other side closes. We can use this to detect disconnects
|
||||
// (https://github.com/electron/electron/blob/11-x-y/docs/api/message-port-main.md#event-close)
|
||||
onDidClientDisconnect: Event.fromDOMEventEmitter(incomingPort, 'close')
|
||||
};
|
||||
|
||||
// Send one port back to the requestor
|
||||
ipcRenderer.postMessage('vscode:createMessageChannelResult', nonce, [outgoingPort]);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(Server.getOnDidClientConnect());
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { createHash } from 'crypto';
|
||||
import { Socket, Server as NetServer, createConnection, createServer } from 'net';
|
||||
import * as zlib from 'zlib';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { join } from 'vs/base/common/path';
|
||||
@@ -120,24 +121,130 @@ const enum ReadState {
|
||||
export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
|
||||
public readonly socket: NodeSocket;
|
||||
public readonly permessageDeflate: boolean;
|
||||
private _totalIncomingWireBytes: number;
|
||||
private _totalIncomingDataBytes: number;
|
||||
private _totalOutgoingWireBytes: number;
|
||||
private _totalOutgoingDataBytes: number;
|
||||
private readonly _zlibInflate: zlib.InflateRaw | null;
|
||||
private readonly _zlibDeflate: zlib.DeflateRaw | null;
|
||||
private _zlibDeflateFlushWaitingCount: number;
|
||||
private readonly _onDidZlibFlush = this._register(new Emitter<void>());
|
||||
private readonly _recordInflateBytes: boolean;
|
||||
private readonly _recordedInflateBytes: Buffer[] = [];
|
||||
private readonly _pendingInflateData: Buffer[] = [];
|
||||
private readonly _pendingDeflateData: Buffer[] = [];
|
||||
private readonly _incomingData: ChunkStream;
|
||||
private readonly _onData = this._register(new Emitter<VSBuffer>());
|
||||
private readonly _onClose = this._register(new Emitter<void>());
|
||||
private _isEnded: boolean = false;
|
||||
|
||||
private readonly _state = {
|
||||
state: ReadState.PeekHeader,
|
||||
readLen: Constants.MinHeaderByteSize,
|
||||
fin: 0,
|
||||
mask: 0
|
||||
};
|
||||
|
||||
constructor(socket: NodeSocket) {
|
||||
public get totalIncomingWireBytes(): number {
|
||||
return this._totalIncomingWireBytes;
|
||||
}
|
||||
|
||||
public get totalIncomingDataBytes(): number {
|
||||
return this._totalIncomingDataBytes;
|
||||
}
|
||||
|
||||
public get totalOutgoingWireBytes(): number {
|
||||
return this._totalOutgoingWireBytes;
|
||||
}
|
||||
|
||||
public get totalOutgoingDataBytes(): number {
|
||||
return this._totalOutgoingDataBytes;
|
||||
}
|
||||
|
||||
public get recordedInflateBytes(): VSBuffer {
|
||||
if (this._recordInflateBytes) {
|
||||
return VSBuffer.wrap(Buffer.concat(this._recordedInflateBytes));
|
||||
}
|
||||
return VSBuffer.alloc(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket which can communicate using WebSocket frames.
|
||||
*
|
||||
* **NOTE**: When using the permessage-deflate WebSocket extension, if parts of inflating was done
|
||||
* in a different zlib instance, we need to pass all those bytes into zlib, otherwise the inflate
|
||||
* might hit an inflated portion referencing a distance too far back.
|
||||
*
|
||||
* @param socket The underlying socket
|
||||
* @param permessageDeflate Use the permessage-deflate WebSocket extension
|
||||
* @param inflateBytes "Seed" zlib inflate with these bytes.
|
||||
* @param recordInflateBytes Record all bytes sent to inflate
|
||||
*/
|
||||
constructor(socket: NodeSocket, permessageDeflate: boolean, inflateBytes: VSBuffer | null, recordInflateBytes: boolean) {
|
||||
super();
|
||||
this.socket = socket;
|
||||
this._totalIncomingWireBytes = 0;
|
||||
this._totalIncomingDataBytes = 0;
|
||||
this._totalOutgoingWireBytes = 0;
|
||||
this._totalOutgoingDataBytes = 0;
|
||||
this.permessageDeflate = permessageDeflate;
|
||||
this._recordInflateBytes = recordInflateBytes;
|
||||
if (permessageDeflate) {
|
||||
// See https://tools.ietf.org/html/rfc7692#page-16
|
||||
// To simplify our logic, we don't negociate the window size
|
||||
// and simply dedicate (2^15) / 32kb per web socket
|
||||
this._zlibInflate = zlib.createInflateRaw({
|
||||
windowBits: 15
|
||||
});
|
||||
this._zlibInflate.on('error', (err) => {
|
||||
// zlib errors are fatal, since we have no idea how to recover
|
||||
console.error(err);
|
||||
onUnexpectedError(err);
|
||||
this._onClose.fire();
|
||||
});
|
||||
this._zlibInflate.on('data', (data: Buffer) => {
|
||||
this._pendingInflateData.push(data);
|
||||
});
|
||||
if (inflateBytes) {
|
||||
this._zlibInflate.write(inflateBytes.buffer);
|
||||
this._zlibInflate.flush(() => {
|
||||
this._pendingInflateData.length = 0;
|
||||
});
|
||||
}
|
||||
|
||||
this._zlibDeflate = zlib.createDeflateRaw({
|
||||
windowBits: 15
|
||||
});
|
||||
this._zlibDeflate.on('error', (err) => {
|
||||
// zlib errors are fatal, since we have no idea how to recover
|
||||
console.error(err);
|
||||
onUnexpectedError(err);
|
||||
this._onClose.fire();
|
||||
});
|
||||
this._zlibDeflate.on('data', (data: Buffer) => {
|
||||
this._pendingDeflateData.push(data);
|
||||
});
|
||||
} else {
|
||||
this._zlibInflate = null;
|
||||
this._zlibDeflate = null;
|
||||
}
|
||||
this._zlibDeflateFlushWaitingCount = 0;
|
||||
this._incomingData = new ChunkStream();
|
||||
this._register(this.socket.onData(data => this._acceptChunk(data)));
|
||||
this._register(this.socket.onClose(() => this._onClose.fire()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.socket.dispose();
|
||||
if (this._zlibDeflateFlushWaitingCount > 0) {
|
||||
// Wait for any outstanding writes to finish before disposing
|
||||
this._register(this._onDidZlibFlush.event(() => {
|
||||
this.dispose();
|
||||
}));
|
||||
} else {
|
||||
this.socket.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public onData(listener: (e: VSBuffer) => void): IDisposable {
|
||||
@@ -145,7 +252,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
}
|
||||
|
||||
public onClose(listener: () => void): IDisposable {
|
||||
return this.socket.onClose(listener);
|
||||
return this._onClose.event(listener);
|
||||
}
|
||||
|
||||
public onEnd(listener: () => void): IDisposable {
|
||||
@@ -153,6 +260,36 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
}
|
||||
|
||||
public write(buffer: VSBuffer): void {
|
||||
this._totalOutgoingDataBytes += buffer.byteLength;
|
||||
|
||||
if (this._zlibDeflate) {
|
||||
this._zlibDeflate.write(<Buffer>buffer.buffer);
|
||||
|
||||
this._zlibDeflateFlushWaitingCount++;
|
||||
// See https://zlib.net/manual.html#Constants
|
||||
this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => {
|
||||
this._zlibDeflateFlushWaitingCount--;
|
||||
let data = Buffer.concat(this._pendingDeflateData);
|
||||
this._pendingDeflateData.length = 0;
|
||||
|
||||
// See https://tools.ietf.org/html/rfc7692#section-7.2.1
|
||||
data = data.slice(0, data.length - 4);
|
||||
|
||||
if (!this._isEnded) {
|
||||
// Avoid ERR_STREAM_WRITE_AFTER_END
|
||||
this._write(VSBuffer.wrap(data), true);
|
||||
}
|
||||
|
||||
if (this._zlibDeflateFlushWaitingCount === 0) {
|
||||
this._onDidZlibFlush.fire();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._write(buffer, false);
|
||||
}
|
||||
}
|
||||
|
||||
private _write(buffer: VSBuffer, compressed: boolean): void {
|
||||
let headerLen = Constants.MinHeaderByteSize;
|
||||
if (buffer.byteLength < 126) {
|
||||
headerLen += 0;
|
||||
@@ -163,7 +300,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
}
|
||||
const header = VSBuffer.alloc(headerLen);
|
||||
|
||||
header.writeUInt8(0b10000010, 0);
|
||||
if (compressed) {
|
||||
// The RSV1 bit indicates a compressed frame
|
||||
header.writeUInt8(0b11000010, 0);
|
||||
} else {
|
||||
header.writeUInt8(0b10000010, 0);
|
||||
}
|
||||
if (buffer.byteLength < 126) {
|
||||
header.writeUInt8(buffer.byteLength, 1);
|
||||
} else if (buffer.byteLength < 2 ** 16) {
|
||||
@@ -184,10 +326,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset);
|
||||
}
|
||||
|
||||
this._totalOutgoingWireBytes += header.byteLength + buffer.byteLength;
|
||||
this.socket.write(VSBuffer.concat([header, buffer]));
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this._isEnded = true;
|
||||
this.socket.end();
|
||||
}
|
||||
|
||||
@@ -195,6 +339,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
if (data.byteLength === 0) {
|
||||
return;
|
||||
}
|
||||
this._totalIncomingWireBytes += data.byteLength;
|
||||
|
||||
this._incomingData.acceptChunk(data);
|
||||
|
||||
@@ -203,14 +348,15 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
if (this._state.state === ReadState.PeekHeader) {
|
||||
// peek to see if we can read the entire header
|
||||
const peekHeader = this._incomingData.peek(this._state.readLen);
|
||||
// const firstByte = peekHeader.readUInt8(0);
|
||||
// const finBit = (firstByte & 0b10000000) >>> 7;
|
||||
const firstByte = peekHeader.readUInt8(0);
|
||||
const finBit = (firstByte & 0b10000000) >>> 7;
|
||||
const secondByte = peekHeader.readUInt8(1);
|
||||
const hasMask = (secondByte & 0b10000000) >>> 7;
|
||||
const len = (secondByte & 0b01111111);
|
||||
|
||||
this._state.state = ReadState.ReadHeader;
|
||||
this._state.readLen = Constants.MinHeaderByteSize + (hasMask ? 4 : 0) + (len === 126 ? 2 : 0) + (len === 127 ? 8 : 0);
|
||||
this._state.fin = finBit;
|
||||
this._state.mask = 0;
|
||||
|
||||
} else if (this._state.state === ReadState.ReadHeader) {
|
||||
@@ -263,13 +409,37 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
this._state.readLen = Constants.MinHeaderByteSize;
|
||||
this._state.mask = 0;
|
||||
|
||||
this._onData.fire(body);
|
||||
if (this._zlibInflate) {
|
||||
// See https://tools.ietf.org/html/rfc7692#section-7.2.2
|
||||
if (this._recordInflateBytes) {
|
||||
this._recordedInflateBytes.push(Buffer.from(<Buffer>body.buffer));
|
||||
}
|
||||
this._zlibInflate.write(<Buffer>body.buffer);
|
||||
if (this._state.fin) {
|
||||
if (this._recordInflateBytes) {
|
||||
this._recordedInflateBytes.push(Buffer.from([0x00, 0x00, 0xff, 0xff]));
|
||||
}
|
||||
this._zlibInflate.write(Buffer.from([0x00, 0x00, 0xff, 0xff]));
|
||||
}
|
||||
this._zlibInflate.flush(() => {
|
||||
const data = Buffer.concat(this._pendingInflateData);
|
||||
this._pendingInflateData.length = 0;
|
||||
this._totalIncomingDataBytes += data.length;
|
||||
this._onData.fire(VSBuffer.wrap(data));
|
||||
});
|
||||
} else {
|
||||
this._totalIncomingDataBytes += body.byteLength;
|
||||
this._onData.fire(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public drain(): Promise<void> {
|
||||
return this.socket.drain();
|
||||
public async drain(): Promise<void> {
|
||||
if (this._zlibDeflateFlushWaitingCount > 0) {
|
||||
await Event.toPromise(this._onDidZlibFlush.event);
|
||||
}
|
||||
await this.socket.drain();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
58
lib/vscode/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts
Normal file
58
lib/vscode/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp';
|
||||
|
||||
suite('IPC, MessagePorts', () => {
|
||||
|
||||
test('message passing', async () => {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
|
||||
const client1 = new MessagePortClient(port1, 'client1');
|
||||
const client2 = new MessagePortClient(port2, 'client2');
|
||||
|
||||
client1.registerChannel('client1', {
|
||||
call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
|
||||
switch (command) {
|
||||
case 'testMethodClient1': return Promise.resolve('success1');
|
||||
default: return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
},
|
||||
|
||||
listen(_: unknown, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
default: throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client2.registerChannel('client2', {
|
||||
call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
|
||||
switch (command) {
|
||||
case 'testMethodClient2': return Promise.resolve('success2');
|
||||
default: return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
},
|
||||
|
||||
listen(_: unknown, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
default: throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const channelClient1 = client2.getChannel('client1');
|
||||
assert.strictEqual(await channelClient1.call('testMethodClient1'), 'success1');
|
||||
|
||||
const channelClient2 = client1.getChannel('client2');
|
||||
assert.strictEqual(await channelClient2.call('testMethodClient2'), 'success2');
|
||||
|
||||
client1.dispose();
|
||||
client2.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp';
|
||||
|
||||
suite('IPC, MessagePorts', () => {
|
||||
|
||||
test('message port close event', async () => {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
|
||||
new MessagePortClient(port1, 'client1');
|
||||
const client2 = new MessagePortClient(port2, 'client2');
|
||||
|
||||
// This test ensures that Electron's API for the close event
|
||||
// does not break because we rely on it to dispose client
|
||||
// connections from the server.
|
||||
//
|
||||
// This event is not provided by browser MessagePort API though.
|
||||
const whenClosed = new Promise<boolean>(resolve => port1.addEventListener('close', () => resolve(true)));
|
||||
|
||||
client2.dispose();
|
||||
|
||||
assert.ok(await whenClosed);
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
function createClient(): Client {
|
||||
return new Client(getPathFromAmdModule(require, 'bootstrap-fork'), {
|
||||
serverName: 'TestServer',
|
||||
env: { AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true }
|
||||
env: { VSCODE_AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -135,13 +135,13 @@ suite('IPC, Socket Protocol', () => {
|
||||
|
||||
a.send(VSBuffer.fromString('foobarfarboo'));
|
||||
const msg1 = await bMessages.waitForOne();
|
||||
assert.equal(msg1.toString(), 'foobarfarboo');
|
||||
assert.strictEqual(msg1.toString(), 'foobarfarboo');
|
||||
|
||||
const buffer = VSBuffer.alloc(1);
|
||||
buffer.writeUInt8(123, 0);
|
||||
a.send(buffer);
|
||||
const msg2 = await bMessages.waitForOne();
|
||||
assert.equal(msg2.readUInt8(0), 123);
|
||||
assert.strictEqual(msg2.readUInt8(0), 123);
|
||||
});
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ suite('IPC, Socket Protocol', () => {
|
||||
|
||||
a.send(VSBuffer.fromString(JSON.stringify(data)));
|
||||
const msg = await bMessages.waitForOne();
|
||||
assert.deepEqual(JSON.parse(msg.toString()), data);
|
||||
assert.deepStrictEqual(JSON.parse(msg.toString()), data);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -179,49 +179,49 @@ suite('PersistentProtocol reconnection', () => {
|
||||
const bMessages = new MessageStream(b);
|
||||
|
||||
a.send(VSBuffer.fromString('a1'));
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a.unacknowledgedCount, 1);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
a.send(VSBuffer.fromString('a2'));
|
||||
assert.equal(a.unacknowledgedCount, 2);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a.unacknowledgedCount, 2);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
a.send(VSBuffer.fromString('a3'));
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
const a1 = await bMessages.waitForOne();
|
||||
assert.equal(a1.toString(), 'a1');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a1.toString(), 'a1');
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
const a2 = await bMessages.waitForOne();
|
||||
assert.equal(a2.toString(), 'a2');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a2.toString(), 'a2');
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
const a3 = await bMessages.waitForOne();
|
||||
assert.equal(a3.toString(), 'a3');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a3.toString(), 'a3');
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
b.send(VSBuffer.fromString('b1'));
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 1);
|
||||
|
||||
const b1 = await aMessages.waitForOne();
|
||||
assert.equal(b1.toString(), 'b1');
|
||||
assert.equal(a.unacknowledgedCount, 0);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
assert.strictEqual(b1.toString(), 'b1');
|
||||
assert.strictEqual(a.unacknowledgedCount, 0);
|
||||
assert.strictEqual(b.unacknowledgedCount, 1);
|
||||
|
||||
a.send(VSBuffer.fromString('a4'));
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
assert.strictEqual(a.unacknowledgedCount, 1);
|
||||
assert.strictEqual(b.unacknowledgedCount, 1);
|
||||
|
||||
const b2 = await bMessages.waitForOne();
|
||||
assert.equal(b2.toString(), 'a4');
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(b2.toString(), 'a4');
|
||||
assert.strictEqual(a.unacknowledgedCount, 1);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user