1 |
|
2 | const { Connector } = require('./connector');
|
3 | const { Timer } = require('./timer');
|
4 | const { Options } = require('./options');
|
5 | const { Reloader } = require('./reloader');
|
6 | const { ProtocolError } = require('./protocol');
|
7 |
|
8 | class LiveReload {
|
9 | constructor (window) {
|
10 | this.window = window;
|
11 | this.listeners = {};
|
12 | this.plugins = [];
|
13 | this.pluginIdentifiers = {};
|
14 |
|
15 |
|
16 | this.console =
|
17 | this.window.console && this.window.console.log && this.window.console.error
|
18 | ? this.window.location.href.match(/LR-verbose/)
|
19 | ? this.window.console
|
20 | : {
|
21 | log () {},
|
22 | error: this.window.console.error.bind(this.window.console)
|
23 | }
|
24 | : {
|
25 | log () {},
|
26 | error () {}
|
27 | };
|
28 |
|
29 |
|
30 | if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {
|
31 | this.console.error('LiveReload disabled because the browser does not seem to support web sockets');
|
32 |
|
33 | return;
|
34 | }
|
35 |
|
36 |
|
37 | if ('LiveReloadOptions' in window) {
|
38 | this.options = new Options();
|
39 |
|
40 | for (const k of Object.keys(window.LiveReloadOptions || {})) {
|
41 | const v = window.LiveReloadOptions[k];
|
42 |
|
43 | this.options.set(k, v);
|
44 | }
|
45 | } else {
|
46 | this.options = Options.extract(this.window.document);
|
47 |
|
48 | if (!this.options) {
|
49 | this.console.error('LiveReload disabled because it could not find its own <SCRIPT> tag');
|
50 |
|
51 | return;
|
52 | }
|
53 | }
|
54 |
|
55 |
|
56 | this.reloader = new Reloader(this.window, this.console, Timer);
|
57 |
|
58 |
|
59 | this.connector = new Connector(this.options, this.WebSocket, Timer, {
|
60 | connecting: () => {},
|
61 |
|
62 | socketConnected: () => {},
|
63 |
|
64 | connected: protocol => {
|
65 | if (typeof this.listeners.connect === 'function') {
|
66 | this.listeners.connect();
|
67 | }
|
68 |
|
69 | this.log(`LiveReload is connected to ${this.options.host}:${this.options.port} (protocol v${protocol}).`);
|
70 |
|
71 | return this.analyze();
|
72 | },
|
73 |
|
74 | error: e => {
|
75 | if (e instanceof ProtocolError) {
|
76 | if (typeof console !== 'undefined' && console !== null) {
|
77 | return console.log(`${e.message}.`);
|
78 | }
|
79 | } else {
|
80 | if (typeof console !== 'undefined' && console !== null) {
|
81 | return console.log(`LiveReload internal error: ${e.message}`);
|
82 | }
|
83 | }
|
84 | },
|
85 |
|
86 | disconnected: (reason, nextDelay) => {
|
87 | if (typeof this.listeners.disconnect === 'function') {
|
88 | this.listeners.disconnect();
|
89 | }
|
90 |
|
91 | switch (reason) {
|
92 | case 'cannot-connect':
|
93 | return this.log(`LiveReload cannot connect to ${this.options.host}:${this.options.port}, will retry in ${nextDelay} sec.`);
|
94 | case 'broken':
|
95 | return this.log(`LiveReload disconnected from ${this.options.host}:${this.options.port}, reconnecting in ${nextDelay} sec.`);
|
96 | case 'handshake-timeout':
|
97 | return this.log(`LiveReload cannot connect to ${this.options.host}:${this.options.port} (handshake timeout), will retry in ${nextDelay} sec.`);
|
98 | case 'handshake-failed':
|
99 | return this.log(`LiveReload cannot connect to ${this.options.host}:${this.options.port} (handshake failed), will retry in ${nextDelay} sec.`);
|
100 | case 'manual':
|
101 | case 'error':
|
102 | default:
|
103 | return this.log(`LiveReload disconnected from ${this.options.host}:${this.options.port} (${reason}), reconnecting in ${nextDelay} sec.`);
|
104 | }
|
105 | },
|
106 |
|
107 | message: message => {
|
108 | switch (message.command) {
|
109 | case 'reload':
|
110 | return this.performReload(message);
|
111 | case 'alert':
|
112 | return this.performAlert(message);
|
113 | }
|
114 | }
|
115 | });
|
116 |
|
117 | this.initialized = true;
|
118 | }
|
119 |
|
120 | on (eventName, handler) {
|
121 | this.listeners[eventName] = handler;
|
122 | }
|
123 |
|
124 | log (message) {
|
125 | return this.console.log(`${message}`);
|
126 | }
|
127 |
|
128 | performReload (message) {
|
129 | this.log(`LiveReload received reload request: ${JSON.stringify(message, null, 2)}`);
|
130 |
|
131 | return this.reloader.reload(message.path, {
|
132 | liveCSS: message.liveCSS != null ? message.liveCSS : true,
|
133 | liveImg: message.liveImg != null ? message.liveImg : true,
|
134 | reloadMissingCSS: message.reloadMissingCSS != null ? message.reloadMissingCSS : true,
|
135 | originalPath: message.originalPath || '',
|
136 | overrideURL: message.overrideURL || '',
|
137 | serverURL: `http://${this.options.host}:${this.options.port}`,
|
138 | pluginOrder: this.options.pluginOrder
|
139 | });
|
140 | }
|
141 |
|
142 | performAlert (message) {
|
143 | return alert(message.message);
|
144 | }
|
145 |
|
146 | shutDown () {
|
147 | if (!this.initialized) {
|
148 | return;
|
149 | }
|
150 |
|
151 | this.connector.disconnect();
|
152 | this.log('LiveReload disconnected.');
|
153 |
|
154 | return (typeof this.listeners.shutdown === 'function' ? this.listeners.shutdown() : undefined);
|
155 | }
|
156 |
|
157 | hasPlugin (identifier) {
|
158 | return !!this.pluginIdentifiers[identifier];
|
159 | }
|
160 |
|
161 | addPlugin (PluginClass) {
|
162 | if (!this.initialized) {
|
163 | return;
|
164 | }
|
165 |
|
166 | if (this.hasPlugin(PluginClass.identifier)) {
|
167 | return;
|
168 | }
|
169 |
|
170 | this.pluginIdentifiers[PluginClass.identifier] = true;
|
171 |
|
172 | const plugin = new PluginClass(
|
173 | this.window,
|
174 | {
|
175 |
|
176 |
|
177 | _livereload: this,
|
178 | _reloader: this.reloader,
|
179 | _connector: this.connector,
|
180 |
|
181 |
|
182 | console: this.console,
|
183 | Timer,
|
184 | generateCacheBustUrl: url => this.reloader.generateCacheBustUrl(url)
|
185 | }
|
186 | );
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 | this.plugins.push(plugin);
|
208 | this.reloader.addPlugin(plugin);
|
209 | }
|
210 |
|
211 | analyze () {
|
212 | if (!this.initialized) {
|
213 | return;
|
214 | }
|
215 |
|
216 | if (!(this.connector.protocol >= 7)) {
|
217 | return;
|
218 | }
|
219 |
|
220 | const pluginsData = {};
|
221 |
|
222 | for (const plugin of this.plugins) {
|
223 | var pluginData = (typeof plugin.analyze === 'function' ? plugin.analyze() : undefined) || {};
|
224 |
|
225 | pluginsData[plugin.constructor.identifier] = pluginData;
|
226 | pluginData.version = plugin.constructor.version;
|
227 | }
|
228 |
|
229 | this.connector.sendCommand({
|
230 | command: 'info',
|
231 | plugins: pluginsData,
|
232 | url: this.window.location.href
|
233 | });
|
234 | }
|
235 | };
|
236 |
|
237 | exports.LiveReload = LiveReload;
|