const { Parser, PROTOCOL_6, PROTOCOL_7 } = require('./protocol'); const VERSION = process.env.npm_package_version; class Connector { constructor (options, WebSocket, Timer, handlers) { this.options = options; this.WebSocket = WebSocket; this.Timer = Timer; this.handlers = handlers; const path = this.options.path ? `${this.options.path}` : 'livereload'; this._uri = `ws${this.options.https ? 's' : ''}://${this.options.host}:${this.options.port}/${path}`; this._nextDelay = this.options.mindelay; this._connectionDesired = false; this.protocol = 0; this.protocolParser = new Parser({ connected: protocol => { this.protocol = protocol; this._handshakeTimeout.stop(); this._nextDelay = this.options.mindelay; this._disconnectionReason = 'broken'; return this.handlers.connected(this.protocol); }, error: e => { this.handlers.error(e); return this._closeOnError(); }, message: message => { return this.handlers.message(message); } }); this._handshakeTimeout = new this.Timer(() => { if (!this._isSocketConnected()) { return; } this._disconnectionReason = 'handshake-timeout'; return this.socket.close(); }); this._reconnectTimer = new this.Timer(() => { if (!this._connectionDesired) { // shouldn't hit this, but just in case return; } return this.connect(); }); this.connect(); } _isSocketConnected () { return this.socket && (this.socket.readyState === this.WebSocket.OPEN); } connect () { this._connectionDesired = true; if (this._isSocketConnected()) { return; } // prepare for a new connection this._reconnectTimer.stop(); this._disconnectionReason = 'cannot-connect'; this.protocolParser.reset(); this.handlers.connecting(); this.socket = new this.WebSocket(this._uri); this.socket.onopen = e => this._onopen(e); this.socket.onclose = e => this._onclose(e); this.socket.onmessage = e => this._onmessage(e); this.socket.onerror = e => this._onerror(e); } disconnect () { this._connectionDesired = false; this._reconnectTimer.stop(); // in case it was running if (!this._isSocketConnected()) { return; } this._disconnectionReason = 'manual'; return this.socket.close(); } _scheduleReconnection () { if (!this._connectionDesired) { // don't reconnect after manual disconnection return; } if (!this._reconnectTimer.running) { this._reconnectTimer.start(this._nextDelay); this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2); } } sendCommand (command) { if (!this.protocol) { return; } return this._sendCommand(command); } _sendCommand (command) { return this.socket.send(JSON.stringify(command)); } _closeOnError () { this._handshakeTimeout.stop(); this._disconnectionReason = 'error'; return this.socket.close(); } _onopen (e) { this.handlers.socketConnected(); this._disconnectionReason = 'handshake-failed'; // start handshake const hello = { command: 'hello', protocols: [PROTOCOL_6, PROTOCOL_7] }; hello.ver = VERSION; if (this.options.ext) { hello.ext = this.options.ext; } if (this.options.extver) { hello.extver = this.options.extver; } if (this.options.snipver) { hello.snipver = this.options.snipver; } this._sendCommand(hello); return this._handshakeTimeout.start(this.options.handshake_timeout); } _onclose (e) { this.protocol = 0; this.handlers.disconnected(this._disconnectionReason, this._nextDelay); return this._scheduleReconnection(); } _onerror (e) {} _onmessage (e) { return this.protocolParser.process(e.data); } }; exports.Connector = Connector;