UNPKG

livereload-js

Version:

LiveReload JS client - auto reload browser on changes

246 lines (199 loc) 7.34 kB
/* global alert */ const { Connector } = require('./connector'); const { Timer } = require('./timer'); const { Options } = require('./options'); const { Reloader } = require('./reloader'); const { ProtocolError } = require('./protocol'); class LiveReload { constructor (window) { this.window = window; this.listeners = {}; this.plugins = []; this.pluginIdentifiers = {}; // i can haz console? this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.location.href.match(/LR-verbose/) ? this.window.console : { log () {}, error: this.window.console.error.bind(this.window.console) } : { log () {}, error () {} }; // i can haz sockets? if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) { this.console.error('LiveReload disabled because the browser does not seem to support web sockets'); return; } // i can haz options? if ('LiveReloadOptions' in window) { this.options = new Options(); for (const k of Object.keys(window.LiveReloadOptions || {})) { const v = window.LiveReloadOptions[k]; this.options.set(k, v); } } else { this.options = Options.extract(this.window.document); if (!this.options) { this.console.error('LiveReload disabled because it could not find its own <SCRIPT> tag'); return; } } // i can haz reloader? this.reloader = new Reloader(this.window, this.console, Timer); // i can haz connection? this.connector = new Connector(this.options, this.WebSocket, Timer, { connecting: () => {}, socketConnected: () => {}, connected: protocol => { if (typeof this.listeners.connect === 'function') { this.listeners.connect(); } const { host } = this.options; const port = this.options.port ? `:${this.options.port}` : ''; this.log(`LiveReload is connected to ${host}${port} (protocol v${protocol}).`); return this.analyze(); }, error: e => { if (e instanceof ProtocolError) { if (typeof console !== 'undefined' && console !== null) { return console.log(`${e.message}.`); } } else { if (typeof console !== 'undefined' && console !== null) { return console.log(`LiveReload internal error: ${e.message}`); } } }, disconnected: (reason, nextDelay) => { if (typeof this.listeners.disconnect === 'function') { this.listeners.disconnect(); } const { host } = this.options; const port = this.options.port ? `:${this.options.port}` : ''; switch (reason) { case 'cannot-connect': return this.log(`LiveReload cannot connect to ${host}${port}, will retry in ${nextDelay} sec.`); case 'broken': return this.log(`LiveReload disconnected from ${host}${port}, reconnecting in ${nextDelay} sec.`); case 'handshake-timeout': return this.log(`LiveReload cannot connect to ${host}${port} (handshake timeout), will retry in ${nextDelay} sec.`); case 'handshake-failed': return this.log(`LiveReload cannot connect to ${host}${port} (handshake failed), will retry in ${nextDelay} sec.`); case 'manual': // nop case 'error': // nop default: return this.log(`LiveReload disconnected from ${host}${port} (${reason}), reconnecting in ${nextDelay} sec.`); } }, message: message => { switch (message.command) { case 'reload': return this.performReload(message); case 'alert': return this.performAlert(message); } } }); this.initialized = true; } on (eventName, handler) { this.listeners[eventName] = handler; } log (message) { return this.console.log(`${message}`); } performReload (message) { this.log(`LiveReload received reload request: ${JSON.stringify(message, null, 2)}`); const { host, port, pluginOrder } = this.options; return this.reloader.reload(message.path, { liveCSS: message.liveCSS != null ? message.liveCSS : true, liveImg: message.liveImg != null ? message.liveImg : true, reloadMissingCSS: message.reloadMissingCSS != null ? message.reloadMissingCSS : true, originalPath: message.originalPath || '', overrideURL: message.overrideURL || '', serverURL: `http://${host}${port && `:${port}`}`, pluginOrder }); } performAlert (message) { return alert(message.message); } shutDown () { if (!this.initialized) { return; } this.connector.disconnect(); this.log('LiveReload disconnected.'); return (typeof this.listeners.shutdown === 'function' ? this.listeners.shutdown() : undefined); } hasPlugin (identifier) { return !!this.pluginIdentifiers[identifier]; } addPlugin (PluginClass) { if (!this.initialized) { return; } if (this.hasPlugin(PluginClass.identifier)) { return; } this.pluginIdentifiers[PluginClass.identifier] = true; const plugin = new PluginClass( this.window, { // expose internal objects for those who know what they're doing // (note that these are private APIs and subject to change at any time!) _livereload: this, _reloader: this.reloader, _connector: this.connector, // official API console: this.console, Timer, generateCacheBustUrl: url => this.reloader.generateCacheBustUrl(url) } ); // API that PluginClass can/must provide: // // string PluginClass.identifier // -- required, globally-unique name of this plugin // // string PluginClass.version // -- required, plugin version number (format %d.%d or %d.%d.%d) // // plugin = new PluginClass(window, officialLiveReloadAPI) // -- required, plugin constructor // // bool plugin.reload(string path, { bool liveCSS, bool liveImg }) // -- optional, attemp to reload the given path, return true if handled // // object plugin.analyze() // -- optional, returns plugin-specific information about the current document (to send to the connected server) // (LiveReload 2 server currently only defines 'disable' key in this object; return {disable:true} to disable server-side // compilation of a matching plugin's files) this.plugins.push(plugin); this.reloader.addPlugin(plugin); } analyze () { if (!this.initialized) { return; } if (!(this.connector.protocol >= 7)) { return; } const pluginsData = {}; for (const plugin of this.plugins) { const pluginData = (typeof plugin.analyze === 'function' ? plugin.analyze() : undefined) || {}; pluginsData[plugin.constructor.identifier] = pluginData; pluginData.version = plugin.constructor.version; } this.connector.sendCommand({ command: 'info', plugins: pluginsData, url: this.window.location.href }); } }; exports.LiveReload = LiveReload;