-
Notifications
You must be signed in to change notification settings - Fork 188
Expand file tree
/
Copy pathlivereload.coffee
More file actions
170 lines (138 loc) · 5.96 KB
/
livereload.coffee
File metadata and controls
170 lines (138 loc) · 5.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
{ Connector } = require './connector'
{ Timer } = require './timer'
{ Options } = require './options'
{ Reloader } = require './reloader'
exports.LiveReload = class LiveReload
constructor: (@window) ->
@listeners = {}
@plugins = []
@pluginIdentifiers = {}
# i can haz console?
@console = if @window.location.href.match(/LR-verbose/) && @window.console && @window.console.log && @window.console.error
@window.console
else
log: ->
error: ->
# i can haz sockets?
unless @WebSocket = @window.WebSocket || @window.MozWebSocket
@console.error("LiveReload disabled because the browser does not seem to support web sockets")
return
# i can haz options?
unless @options = Options.extract(@window.document)
@console.error("LiveReload disabled because it could not find its own <SCRIPT> tag")
return
# i can haz reloader?
@reloader = new Reloader(@window, @console, Timer)
# i can haz connection?
@connector = new Connector @options, @WebSocket, Timer,
connecting: =>
socketConnected: =>
connected: (protocol) =>
@listeners.connect?()
@log "LiveReload is connected to #{@options.host}:#{@options.port} (protocol v#{protocol})."
@analyze()
error: (e) =>
if e instanceof ProtocolError
console.log "#{e.message}." if console?
else
console.log "LiveReload internal error: #{e.message}" if console?
disconnected: (reason, nextDelay) =>
@listeners.disconnect?()
switch reason
when 'cannot-connect'
@log "LiveReload cannot connect to #{@options.host}:#{@options.port}, will retry in #{nextDelay} sec."
when 'broken'
@log "LiveReload disconnected from #{@options.host}:#{@options.port}, reconnecting in #{nextDelay} sec."
when 'handshake-timeout'
@log "LiveReload cannot connect to #{@options.host}:#{@options.port} (handshake timeout), will retry in #{nextDelay} sec."
when 'handshake-failed'
@log "LiveReload cannot connect to #{@options.host}:#{@options.port} (handshake failed), will retry in #{nextDelay} sec."
when 'manual' then #nop
when 'error' then #nop
else
@log "LiveReload disconnected from #{@options.host}:#{@options.port} (#{reason}), reconnecting in #{nextDelay} sec."
message: (message) =>
switch message.command
when 'reload' then @performReload(message)
when 'alert' then @performAlert(message)
on: (eventName, handler) ->
@listeners[eventName] = handler
log: (message) ->
@console.log "#{message}"
performReload: (message) ->
@log "LiveReload received reload request: #{JSON.stringify(message, null, 2)}"
@reloader.reload message.path,
liveCSS: message.liveCSS ? yes
liveImg: message.liveImg ? yes
originalPath: message.originalPath || ''
overrideURL: message.overrideURL || ''
serverURL: "http://#{@options.host}:#{@options.port}"
@performTransition() if @options.animate
performAlert: (message) ->
alert message.message
performTransition: ->
html = document.body.parentNode
reloadedClass = ' livereload-reloaded '
existingHtmlClass = html.getAttribute('class') ? ''
html.setAttribute('class', "#{existingHtmlClass.replace(reloadedClass, '')} #{reloadedClass}")
setTimeout (-> html.setAttribute('class', existingHtmlClass.replace(reloadedClass, ''))),
parseInt(@options.animate, 10)
shutDown: ->
@connector.disconnect()
@log "LiveReload disconnected."
@listeners.shutdown?()
hasPlugin: (identifier) -> !!@pluginIdentifiers[identifier]
addPlugin: (pluginClass) ->
return if @hasPlugin(pluginClass.identifier)
@pluginIdentifiers[pluginClass.identifier] = yes
plugin = new pluginClass @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: @reloader
_connector: @connector
# official API
console: @console
Timer: Timer
generateCacheBustUrl: (url) => @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)
@plugins.push plugin
@reloader.addPlugin plugin
return
analyze: ->
return unless @connector.protocol >= 7
pluginsData = {}
for plugin in @plugins
pluginsData[plugin.constructor.identifier] = pluginData = plugin.analyze?() || {}
pluginData.version = plugin.constructor.version
@connector.sendCommand { command: 'info', plugins: pluginsData, url: @window.location.href }
return
setUpCSSTransitions: ->
prefixer = (declaration) ->
(['-webkit-', '-moz-', ''].map (item) -> ("#{item}#{declaration}")).join(' ')
head = document.getElementsByTagName('head')[0]
styleNode = document.createElement("style")
cssText = ".livereload-reloaded * { #{prefixer('transition: all ' +
@options.animate + 'ms ease-out;')} }"
if styleNode.styleSheet
styleNode.styleSheet.cssText = cssText
else
styleNode.appendChild(document.createTextNode(cssText))
head.appendChild(styleNode)