Permalink
Browse files

Initial version: no reloading, only web socket connection stuff

  • Loading branch information...
andreyvit committed Sep 29, 2011
1 parent b53ddda commit 710597fa7cba19840cebc636831bf92593f60c49
View
@@ -0,0 +1,2 @@
+.DS_Store
+*.js
View
@@ -0,0 +1,11 @@
+fs = require 'fs'
+path = require 'path'
+
+task 'stitch', 'Build a merged JS file', ->
+ stitch = require 'stitch'
+ package = stitch.createPackage(paths: [__dirname + "/lib"])
+
+ package.compile (err, source) ->
+ fs.writeFile __dirname + "/dist/livereload.js", source, (err) ->
+ throw err if err
+ console.log "Compiled livereload.js"
View
No changes.
View
No changes.
View
@@ -0,0 +1,82 @@
+{ Parser, PROTOCOL_6, PROTOCOL_7 } = require 'protocol'
+
+exports.Connector = class Connector
+
+ constructor: (@options, @WebSocket, @Timer, @handlers) ->
+ @_uri = "ws://#{@options.host}:#{@options.port}/livereload"
+ @_nextDelay = @options.mindelay
+
+ @protocolParser = new Parser
+ connected: (protocol) =>
+ @_handshakeTimeout.stop()
+ @_nextDelay = @options.mindelay
+ @_disconnectionReason = 'broken'
+ @handlers.connected(protocol)
+ error: (e) =>
+ @handlers.error(e)
+ @_closeOnError()
+ message: (message) =>
+ @handlers.message(message)
+
+ @_handshakeTimeout = new Timer =>
+ return unless @_isSocketConnected()
+ @_disconnectionReason = 'handshake-timeout'
+ @socket.close()
+
+ @_reconnectTimer = new Timer => @connect()
+
+ @connect()
+
+
+ _isSocketConnected: ->
+ @socket and @socket.readyState is @WebSocket.OPEN
+
+ connect: ->
+ return if @_isSocketConnected()
+
+ # prepare for a new connection
+ clearTimeout @_reconnectTimer if @_reconnectTimer
+ @_disconnectionReason = 'cannot-connect'
+ @protocolParser.reset()
+
+ @handlers.connecting()
+
+ @socket = new @WebSocket(@_uri)
+ @socket.onopen = (e) => @_onopen(e)
+ @socket.onclose = (e) => @_onclose(e)
+ @socket.onmessage = (e) => @_onmessage(e)
+ @socket.onerror = (e) => @_onerror(e)
+
+ _scheduleReconnection: ->
+ unless @_reconnectTimer.running
+ @_reconnectTimer.start(@_nextDelay)
+ @_nextDelay = Math.min(@options.maxdelay, @_nextDelay * 2)
+
+ sendCommand: (command) ->
+ return unless @protocol?
+ @_sendCommand command
+
+ _sendCommand: (command) ->
+ @socket.send JSON.stringify(command)
+
+ _closeOnError: ->
+ @_handshakeTimeout.stop()
+ @_disconnectionReason = 'error'
+ @socket.close()
+
+ _onopen: (e) ->
+ @handlers.socketConnected()
+ @_disconnectionReason = 'handshake-failed'
+
+ # start handshake
+ @_sendCommand { command: 'hello', protocols: [PROTOCOL_6, PROTOCOL_7] }
+ @_handshakeTimeout.start(@options.handshake_timeout)
+
+ _onclose: (e) ->
+ @handlers.disconnected @_disconnectionReason, @_nextDelay
+ @_scheduleReconnection() unless @_disconnectionReason is 'manual'
+
+ _onerror: (e) ->
+
+ _onmessage: (e) ->
+ @protocolParser.process(e.data)
View
@@ -0,0 +1,57 @@
+{ Connector } = require 'connector'
+{ Timer } = require 'timer'
+{ Options } = require 'options'
+
+exports.LiveReload = class LiveReload
+
+ constructor: (@window) ->
+ # i can haz console?
+ @console = if @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?
+ @options = Options.extract(@window.document)
+
+ # i can haz connection?
+ @connector = new Connector @options, @WebSocket, Timer,
+ connecting: ->
+
+ socketConnected: ->
+
+ connected: (protocol) ->
+ @log "LiveReload is connected to #{@options.host}:#{@options.port} (protocol v#{protocol})."
+
+ error: (e) ->
+ if e instanceof ProtocolError
+ console.log "#{e.message}."
+ else
+ console.log "LiveReload internal error: #{e.message}"
+
+ disconnected: (reason, nextDelay) =>
+ 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) ->
+ @log "LiveReload received message #{message.command}."
+
+ log: (message) ->
+ @console.log "LiveReload: #{message}"
View
@@ -0,0 +1,36 @@
+
+exports.Options = class Options
+ constructor: ->
+ @host = null
+ @port = null
+
+ @snipver = null
+ @ext = null
+ @extver = null
+
+ @mindelay = 1000
+ @maxdelay = 60000
+ @handshake_timeout = 5000
+
+ set: (name, value) ->
+ switch typeof @[name]
+ when 'undefined' then # ignore
+ when 'number'
+ @[name] = +value
+ else
+ @[name] = value
+
+Options.extract = (document) ->
+ for element in document.getElementsByTagName('script')
+ if (src = element.src) && (m = src.match ///^ https?:// ([^/:]+) : (\d+) / livereload\.js (?: \? (.*) )? $///)
+ options = new Options()
+ options.host = m[1]
+ options.port = parseInt(m[2], 10)
+
+ if m[3]
+ for pair in m[3].split('&')
+ if (keyAndValue = pair.split('=')).length > 1
+ options.set keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('=')
+ return options
+
+ return null
View
@@ -0,0 +1,58 @@
+
+exports.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official/6'
+exports.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official/7'
+
+exports.ProtocolError = class ProtocolError
+ constructor: (reason, data) ->
+ @message = "LiveReload protocol error (#{reason}) after receiving data: \"#{data}\"."
+
+exports.Parser = class Parser
+ constructor: (@handlers) ->
+ @reset()
+
+ reset: ->
+ @protocol = null
+
+ process: (data) ->
+ try
+ if not @protocol?
+ if data.match(///^ !!ver: ([\d.]+) $///)
+ @protocol = 6
+ else if message = @_parseMessage(data, ['hello'])
+ if !message.protocols.length
+ throw new ProtocolError("no protocols specified in handshake message")
+ else if PROTOCOL_7 in message.protocols
+ @protocol = 7
+ else if PROTOCOL_6 in message.protocols
+ @protocol = 6
+ else
+ throw new ProtocolError("no supported protocols found")
+ @handlers.connected @protocol
+ else if @protocol == 6
+ message = JSON.parse(data)
+ if !message.length
+ throw new ProtocolError("protocol 6 messages must be arrays")
+ [command, options] = message
+ if command != 'refresh'
+ throw new ProtocolError("unknown protocol 6 command")
+
+ @handlers.message command: 'reload', path: options.path, liveCSS: options.apply_css_live ? yes
+ else
+ message = @_parseMessage(data, ['reload', 'alert'])
+ @handlers.message(message)
+ catch e
+ if e instanceof ProtocolError
+ @handlers.error e
+ else
+ throw e
+
+ _parseMessage: (data, validCommands) ->
+ try
+ message = JSON.parse(data)
+ catch e
+ throw new ProtocolError('unparsable JSON', data)
+ unless message.command
+ throw new ProtocolError('missing "command" key', data)
+ unless message.command in validCommands
+ throw new ProtocolError("invalid command '#{message.command}', only valid commands are: #{validCommands.join(', ')})", data)
+ return message
View
@@ -0,0 +1 @@
+window.LiveReload = new (require('livereload').LiveReload)()
View
@@ -0,0 +1,16 @@
+exports.Timer = class Timer
+ constructor: (@func) ->
+ @running = no; @id = null
+ @_handler = =>
+ @running = no; @id = null
+ @func()
+
+ start: (timeout) ->
+ clearTimeout @id if @running
+ @id = setTimeout @_handler, timeout
+ @running = yes
+
+ stop: ->
+ if @running
+ clearTimeout @id
+ @running = no; @id = null
Oops, something went wrong.

0 comments on commit 710597f

Please sign in to comment.