// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import { FoxgloveClient } from "@foxglove/ws-protocol";
import { Logger } from "../../utils/logger";
import { FoxgloveCommunicator } from "../communicator.js";
const logger = Logger.getLogger();
export class WsWorker extends FoxgloveCommunicator {
    #wasOpen = false;
    #alive = true;
    #reconnecting = false;
    #reconnectAttempts = 0;
    #maxReconnectAttempts = 10;
    #ws;
    #client;
    #subscriptions = new Map();
    send(message) { self.postMessage(message); }
    sendWithTransfer(message, transfer) { self.postMessage(message, transfer); }
    #createWs(url, onReconnect) {
        if (!this.#alive) {
            return;
        }
        this.#ws = new WebSocket(url, FoxgloveClient.SUPPORTED_SUBPROTOCOL);
        this.#ws.binaryType = "arraybuffer";
        const reconnect = () => {
            if (this.#alive && !this.#reconnecting) {
                this.#reconnectAttempts++;
                if (this.#reconnectAttempts > this.#maxReconnectAttempts) {
                    this.#alive = false;
                    this.send({
                        type: "error",
                        error: { message: `Failed to reconnect after ${this.#maxReconnectAttempts} attempts` }
                    });
                    return;
                }
                this.#reconnecting = true;
                onReconnect();
                setTimeout(() => {
                    this.#createWs(url, onReconnect);
                    this.#createClient();
                    this.#handleMessages();
                    this.#reconnecting = false;
                }, 1_000);
            }
        };
        this.#ws.addEventListener('error', reconnect);
        this.#ws.addEventListener('close', reconnect);
    }
    #createClient() {
        this.#client = new FoxgloveClient({ ws: this.#ws });
        this.#client.on("open", () => {
            this.#wasOpen = true;
            this.#reconnectAttempts = 0; // Reset counter on successful connection
            this.send({ type: "open" });
        });
        this.#client.on("close", () => {
            if (this.#wasOpen) {
                this.send({ type: "close" });
            }
        });
        this.#client.on("error", (error) => {
            if (this.#wasOpen) {
                this.send({ type: "error", error });
            }
        });
        this.#client.on("message", ({ subscriptionId, data }) => {
            const subscription = this.#subscriptions.get(subscriptionId);
            if (subscription) {
                this.handleGenericMessage(subscription.topic, subscription.schema, data);
            }
        });
    }
    #handleMessages() {
        this.#client.on("advertise", this.handleChannelsAdvertisement.bind(this));
        this.#client.on("unadvertise", this.handleChannelsUnadvertisement.bind(this));
        this.#client.on("advertiseServices", this.handleServicesAdvertisement.bind(this));
        this.#client.on("unadvertiseServices", this.handleServicesUnadvertisement.bind(this));
        this.#client.on("serviceCallResponse", this.handleServiceResponse.bind(this));
    }
    sendServiceCallRequest(request) {
        this.#client.sendServiceCallRequest(request);
    }
    isReady() {
        return this.#ws.readyState === this.#ws.OPEN;
    }
    ;
    constructor(wsUrl) {
        super();
        try {
            this.#createWs(wsUrl, this.handleReconnect.bind(this));
            this.#createClient();
            this.#handleMessages();
        }
        catch (error) {
            // try-catch is needed to catch `Mixed Content` errors in Chrome, where the client
            // attempts to load `ws://` from `https://`. (Safari would catch these in `ws.onerror`
            // but with `undefined` as an error.)
            this.send({
                type: "error",
                error: error ?? { message: "Insecure WebSocket connection" }
            });
        }
    }
    subscribe(topics) {
        const channels = this.getPublicChannels().filter(publicChannel => topics.includes(publicChannel.topic));
        for (const channel of channels) {
            const subscription = this.#client.subscribe(channel.id);
            this.#subscriptions.set(subscription, {
                topic: channel.topic,
                schema: channel.schemaName,
                kind: channel.kind
            });
        }
    }
    unsubscribe(topics) {
        const subscriptionIds = [...this.#subscriptions.entries()].filter(([_id, data]) => topics.includes(data.topic)).map(subscription => subscription[0]);
        for (const subscriptionId of subscriptionIds) {
            this.#subscriptions.delete(subscriptionId);
            this.#client.unsubscribe(subscriptionId);
        }
    }
    callService(args) {
        super.callService(args);
    }
    close() {
        this.#alive = false;
        this.#client.close();
        this.#ws.close();
    }
}
let wsWorker = null;
self.addEventListener('message', async (event) => {
    const { type, data } = event.data;
    if (type !== 'open' && !wsWorker) {
        logger.warn(`WS Worker Error: Requested ${type} before open`);
        return;
    }
    switch (type) {
        case "open":
            wsWorker = new WsWorker('wsUrl' in data ? data.wsUrl : '');
            break;
        case "close":
            wsWorker.close();
            break;
        case "call_service":
            wsWorker.callService(data);
            break;
        case 'subscribe':
            wsWorker.subscribe(data);
            break;
        case 'unsubscribe':
            wsWorker.unsubscribe(data);
            break;
    }
});
//# sourceMappingURL=worker.js.map