"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketStream = exports.NodeStream = exports.BaseStream = void 0;
const buffer_1 = require("buffer");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const cancellation_1 = require("./util/cancellation");
const errors_1 = require("./errors");
function handleCancellation(reject, cancellation) {
    if (cancellation) {
        if (cancellation.isCancellationRequested) {
            reject(new cancellation_1.CancellationError());
        }
        else {
            cancellation.onCancellationRequested(() => {
                reject(new cancellation_1.CancellationError());
            });
        }
    }
}
/**
 * Base class for stream adapters.
 */
class BaseStream {
    constructor() {
        this.incomingData = [];
        this.pendingReads = [];
        this.error = null;
        this.disposed = false;
        this.closedEmitter = new vscode_jsonrpc_1.Emitter();
        this.closed = this.closedEmitter.event;
    }
    onData(data) {
        while (this.pendingReads.length > 0) {
            const read = this.pendingReads.shift();
            if (read.count >= data.length) {
                // This read operation consumes all of the incoming data.
                read.resolve(data);
                return;
            }
            else {
                // This read operation consumes part of the incoming data.
                const readData = data.slice(0, read.count);
                data = data.slice(read.count);
                read.resolve(readData);
            }
        }
        this.incomingData.push(data);
    }
    onEnd() {
        while (this.pendingReads.length > 0) {
            const read = this.pendingReads.shift();
            read.resolve(null);
        }
        this.incomingData.push(buffer_1.Buffer.alloc(0));
    }
    onError(error) {
        if (!this.error) {
            this.error = error;
        }
        while (this.pendingReads.length > 0) {
            const read = this.pendingReads.shift();
            read.reject(error);
        }
    }
    async read(count, cancellation) {
        if (this.disposed)
            throw new errors_1.ObjectDisposedError(this);
        if (this.incomingData.length > 0) {
            // Consume data that has already arrived.
            let data = this.incomingData[0];
            if (data.length === 0) {
                // Reached the end of the stream.
                return null;
            }
            else if (count >= data.length) {
                // Consuming the whole chunk.
                this.incomingData.shift();
            }
            else {
                // Consuming part of the chunk.
                this.incomingData[0] = data.slice(count);
                data = data.slice(0, count);
            }
            return data;
        }
        else if (this.error) {
            throw this.error;
        }
        else {
            // Wait for more data to arrive.
            return await new Promise((resolve, reject) => {
                if (cancellation) {
                    if (cancellation.isCancellationRequested) {
                        reject(new cancellation_1.CancellationError());
                        return;
                    }
                    cancellation.onCancellationRequested(() => {
                        // Discard any pending reads that use this cancellation token.
                        for (let i = 0; i < this.pendingReads.length; i++) {
                            if (Object.is(cancellation, this.pendingReads[i].cancellation)) {
                                const read = this.pendingReads.splice(i--, 1)[0];
                                read.reject(new cancellation_1.CancellationError());
                            }
                        }
                    });
                }
                this.pendingReads.push({ count, resolve, reject, cancellation });
            });
        }
    }
    dispose() {
        if (!this.disposed) {
            this.disposed = true;
            const error = new errors_1.ObjectDisposedError(this);
            this.onError(error);
            this.fireOnClose(error);
        }
    }
    fireOnClose(error) {
        this.closedEmitter.fire({ error });
    }
    get isDisposed() {
        return this.disposed;
    }
}
exports.BaseStream = BaseStream;
/**
 * Stream adapter for a Node.js Socket, Duplex stream, or Readable/Writable stream pair.
 */
class NodeStream extends BaseStream {
    constructor(duplexOrReadStream, writeStream) {
        super();
        if (!duplexOrReadStream)
            throw new TypeError('Duplex or Readable/Writable stream are required.');
        this.readStream = duplexOrReadStream;
        this.writeStream = writeStream || duplexOrReadStream;
        this.readStream.on('data', this.onData.bind(this));
        this.readStream.on('end', this.onEnd.bind(this));
        this.readStream.on('error', this.onError.bind(this));
        this.readStream.on('close', () => {
            this.onEnd();
            this.fireOnClose();
        });
    }
    async write(data, cancellation) {
        if (!data)
            throw new TypeError('Data is required.');
        if (this.disposed)
            throw new errors_1.ObjectDisposedError(this);
        return new Promise((resolve, reject) => {
            handleCancellation(reject, cancellation);
            this.writeStream.write(data, (err) => {
                if (!err) {
                    resolve();
                }
                else {
                    reject(err);
                }
            });
        });
    }
    async close(error, cancellation) {
        if (this.disposed)
            throw new errors_1.ObjectDisposedError(this);
        await new Promise((resolve, reject) => {
            handleCancellation(reject, cancellation);
            this.writeStream.end(resolve);
        });
        this.disposed = true;
        this.onError(error || new errors_1.ObjectDisposedError(this));
        this.closedEmitter.fire({ error });
    }
    dispose() {
        if (!this.disposed) {
            const error = new errors_1.ObjectDisposedError(this);
            this.readStream.destroy(error);
            this.writeStream.destroy(error);
        }
        super.dispose();
    }
}
exports.NodeStream = NodeStream;
/**
 * Stream adapter for a browser websocket.
 */
class WebSocketStream extends BaseStream {
    constructor(websocket) {
        super();
        this.websocket = websocket;
        if (!websocket)
            throw new TypeError('WebSocket is required.');
        if (typeof websocket.binaryType === 'string' &&
            websocket.binaryType !== 'arraybuffer') {
            throw new Error('WebSocket must use arraybuffer binary type.');
        }
        websocket.onmessage = (e) => {
            this.onData(buffer_1.Buffer.from(e.data));
        };
        websocket.onclose = (e) => {
            if (e.wasClean) {
                this.onEnd();
            }
            else {
                const error = new Error(e.reason);
                error.code = e.code;
                this.onError(error);
            }
        };
    }
    get protocol() {
        return this.websocket.protocol;
    }
    async write(data, cancellation) {
        if (!data)
            throw new TypeError('Data is required.');
        if (this.disposed)
            throw new errors_1.ObjectDisposedError(this);
        if ('readyState' in this.websocket) {
            if (this.websocket.readyState === 2 /* WebSocketReadyState.Closing */ ||
                this.websocket.readyState === 3 /* WebSocketReadyState.Closed */) {
                throw new DOMException('WebSocket is already in CLOSING or CLOSED state.', 'InvalidStateError');
            }
        }
        this.websocket.send(data);
    }
    async close(error, cancellation) {
        if (this.disposed)
            throw new errors_1.ObjectDisposedError(this);
        if (!error) {
            this.websocket.close();
        }
        else {
            const code = typeof error.code === 'number' ? error.code : undefined;
            this.websocket.close(code, error.message);
        }
        this.disposed = true;
        this.closedEmitter.fire({ error });
        this.onError(error || new Error('Stream closed.'));
    }
    dispose() {
        if (!this.disposed) {
            this.websocket.close();
        }
        super.dispose();
    }
}
exports.WebSocketStream = WebSocketStream;
//# sourceMappingURL=https://github.com/VSCodium/sourcemaps/releases/download/stable-221e0a382c0be3a673a4e4cab0601344a0b3de3a/node_modules-@microsoft-dev-tunnels-ssh-streams.js.map