/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import WebSocket from 'ws';
import { EventEmitter } from 'events';
class WebSocketTransport {
    _ws;
    onmessage;
    onclose;
    wsEndpoint;
    static async connect(url, headers = {}) {
        const transport = new WebSocketTransport(url, headers);
        await new Promise((fulfill, reject) => {
            transport._ws.addEventListener('open', async () => {
                fulfill(transport);
            });
            transport._ws.addEventListener('error', event => {
                reject(new Error('WebSocket error: ' + event.message));
                transport._ws.close();
            });
        });
        return transport;
    }
    constructor(url, headers = {}) {
        this.wsEndpoint = url;
        this._ws = new WebSocket(url, [], {
            perMessageDeflate: false,
            maxPayload: 256 * 1024 * 1024, // 256Mb,
            handshakeTimeout: 30000,
            headers
        });
        this._ws.addEventListener('message', event => {
            try {
                if (this.onmessage)
                    this.onmessage.call(null, JSON.parse(event.data.toString()));
            }
            catch (e) {
                this._ws.close();
            }
        });
        this._ws.addEventListener('close', event => {
            if (this.onclose)
                this.onclose.call(null);
        });
        // Prevent Error: read ECONNRESET.
        this._ws.addEventListener('error', () => { });
    }
    isClosed() {
        return this._ws.readyState === WebSocket.CLOSING || this._ws.readyState === WebSocket.CLOSED;
    }
    send(message) {
        this._ws.send(JSON.stringify(message));
    }
    close() {
        this._ws.close();
    }
    async closeAndWait() {
        const promise = new Promise(f => this._ws.once('close', f));
        this.close();
        await promise; // Make sure to await the actual disconnect.
    }
}
export class Backend extends EventEmitter {
    static _lastId = 0;
    _callbacks = new Map();
    _transport;
    channel;
    constructor() {
        super();
    }
    async connect(wsEndpoint) {
        this._transport = await WebSocketTransport.connect(wsEndpoint + '?debug-controller');
        this._transport.onmessage = (message) => {
            if (!message.id) {
                this.emit(message.method, message.params);
                return;
            }
            const pair = this._callbacks.get(message.id);
            if (!pair)
                return;
            this._callbacks.delete(message.id);
            if (message.error) {
                const error = new Error(message.error.error?.message || message.error.value);
                error.stack = message.error.error?.stack;
                pair.reject(error);
            }
            else {
                pair.fulfill(message.result);
            }
        };
        this.channel = new Proxy(this, {
            get: (target, propKey) => {
                if (['on', 'once'].includes(String(propKey)))
                    return target[propKey].bind(target);
                return (...args) => this._send(String(propKey), ...args);
            }
        });
    }
    async initialize() {
        await this.channel.initialize({ codegenId: 'playwright-test', sdkLanguage: 'javascript' });
    }
    async close() {
        await this._transport.closeAndWait();
    }
    _send(method, params = {}) {
        return new Promise((fulfill, reject) => {
            const id = ++Backend._lastId;
            const command = { id, guid: 'DebugController', method, params, metadata: {} };
            this._transport.send(command);
            this._callbacks.set(id, { fulfill, reject });
        });
    }
}
//# sourceMappingURL=debugControllerBackend.js.map