1 | fs = require 'fs'
|
2 | path = require 'path'
|
3 | ws = require 'ws'
|
4 | http = require 'http'
|
5 | https = require 'https'
|
6 | url = require 'url'
|
7 | chokidar = require 'chokidar'
|
8 |
|
9 | protocol_version = '7'
|
10 | defaultPort = 35729
|
11 |
|
12 | defaultExts = [
|
13 | 'html', 'css', 'js', 'png', 'gif', 'jpg',
|
14 | 'php', 'php5', 'py', 'rb', 'erb', 'coffee'
|
15 | ]
|
16 |
|
17 | defaultExclusions = [/\.git\//, /\.svn\//, /\.hg\//]
|
18 |
|
19 | class Server
|
20 | constructor: (@config) ->
|
21 | @config ?= {}
|
22 |
|
23 | @config.version ?= protocol_version
|
24 | @config.port ?= defaultPort
|
25 |
|
26 | @config.exts ?= []
|
27 | @config.exclusions ?= []
|
28 |
|
29 | @config.exts = @config.exts.concat defaultExts
|
30 | @config.exclusions = @config.exclusions.concat defaultExclusions
|
31 |
|
32 | @config.applyCSSLive ?= true
|
33 |
|
34 | @config.originalPath ?= ''
|
35 | @config.overrideURL ?= ''
|
36 |
|
37 | @config.usePolling ?= false
|
38 |
|
39 | listen: (callback) ->
|
40 | @debug "LiveReload is waiting for browser to connect."
|
41 | @debug """
|
42 | Protocol version: #{@config.version}
|
43 | Exclusions: #{@config.exclusions}
|
44 | Extensions: #{@config.exts}
|
45 | Polling: #{@config.usePolling}
|
46 |
|
47 | """
|
48 |
|
49 | if @config.server
|
50 | @config.server.listen @config.port
|
51 | @server = new ws.Server({server: @config.server})
|
52 | else
|
53 | @server = new ws.Server({port: @config.port})
|
54 |
|
55 | @server.on 'connection', @onConnection.bind @
|
56 | @server.on 'close', @onClose.bind @
|
57 | if callback
|
58 | @server.once 'listening', callback
|
59 |
|
60 | onConnection: (socket) ->
|
61 | @debug "Browser connected."
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | socket.on 'message', (message) =>
|
70 | @debug "Client message: #{message}"
|
71 |
|
72 | request = JSON.parse(message)
|
73 |
|
74 | if request.command == "hello"
|
75 | @debug "Client requested handshake..."
|
76 | @debug "Handshaking with client using protocol #{@config.version}..."
|
77 |
|
78 | data = JSON.stringify {
|
79 | command: 'hello',
|
80 | protocols: [
|
81 | 'http://livereload.com/protocols/official-7',
|
82 | 'http://livereload.com/protocols/official-8',
|
83 | 'http://livereload.com/protocols/official-9',
|
84 | 'http://livereload.com/protocols/2.x-origin-version-negotiation',
|
85 | 'http://livereload.com/protocols/2.x-remote-control'],
|
86 | serverName: 'node-livereload'
|
87 | }
|
88 |
|
89 | socket.send data
|
90 |
|
91 |
|
92 | socket.on 'error', (err) =>
|
93 | @debug "Error in client socket: #{err}"
|
94 |
|
95 | socket.on 'close', (message) =>
|
96 | @debug "Client closed connection"
|
97 |
|
98 |
|
99 | onClose: (socket) ->
|
100 | @debug "Socket closed."
|
101 |
|
102 | watch: (paths) ->
|
103 | @debug "Watching #{paths}..."
|
104 | @watcher = chokidar.watch(paths,
|
105 | ignoreInitial: true
|
106 | ignored: @config.exclusions
|
107 | usePolling: @config.usePolling
|
108 | )
|
109 | .on 'add', @filterRefresh.bind(@)
|
110 | .on 'change', @filterRefresh.bind(@)
|
111 | .on 'unlink', @filterRefresh.bind(@)
|
112 |
|
113 |
|
114 | filterRefresh: (filepath) ->
|
115 | exts = @config.exts
|
116 | fileext = path.extname filepath
|
117 | .substring 1
|
118 |
|
119 |
|
120 | if (exts.indexOf(fileext) != -1)
|
121 | if @config.delay
|
122 | delayedRefresh = setTimeout(
|
123 | =>
|
124 | clearTimeout(delayedRefresh)
|
125 | @refresh filepath
|
126 | @config.delay
|
127 | )
|
128 | else
|
129 | @refresh filepath
|
130 |
|
131 | refresh: (filepath) ->
|
132 | @debug "Reloading: #{filepath}"
|
133 | data = JSON.stringify {
|
134 | command: 'reload',
|
135 | path: filepath,
|
136 | liveCSS: @config.applyCSSLive,
|
137 | liveImg: @config.applyImgLive,
|
138 | originalPath: this.config.originalPath,
|
139 | overrideURL: this.config.overrideURL
|
140 | }
|
141 | @sendAllClients data
|
142 |
|
143 | alert: (message) ->
|
144 | @debug "Alert: #{message}"
|
145 | data = JSON.stringify {
|
146 | command: 'alert',
|
147 | message: message
|
148 | }
|
149 | @sendAllClients data
|
150 |
|
151 | sendAllClients: (data) ->
|
152 | for socket in @server.clients
|
153 | socket.send data, (error) =>
|
154 | if error
|
155 | @debug error
|
156 |
|
157 | debug: (str) ->
|
158 | if @config.debug
|
159 | console.log "#{str}\n"
|
160 |
|
161 | close: ->
|
162 | @watcher.close()
|
163 |
|
164 | @server._server.close()
|
165 | @server.close()
|
166 |
|
167 | exports.createServer = (config = {}, callback) ->
|
168 | requestHandler = ( req, res )->
|
169 | if url.parse(req.url).pathname is '/livereload.js'
|
170 | res.writeHead(200, {'Content-Type': 'text/javascript'})
|
171 | res.end fs.readFileSync __dirname + '/../ext/livereload.js'
|
172 | if !config.https?
|
173 | app = http.createServer requestHandler
|
174 | else
|
175 | app = https.createServer config.https, requestHandler
|
176 |
|
177 | config.server ?= app
|
178 |
|
179 | server = new Server config
|
180 | unless config.noListen
|
181 | server.listen(callback)
|
182 | server
|