import { throttle } from './throttle.js';
// For server/client communication.
export class Socket {
    constructor(url) {
        this._sending = false;
        this._onConnect = () => { };
        this._onReceive = () => { };
        this._lastMessageReceived = Date.now();
        this._url = url;
        this._ensureConnection = throttle(5000, this._ensureConnection.bind(this));
        this._requests = [];
    }
    // Sends a message to the server.
    send(message, callback, wait) {
        if (message) {
            this._requests.push({ message, callback, wait });
            if (!this._sending) {
                this._sending = true;
                this._processRequests();
            }
        }
    }
    async _processRequests() {
        // Session not available yet so try again in a bit.
        if (!this._sessionId) {
            setTimeout(() => this._processRequests(), 100);
            return;
        }
        // All done sending for now.
        if (this._requests.length <= 0) {
            this._sending = false;
            return;
        }
        const request = this._requests.shift();
        if (!request) {
            return;
        }
        const { message, callback, wait } = request;
        // Query string only used for logging.
        const body = new URLSearchParams();
        body.append('sessionId', this._sessionId);
        body.append('message', JSON.stringify(message));
        const type = message.type;
        fetch(`${this._url}${type ? `?${encodeURIComponent(type)}` : ''}`, {
            method: 'post',
            cache: 'no-store',
            body,
            credentials: 'include',
        })
            .then(response => {
            if (!response.ok) {
                throw '';
            }
            return response.text();
        })
            .then(text => {
            if (callback) {
                callback(text ? JSON.parse(text) : null);
            }
        })
            .catch(() => {
            this._requests.unshift(request);
            this.reconnect();
        })
            .finally(() => {
            this._processRequests();
        });
        if (wait) {
            const start = +Date.now();
            while (Date.now() - start < 500) { }
        }
    }
    // Connects to the server for receiving messages.
    connect(options) {
        this._onConnect = options.onConnect || (() => { });
        this._onReceive = options.onReceive || (() => { });
        if (!this._ensureConnectionInterval) {
            this._ensureConnectionInterval = setInterval(this._ensureConnection, 100);
            this._ensureConnection();
        }
    }
    _ensureConnection() {
        if (this._receiver) {
            const state = this._receiver.readyState;
            if (state === EventSource.CONNECTING) {
                return;
            }
            else if (state === EventSource.OPEN && this._sessionId) {
                return;
            }
        }
        this._reset();
        // Connect...
        this._receiver = new EventSource(this._url);
        this._receiver.onmessage = event => {
            this._lastMessageReceived = Date.now();
            const data = JSON.parse(event.data);
            // First message along with the session ID.
            if (data._first) {
                if (typeof data.sessionId === 'string') {
                    this._sessionId = data.sessionId;
                }
                if (this._receiver) {
                    this._receiver.onerror = () => this.reconnect();
                }
                // There should be a message from the server at least
                // every 5 seconds, so if not, reconnect.
                this._messageInterval = setInterval(() => {
                    if (Date.now() > this._lastMessageReceived + 6000) {
                        this.reconnect();
                    }
                }, 1000);
                // Ping roughly every minute to prevent the server from
                // forcibly disconnecting the session.
                this._pingInterval = setInterval(() => this.send({ type: 'ping' }), 55000);
            }
            else if (!data._ping) {
                // Broadcast regular message.
                this._onReceive(data);
            }
        };
        this._onConnect();
    }
    // Disconnects from the server.
    disconnect() {
        if (this._ensureConnectionInterval) {
            clearInterval(this._ensureConnectionInterval);
            this._ensureConnectionInterval = undefined;
        }
        this._reset();
    }
    _reset() {
        this._sessionId = undefined;
        if (this._receiver) {
            this._receiver.close();
            this._receiver = undefined;
        }
        if (this._messageInterval) {
            clearInterval(this._messageInterval);
            this._messageInterval = undefined;
        }
        if (this._pingInterval) {
            clearInterval(this._pingInterval);
            this._pingInterval = undefined;
        }
    }
    // Reconnects to the server.
    reconnect() {
        this._reset();
        this._ensureConnection();
    }
}
//# sourceMappingURL=Socket.js.map