// Mimic https://github.com/streamlit/streamlit/blob/1.27.0/frontend/app/src/connection/ConnectionManager.ts
// and WebsocketConnection.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { BackMsg, ForwardMsg } from "@streamlit/lib/src/proto";
import { ensureError } from "@streamlit/lib/src/util/ErrorHandling";
import { DUMMY_BASE_HOSTNAME, DUMMY_BASE_PORT } from "../../consts";
import { ConnectionState } from "@streamlit/app/src/connection/ConnectionState";
/**
 * Manages our connection to the Server.
 */
export class ConnectionManager {
    constructor(props) {
        this.connectionState = ConnectionState.INITIAL;
        /**
         * To guarantee packet transmission order, this is the index of the last
         * dispatched incoming message.
         */
        this.lastDispatchedMessageIndex = -1;
        /**
         * And this is the index of the next message we receive.
         */
        this.nextMessageIndex = 0;
        /**
         * This dictionary stores received messages that we haven't sent out yet
         * (because we're still decoding previous messages)
         */
        this.messageQueue = {};
        this.setConnectionState = (connectionState, errMsg) => {
            if (this.connectionState !== connectionState) {
                this.connectionState = connectionState;
                this.props.connectionStateChanged(connectionState);
            }
            if (errMsg || connectionState === ConnectionState.DISCONNECTED_FOREVER) {
                this.props.onConnectionError(errMsg || "unknown");
            }
        };
        this.props = props;
        this.props.kernel.onWebSocketMessage((payload) => {
            if (typeof payload === "string") {
                console.error("Unexpected payload type.");
                return;
            }
            this.handleMessage(payload);
        });
        this.props.kernel.loaded.then(() => {
            console.log("The kernel has been loaded. Start connecting.");
            this.props.onHostConfigResp(this.props.kernel.hostConfigResponse);
            this.connect();
        });
    }
    /**
     * Indicates whether we're connected to the server.
     */
    isConnected() {
        return this.connectionState === ConnectionState.CONNECTED;
    }
    /**
     * Return the BaseUriParts for the server we're connected to,
     * if we are connected to a server.
     */
    getBaseUriParts() {
        if (this.connectionState === ConnectionState.CONNECTED) {
            // The existence of this property became necessary for multi-page apps: https://github.com/streamlit/streamlit/pull/4698/files#diff-e56cb91573ddb6a97ecd071925fe26504bb5a65f921dc64c63e534162950e1ebR967-R975
            // so here a dummy BaseUriParts object is returned.
            // The host and port are set as dummy values that are invalid as a URL
            // in order to avoid unexpected accesses to external resources,
            // while the basePath is representing the actual info.
            return {
                host: DUMMY_BASE_HOSTNAME,
                port: DUMMY_BASE_PORT,
                // When a new session starts, a page name for multi-page apps (a relative path to the app root url) is calculated based on this `basePath`
                // then a `rerunScript` BackMsg is sent to the server with `pageName` (https://github.com/streamlit/streamlit/blob/ace58bfa3582d4f8e7f281b4dbd266ddd8a32b54/frontend/src/App.tsx#L1064)
                // and `window.history.pushState` is called (https://github.com/streamlit/streamlit/blob/ace58bfa3582d4f8e7f281b4dbd266ddd8a32b54/frontend/src/App.tsx#L665).
                basePath: this.props.kernel.basePath,
            };
        }
        return undefined;
    }
    sendMessage(obj) {
        if (this.isConnected()) {
            this.props.kernel.sendWebSocketMessage(BackMsg.encode(BackMsg.create(obj)).finish());
        }
        else {
            // Don't need to make a big deal out of this. Just print to console.
            console.error(`Cannot send message when server is disconnected: ${obj}`);
        }
    }
    /**
     * No-op in stlite.
     */
    incrementMessageCacheRunCount() {
        // no-op.
        // Because caching is disabled in stlite. See https://github.com/whitphx/stlite/issues/495
    }
    /**
     * No-op in stlite.
     */
    disconnect() {
        // no-op.
    }
    handleMessage(data) {
        return __awaiter(this, void 0, void 0, function* () {
            // Assign this message an index.
            const messageIndex = this.nextMessageIndex;
            this.nextMessageIndex += 1;
            const encodedMsg = new Uint8Array(data);
            const msg = ForwardMsg.decode(encodedMsg);
            // stlite doesn't handle caches.
            if (msg.type === "refHash") {
                throw new Error(`Unexpected cache reference message.`);
            }
            this.messageQueue[messageIndex] = msg;
            // Dispatch any pending messages in the queue. This may *not* result
            // in our just-decoded message being dispatched: if there are other
            // messages that were received earlier than this one but are being
            // downloaded, our message won't be sent until they're done.
            while (this.lastDispatchedMessageIndex + 1 in this.messageQueue) {
                const dispatchMessageIndex = this.lastDispatchedMessageIndex + 1;
                this.props.onMessage(this.messageQueue[dispatchMessageIndex]);
                delete this.messageQueue[dispatchMessageIndex];
                this.lastDispatchedMessageIndex = dispatchMessageIndex;
            }
        });
    }
    connect() {
        return __awaiter(this, void 0, void 0, function* () {
            const WEBSOCKET_STREAM_PATH = "_stcore/stream"; // The original is defined in streamlit/frontend/src/lib/WebsocketConnection.tsx
            try {
                yield this.props.kernel.connectWebSocket("/" + WEBSOCKET_STREAM_PATH);
                this.setConnectionState(ConnectionState.CONNECTED);
            }
            catch (e) {
                const err = ensureError(e);
                console.error(err.message);
                this.setConnectionState(ConnectionState.DISCONNECTED_FOREVER, err.message);
            }
        });
    }
}
//# sourceMappingURL=ConnectionManager.js.map