"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Holder_1 = require("../../../../../Class/Holder");
const http_1 = require("../../../../../client/platform/web/api/http");
const intercept_1 = require("../../../../../client/platform/web/api/intercept");
const APINotImplementedError_1 = require("../../../../../exception/APINotImplementedError");
const NOOP_1 = require("../../../../../NOOP");
const WebSocket_1 = require("../../../../../wrap/WebSocket");
const _ids_1 = require("../../../../_ids");
class WebSocket_ extends Holder_1.Holder {
    constructor(system) {
        super({
            fi: ['url'],
            fo: ['channel'],
            i: [],
            o: [],
        }, {
            output: {
                channel: {
                    ref: true,
                },
            },
        }, system, _ids_1.ID_WEB_SOCKET, 'close');
        this._webSocket = null;
    }
    async f({ url }, done, fail) {
        let { api: { window: { WebSocket }, http: { fetch }, window: { nextTick }, }, cache: { ws, wss, interceptors }, } = this.__system;
        if (!WebSocket) {
            fail((0, APINotImplementedError_1.apiNotSupportedError)('WebSocket'));
            return;
        }
        const willIntercept = interceptors.some((interceptor) => (0, intercept_1.intercept)(interceptor.opt, url));
        const upgrade = async () => {
            return await fetch(url, {
                method: 'GET',
                headers: {
                    Upgrade: 'websocket',
                    Connection: 'upgrade',
                },
            }, interceptors);
        };
        if (willIntercept) {
            let response;
            try {
                response = await upgrade();
            }
            catch (err) {
                fail('could not connect');
                return;
            }
            const redirectStatusCodes = [301, 302, 303, 307, 308];
            if (redirectStatusCodes.includes(response.status)) {
                nextTick(() => {
                    if (this._webSocket) {
                        this._webSocket.readyState = WebSocket.CLOSED;
                        channel.emit('close', { code: 0, reason: '' });
                    }
                });
                return;
            }
            if (response.ok) {
                const internalId = response.headers.get(http_1.CUSTOM_HEADER_X_WEBSOCKET_ID);
                const webSocket = {
                    readyState: WebSocket.CLOSED,
                    send: function (data) {
                        const server = wss[internalId];
                        server.onmessage(data);
                    },
                    close: function (code, reason) {
                        channel.emit('close', { code, reason });
                        const server = wss[internalId];
                        server.onclose(code, reason);
                    },
                    onopen: function (evevnt) {
                        channel.emit('open', {});
                    },
                    onmessage: function (event) {
                        const { data } = event;
                        channel.emit('message', data);
                    },
                    onerror: function (event) {
                        channel.emit('error', '');
                    },
                    onclose: function (event) {
                        //
                    },
                };
                this._webSocket = webSocket;
                nextTick(() => {
                    webSocket.readyState = WebSocket.OPEN;
                    channel.emit('open', {});
                });
                ws[internalId] = this._webSocket;
            }
            else {
                fail('failed to connect');
                return;
            }
        }
        else {
            try {
                this._webSocket = new WebSocket(url);
                this._webSocket.onopen = () => {
                    channel.emit('open', {});
                };
                this._webSocket.onmessage = (message) => {
                    channel.emit('message', message.data);
                };
                this._webSocket.onerror = (event) => {
                    channel.emit('error', '');
                };
                this._webSocket.onclose = (event) => {
                    const { code, reason } = event;
                    channel.emit('close', { code, reason });
                };
            }
            catch (err) {
                if (err.message ===
                    "Failed to construct 'WebSocket': The URL '' is invalid.") {
                    fail('malformed url');
                }
                else if ("failed to construct 'websocket': an insecure websocket connection may not be initiated from a page loaded over https.") {
                    fail('an insecure websocket connection may not be initiated from a page loaded over https');
                }
                else {
                    fail(err.message.toLowerCase());
                }
                return;
            }
        }
        const channel = (0, WebSocket_1.wrapWebSocket)(this._webSocket, this.__system);
        done({ channel });
    }
    async d() {
        if (this._webSocket) {
            const webSocket = this._webSocket;
            this._webSocket = null;
            webSocket.onclose = NOOP_1.NOOP;
            webSocket.onerror = NOOP_1.NOOP;
            try {
                await webSocket.close();
            }
            catch (err) {
                //
            }
            webSocket.onopen = null;
            webSocket.onmessage = null;
        }
    }
}
exports.default = WebSocket_;
//# sourceMappingURL=index.js.map