UNPKG

3.96 kBJavaScriptView Raw
1const { Parser, PROTOCOL_6, PROTOCOL_7 } = require('./protocol');
2
3const VERSION = process.env.npm_package_version;
4
5class Connector {
6 constructor (options, WebSocket, Timer, handlers) {
7 this.options = options;
8 this.WebSocket = WebSocket;
9 this.Timer = Timer;
10 this.handlers = handlers;
11 const path = this.options.path ? `${this.options.path}` : 'livereload';
12 this._uri = `ws${this.options.https ? 's' : ''}://${this.options.host}:${this.options.port}/${path}`;
13
14 this._nextDelay = this.options.mindelay;
15 this._connectionDesired = false;
16 this.protocol = 0;
17
18 this.protocolParser = new Parser({
19 connected: protocol => {
20 this.protocol = protocol;
21 this._handshakeTimeout.stop();
22 this._nextDelay = this.options.mindelay;
23 this._disconnectionReason = 'broken';
24
25 return this.handlers.connected(this.protocol);
26 },
27 error: e => {
28 this.handlers.error(e);
29
30 return this._closeOnError();
31 },
32 message: message => {
33 return this.handlers.message(message);
34 }
35 });
36
37 this._handshakeTimeout = new this.Timer(() => {
38 if (!this._isSocketConnected()) {
39 return;
40 }
41
42 this._disconnectionReason = 'handshake-timeout';
43
44 return this.socket.close();
45 });
46
47 this._reconnectTimer = new this.Timer(() => {
48 if (!this._connectionDesired) {
49 // shouldn't hit this, but just in case
50 return;
51 }
52
53 return this.connect();
54 });
55
56 this.connect();
57 }
58
59 _isSocketConnected () {
60 return this.socket && (this.socket.readyState === this.WebSocket.OPEN);
61 }
62
63 connect () {
64 this._connectionDesired = true;
65
66 if (this._isSocketConnected()) {
67 return;
68 }
69
70 // prepare for a new connection
71 this._reconnectTimer.stop();
72 this._disconnectionReason = 'cannot-connect';
73 this.protocolParser.reset();
74
75 this.handlers.connecting();
76
77 this.socket = new this.WebSocket(this._uri);
78 this.socket.onopen = e => this._onopen(e);
79 this.socket.onclose = e => this._onclose(e);
80 this.socket.onmessage = e => this._onmessage(e);
81 this.socket.onerror = e => this._onerror(e);
82 }
83
84 disconnect () {
85 this._connectionDesired = false;
86 this._reconnectTimer.stop(); // in case it was running
87
88 if (!this._isSocketConnected()) {
89 return;
90 }
91
92 this._disconnectionReason = 'manual';
93
94 return this.socket.close();
95 }
96
97 _scheduleReconnection () {
98 if (!this._connectionDesired) {
99 // don't reconnect after manual disconnection
100 return;
101 }
102
103 if (!this._reconnectTimer.running) {
104 this._reconnectTimer.start(this._nextDelay);
105 this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);
106 }
107 }
108
109 sendCommand (command) {
110 if (!this.protocol) {
111 return;
112 }
113
114 return this._sendCommand(command);
115 }
116
117 _sendCommand (command) {
118 return this.socket.send(JSON.stringify(command));
119 }
120
121 _closeOnError () {
122 this._handshakeTimeout.stop();
123 this._disconnectionReason = 'error';
124
125 return this.socket.close();
126 }
127
128 _onopen (e) {
129 this.handlers.socketConnected();
130 this._disconnectionReason = 'handshake-failed';
131
132 // start handshake
133 const hello = { command: 'hello', protocols: [PROTOCOL_6, PROTOCOL_7] };
134
135 hello.ver = VERSION;
136
137 if (this.options.ext) {
138 hello.ext = this.options.ext;
139 }
140
141 if (this.options.extver) {
142 hello.extver = this.options.extver;
143 }
144
145 if (this.options.snipver) {
146 hello.snipver = this.options.snipver;
147 }
148
149 this._sendCommand(hello);
150
151 return this._handshakeTimeout.start(this.options.handshake_timeout);
152 }
153
154 _onclose (e) {
155 this.protocol = 0;
156 this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
157
158 return this._scheduleReconnection();
159 }
160
161 _onerror (e) {}
162
163 _onmessage (e) {
164 return this.protocolParser.process(e.data);
165 }
166};
167
168exports.Connector = Connector;