{"version":3,"file":"graceful-ws.min.mjs","sources":["../src/index.ts"],"sourcesContent":["import {Options, WebsocketSettings} from './types';\n\nexport default class GracefulWebSocket {\n\n    // Version\n    public static readonly version = VERSION;\n\n    /**\n     * I could extend / use EventTarget because it's best practice and made for\n     * exactly this purpose. ES6 classes are nearly 5 years old so the extend\n     * syntax + super() should work right? Nope, Safari is insanely stupid and\n     * doesn't even follow the very basic RFC's regarding extending EventTarget.\n     * Just because of this single browser doing whatever it wants and giving a shit about developers\n     * (not everyone wants to buy a god damn mac) we have to take care about event-handling by ourself.\n     * See https://stackoverflow.com/questions/36675693/eventtarget-interface-in-safari?noredirect=1&lq=1\n     */\n    /* eslint-disable no-invalid-this */\n    private readonly _eventProxy = document.createElement('div');\n    public addEventListener: WebSocket['addEventListener'] = this._eventProxy.addEventListener.bind(this._eventProxy);\n    public dispatchEvent: WebSocket['dispatchEvent'] = this._eventProxy.dispatchEvent.bind(this._eventProxy);\n    public removeEventListener: WebSocket['removeEventListener'] = this._eventProxy.removeEventListener.bind(this._eventProxy);\n\n    // Default options\n    private readonly _options: Options = {\n        ws: {\n            protocols: [],\n            url: ''\n        },\n        pingInterval: 5000,\n        pingTimeout: 2500,\n        retryInterval: 1000,\n        com: {\n            message: '__PING__',\n            answer: '__PONG__'\n        }\n    };\n\n    // Instance stuff\n    private _closed = false;\n    private _websocket: WebSocket | null = null;\n    private _connected = false;\n\n    // Timing id's\n    private _disconnectionTimeoutId = 0;\n    private _pingingTimeoutId = 0;\n    private _retryIntervalId = 0;\n\n    constructor(\n        url: (Partial<Options> & {ws: WebsocketSettings}) | string,\n        ...protocols: Array<string>\n    ) {\n        const {_options} = this;\n        if (typeof url === 'string') {\n            _options.ws = {\n                url,\n                protocols\n            };\n        } else {\n            Object.assign(_options, url);\n        }\n\n        if (!_options.ws || !_options.ws.url) {\n            throw new Error('You must provide at least a websocket url.');\n        }\n\n        this._websocket = null;\n        this.start();\n    }\n\n    get pingInterval(): number {\n        return this._options.pingInterval;\n    }\n\n    set pingInterval(value: number) {\n        this._options.pingInterval = value;\n    }\n\n    get pingTimeout(): number {\n        return this._options.pingTimeout;\n    }\n\n    set pingTimeout(value: number) {\n        this._options.pingTimeout = value;\n    }\n\n    get retryInterval(): number {\n        return this._options.retryInterval;\n    }\n\n    set retryInterval(value: number) {\n        this._options.retryInterval = value;\n    }\n\n    // Websocket properties (https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)\n    get binaryType(): BinaryType | null {\n        return this._websocket ? this._websocket.binaryType : null;\n    }\n\n    get bufferedAmount(): number | null {\n        return this._websocket ? this._websocket.bufferedAmount : null;\n    }\n\n    get extensions(): string | null {\n        return this._websocket ? this._websocket.extensions : null;\n    }\n\n    get protocol(): string | null {\n        return this._websocket ? this._websocket.protocol : null;\n    }\n\n    get readyState(): number | null {\n        return this._websocket ? this._websocket.readyState : null;\n    }\n\n    get url(): string | null {\n        return this._websocket ? this._websocket.url : null;\n    }\n\n    // Custom properties\n    get connected(): boolean {\n        return this._connected;\n    }\n\n    public send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {\n        const {_websocket} = this;\n\n        if (_websocket) {\n            _websocket.send(data);\n        } else {\n            throw new Error('Websocket isn\\'t created yet.');\n        }\n    }\n\n    public close(code?: number, reason?: string): void {\n        const {_websocket} = this;\n\n        if (this._closed) {\n            throw new Error('Websocket already closed.');\n        } else if (_websocket) {\n            this._closed = true;\n\n            // Clear retry-interval if currently in a pending state\n            clearInterval(this._retryIntervalId);\n\n            // Close websocket\n            _websocket.close(code, reason);\n\n            // Dispatch close event\n            this.dispatchEvent(new CustomEvent('killed'));\n        } else {\n            throw new Error('Websocket isn\\'t created yet.');\n        }\n    }\n\n    private start(): void {\n        const {com, pingInterval, pingTimeout, ws: {url, protocols}} = this._options;\n        const ws = this._websocket = new WebSocket(url, protocols || []);\n\n        ws.addEventListener('open', () => {\n\n            // Update connection state and dispatch event\n            this._connected = true;\n            this.dispatchEvent(new CustomEvent('connected'));\n\n            // Ping every 5s\n            this._pingingTimeoutId = setInterval(() => {\n                ws.send(com.message);\n\n                this._disconnectionTimeoutId = setTimeout(() => {\n                    ws.close();\n                }, pingTimeout) as unknown as number;\n            }, pingInterval) as unknown as number;\n        });\n\n        ws.addEventListener('message', e => {\n\n            // Check if message is the answer of __ping__ stop propagation if so\n            if (e.data === com.answer) {\n                clearTimeout(this._disconnectionTimeoutId);\n            } else {\n                this.dispatchEvent(new MessageEvent('message', e as EventInit));\n            }\n        });\n\n        ws.addEventListener('close', () => {\n\n            // Clear timeouts\n            clearTimeout(this._disconnectionTimeoutId);\n            clearInterval(this._pingingTimeoutId);\n\n            // Restart if not manually closed\n            if (!this._closed) {\n                this.restart();\n            }\n        });\n    }\n\n    private restart(): void {\n        const wasConnected = this._connected;\n        this._connected = false;\n\n        // Dispatch custom event if it was connected previously\n        if (wasConnected) {\n            this.dispatchEvent(new CustomEvent('disconnected'));\n        }\n\n        // Check every second if ethernet is available\n        this._retryIntervalId = setInterval(() => {\n            if (navigator.onLine) {\n                clearInterval(this._retryIntervalId);\n                this.start();\n            }\n        }, this._options.retryInterval) as unknown as number;\n    }\n}\n"],"names":["GracefulWebSocket","[object Object]","url","protocols","this","document","createElement","_eventProxy","addEventListener","bind","dispatchEvent","removeEventListener","ws","pingInterval","pingTimeout","retryInterval","com","message","answer","i","_options","Object","assign","Error","_websocket","start","value","binaryType","bufferedAmount","extensions","protocol","readyState","connected","_connected","data","o","send","code","reason","_closed","clearInterval","_retryIntervalId","close","CustomEvent","WebSocket","_pingingTimeoutId","setInterval","_disconnectionTimeoutId","setTimeout","e","clearTimeout","MessageEvent","restart","wasConnected","navigator","onLine"],"mappings":"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"}