Compare commits
84 Commits
fix-deadlo
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
8cf0ebec60 | ||
|
c0c47cc83b | ||
|
16c1ba76ed | ||
|
f830396dbf | ||
|
41bdc64bcb | ||
|
651d202dd2 | ||
|
ffa5d63ffc | ||
|
d06a756c8d | ||
|
ef3895cbcf | ||
|
727296953b | ||
|
618feb865c | ||
|
f5cbf07d21 | ||
|
493aa6255d | ||
|
e7cbf825d9 | ||
|
9a4316ce37 | ||
|
952ea670dd | ||
|
5cdf2368e8 | ||
|
5b5d62925f | ||
|
27063b7d20 | ||
|
d67df1bb77 | ||
|
64449acbc9 | ||
|
13ac1c85f3 | ||
|
165f057cd3 | ||
|
40a2ab41fb | ||
|
cd592e9c41 | ||
|
81a14b4357 | ||
|
89b4a710e8 | ||
|
ab8fe2944a | ||
|
707d2955e0 | ||
|
e9f4417307 | ||
|
a0ed81e1b4 | ||
|
117f251ff7 | ||
|
81fb8384cb | ||
|
85a27e6bec | ||
|
1891dbb920 | ||
|
e43835bdbf | ||
|
25a5d217f5 | ||
|
d665d6e3f0 | ||
|
176ce40003 | ||
|
13e09428f3 | ||
|
8cd7e10604 | ||
|
730a595be2 | ||
|
2eae7fd1bb | ||
|
698ae43650 | ||
|
03ea0608cd | ||
|
45d5868aa6 | ||
|
1a6424b2bf | ||
|
20f03cbe7c | ||
|
2327695365 | ||
|
7c432f4438 | ||
|
306e5cd275 | ||
|
6b93954231 | ||
|
75c50f7e92 | ||
|
c1415c32cb | ||
|
c9e096f77a | ||
|
9aae5fa5fa | ||
|
206ce04e77 | ||
|
85855cb25a | ||
|
fbbea710d7 | ||
|
b3bd9fa9a4 | ||
|
0c5d9bc06b | ||
|
706d158328 | ||
|
6f9576215a | ||
|
13c50ca118 | ||
|
657157fcf6 | ||
|
a69cdf1103 | ||
|
cc1ad71a62 | ||
|
ebeb331f0f | ||
|
394af20be2 | ||
|
7a6a51cb62 | ||
|
d1b9a6ef7b | ||
|
6661507d83 | ||
|
7115365877 | ||
|
2988401306 | ||
|
e998fab430 | ||
|
8c83b90015 | ||
|
0d82995d8c | ||
|
e51b347653 | ||
|
2ebc246b61 | ||
|
926f052122 | ||
|
859558622a | ||
|
2f23626fe2 | ||
|
f235eab442 | ||
|
e59329ed38 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
|||||||
*.gem
|
*.gem
|
||||||
.bundle
|
.bundle
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
gemfiles/*.lock
|
||||||
pkg/*
|
pkg/*
|
||||||
*.orig
|
*.orig
|
||||||
|
tmp/
|
||||||
|
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
rvm:
|
||||||
|
- 1.9.3
|
||||||
|
- 2.0.0
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
gemfile:
|
||||||
|
- gemfiles/rails32.gemfile
|
||||||
|
- gemfiles/rails40.gemfile
|
||||||
|
|
7
Appraisals
Normal file
7
Appraisals
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
appraise 'rails32' do
|
||||||
|
gem 'rails', '~> 3.2.0'
|
||||||
|
end
|
||||||
|
|
||||||
|
appraise 'rails40' do
|
||||||
|
gem 'rails', '~> 4.0.0'
|
||||||
|
end
|
@ -1,7 +1,7 @@
|
|||||||
# A sample Guardfile
|
# A sample Guardfile
|
||||||
# More info at https://github.com/guard/guard#readme
|
# More info at https://github.com/guard/guard#readme
|
||||||
|
|
||||||
guard 'rspec', :version => 2, :cli => '-c' do
|
guard 'rspec', :cli => '-c' do
|
||||||
watch(%r{^spec/.+_spec\.rb$})
|
watch(%r{^spec/.+_spec\.rb$})
|
||||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||||
watch('spec/spec_helper.rb') { "spec" }
|
watch('spec/spec_helper.rb') { "spec" }
|
||||||
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright © 2012 John Bintz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the “Software”), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
73
README.md
73
README.md
@ -1,50 +1,75 @@
|
|||||||
Hey, you've got [LiveReload](http://www.livereload.com/) in my [Rack](http://rack.rubyforge.org/)!
|
# Rack::LiveReload
|
||||||
|
|
||||||
|
_This fork is deprecated: Go check out https://github.com/onesupercoder/rack-livereload instead._
|
||||||
|
|
||||||
|
<a href="http://travis-ci.org/johnbintz/rack-livereload"><img src="https://secure.travis-ci.org/johnbintz/rack-livereload.png" /></a>
|
||||||
|
[![Code Climate](https://codeclimate.com/github/johnbintz/rack-livereload.png)](https://codeclimate.com/github/johnbintz/rack-livereload)
|
||||||
|
|
||||||
|
Hey, you've got [LiveReload](http://livereload.com/) in my [Rack](http://rack.rubyforge.org/)!
|
||||||
No need for browser extensions anymore! Just plug it in your middleware stack and go!
|
No need for browser extensions anymore! Just plug it in your middleware stack and go!
|
||||||
Even supports browsers without WebSockets!
|
Even supports browsers without WebSockets!
|
||||||
|
|
||||||
Use this with [guard-livereload](http://github.com/guard/guard-livereload) for maximum fun!
|
Use this with [guard-livereload](http://github.com/guard/guard-livereload) for maximum fun!
|
||||||
|
|
||||||
## Install
|
## Installation
|
||||||
|
|
||||||
`gem install rack-livereload`
|
|
||||||
|
|
||||||
## Using in...
|
|
||||||
|
|
||||||
### Rails
|
### Rails
|
||||||
|
|
||||||
In `config/environments/development.rb`:
|
Add the gem to your Gemfile.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem "rack-livereload", group: :development
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add the middleware to your Rails middleware stack by editing your `config/environments/development.rb`.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# config/environments/development.rb
|
||||||
|
|
||||||
``` ruby
|
|
||||||
MyApp::Application.configure do
|
MyApp::Application.configure do
|
||||||
config.middleware.insert_before(Rack::Lock, Rack::LiveReload)
|
# Add Rack::LiveReload to the bottom of the middleware stack with the default options:
|
||||||
|
config.middleware.insert_after ActionDispatch::Static, Rack::LiveReload
|
||||||
|
|
||||||
# ...or, change some options...
|
# or, if you're using better_errors:
|
||||||
|
config.middleware.insert_before Rack::Lock, Rack::LiveReload
|
||||||
|
|
||||||
config.middleware.insert_before(
|
# ...
|
||||||
Rack::Lock, Rack::LiveReload,
|
|
||||||
:min_delay => 500,
|
|
||||||
:max_delay => 10000,
|
|
||||||
:port => 56789,
|
|
||||||
:host => 'myhost.cool.wow',
|
|
||||||
:ignore => [ %r{dont/modify\.html$} ]
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### config.ru/Sinatra
|
#### Tweaking the options
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Specifying Rack::LiveReload options.
|
||||||
|
config.middleware.use(Rack::LiveReload,
|
||||||
|
min_delay : 500, # default 1000
|
||||||
|
max_delay : 10_000, # default 60_000
|
||||||
|
live_reload_port : 56789, # default 35729
|
||||||
|
host : 'myhost.cool.wow',
|
||||||
|
ignore : [ %r{dont/modify\.html$} ]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition, Rack::LiveReload's position within middleware stack can be
|
||||||
|
specified by inserting it relative to an exsiting middleware via
|
||||||
|
`insert_before` or `insert_after`. See the [Rails on Rack: Adding a
|
||||||
|
Middleware](http://guides.rubyonrails.org/rails_on_rack.html#adding-a-middleware)
|
||||||
|
section for more detail.
|
||||||
|
|
||||||
|
### Sinatra / config.ru
|
||||||
|
|
||||||
``` ruby
|
``` ruby
|
||||||
require 'rack-livereload'
|
require 'rack-livereload'
|
||||||
|
|
||||||
use Rack::LiveReload
|
use Rack::LiveReload
|
||||||
# ...or...
|
# ...or...
|
||||||
use Rack::LiveReload, :min_delay => 500, ...
|
use Rack::LiveReload, min_delay: 500, ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
The necessary `script` tag to bring in a copy of [livereload.js](https://github.com/livereload/livereload-js) is
|
The necessary `script` tag to bring in a copy of [livereload.js](https://github.com/livereload/livereload-js) is
|
||||||
injected right before the closing `head` tag in any `text/html` pages that come through. The `script` tag is built in
|
injected right after the opening `head` tag in any `text/html` pages that come through. The `script` tag is built in
|
||||||
such a way that the `HTTP_HOST` is used as the LiveReload host, so you can connect from external machines (say, to
|
such a way that the `HTTP_HOST` is used as the LiveReload host, so you can connect from external machines (say, to
|
||||||
`mycomputer:3000` instead of `localhost:3000`) and as long as the LiveReload port is accessible from the external machine,
|
`mycomputer:3000` instead of `localhost:3000`) and as long as the LiveReload port is accessible from the external machine,
|
||||||
you'll connect and be LiveReloading away!
|
you'll connect and be LiveReloading away!
|
||||||
@ -66,17 +91,15 @@ your browser doesn't need it. The SWF WebSocket implementor won't be loaded unle
|
|||||||
WebSockets support or if you force it in the middleware stack:
|
WebSockets support or if you force it in the middleware stack:
|
||||||
|
|
||||||
``` ruby
|
``` ruby
|
||||||
use Rack::LiveReload, :force_swf => true
|
use Rack::LiveReload, force_swf: true
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't want any of the web-sockets-js code included at all, use the `no_swf` option:
|
If you don't want any of the web-sockets-js code included at all, use the `no_swf` option:
|
||||||
|
|
||||||
``` ruby
|
``` ruby
|
||||||
use Rack::LiveReload, :no_swf => true
|
use Rack::LiveReload, no_swf: true
|
||||||
```
|
```
|
||||||
|
|
||||||
Once more browsers support WebSockets than don't, this option will be reversed and you'll have
|
Once more browsers support WebSockets than don't, this option will be reversed and you'll have
|
||||||
to explicitly include the Flash shim.
|
to explicitly include the Flash shim.
|
||||||
|
|
||||||
As usual, super-alpha!
|
|
||||||
|
|
||||||
|
2
Rakefile
2
Rakefile
@ -1,4 +1,6 @@
|
|||||||
require "bundler/gem_tasks"
|
require "bundler/gem_tasks"
|
||||||
|
require 'bundler/setup'
|
||||||
|
require 'appraisal'
|
||||||
|
|
||||||
desc 'Update livereload.js'
|
desc 'Update livereload.js'
|
||||||
task :update_livereload_js do
|
task :update_livereload_js do
|
||||||
|
7
gemfiles/rails32.gemfile
Normal file
7
gemfiles/rails32.gemfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file was generated by Appraisal
|
||||||
|
|
||||||
|
source "http://rubygems.org"
|
||||||
|
|
||||||
|
gem "rails", "~> 3.2.0"
|
||||||
|
|
||||||
|
gemspec :path=>"../"
|
7
gemfiles/rails40.gemfile
Normal file
7
gemfiles/rails40.gemfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file was generated by Appraisal
|
||||||
|
|
||||||
|
source "http://rubygems.org"
|
||||||
|
|
||||||
|
gem "rails", "~> 4.0.0"
|
||||||
|
|
||||||
|
gemspec :path=>"../"
|
387
js/livereload.js
387
js/livereload.js
@ -123,12 +123,17 @@ __protocol.Parser = Parser = (function() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
// connector
|
// connector
|
||||||
|
// Generated by CoffeeScript 1.3.3
|
||||||
var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref;
|
var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref;
|
||||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
||||||
_ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7;
|
_ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7;
|
||||||
Version = '2.0.3';
|
|
||||||
|
Version = '2.0.8';
|
||||||
|
|
||||||
__connector.Connector = Connector = (function() {
|
__connector.Connector = Connector = (function() {
|
||||||
|
|
||||||
function Connector(options, WebSocket, Timer, handlers) {
|
function Connector(options, WebSocket, Timer, handlers) {
|
||||||
|
var _this = this;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.WebSocket = WebSocket;
|
this.WebSocket = WebSocket;
|
||||||
this.Timer = Timer;
|
this.Timer = Timer;
|
||||||
@ -138,64 +143,66 @@ __connector.Connector = Connector = (function() {
|
|||||||
this._connectionDesired = false;
|
this._connectionDesired = false;
|
||||||
this.protocol = 0;
|
this.protocol = 0;
|
||||||
this.protocolParser = new Parser({
|
this.protocolParser = new Parser({
|
||||||
connected: __bind(function(protocol) {
|
connected: function(protocol) {
|
||||||
this.protocol = protocol;
|
_this.protocol = protocol;
|
||||||
this._handshakeTimeout.stop();
|
_this._handshakeTimeout.stop();
|
||||||
this._nextDelay = this.options.mindelay;
|
_this._nextDelay = _this.options.mindelay;
|
||||||
this._disconnectionReason = 'broken';
|
_this._disconnectionReason = 'broken';
|
||||||
return this.handlers.connected(protocol);
|
return _this.handlers.connected(protocol);
|
||||||
}, this),
|
},
|
||||||
error: __bind(function(e) {
|
error: function(e) {
|
||||||
this.handlers.error(e);
|
_this.handlers.error(e);
|
||||||
return this._closeOnError();
|
return _this._closeOnError();
|
||||||
}, this),
|
},
|
||||||
message: __bind(function(message) {
|
message: function(message) {
|
||||||
return this.handlers.message(message);
|
return _this.handlers.message(message);
|
||||||
}, this)
|
}
|
||||||
});
|
});
|
||||||
this._handshakeTimeout = new Timer(__bind(function() {
|
this._handshakeTimeout = new Timer(function() {
|
||||||
if (!this._isSocketConnected()) {
|
if (!_this._isSocketConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._disconnectionReason = 'handshake-timeout';
|
_this._disconnectionReason = 'handshake-timeout';
|
||||||
return this.socket.close();
|
return _this.socket.close();
|
||||||
}, this));
|
});
|
||||||
this._reconnectTimer = new Timer(__bind(function() {
|
this._reconnectTimer = new Timer(function() {
|
||||||
if (!this._connectionDesired) {
|
if (!_this._connectionDesired) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return this.connect();
|
return _this.connect();
|
||||||
}, this));
|
});
|
||||||
this.connect();
|
this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
Connector.prototype._isSocketConnected = function() {
|
Connector.prototype._isSocketConnected = function() {
|
||||||
return this.socket && this.socket.readyState === this.WebSocket.OPEN;
|
return this.socket && this.socket.readyState === this.WebSocket.OPEN;
|
||||||
};
|
};
|
||||||
|
|
||||||
Connector.prototype.connect = function() {
|
Connector.prototype.connect = function() {
|
||||||
|
var _this = this;
|
||||||
this._connectionDesired = true;
|
this._connectionDesired = true;
|
||||||
if (this._isSocketConnected()) {
|
if (this._isSocketConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._reconnectTimer) {
|
this._reconnectTimer.stop();
|
||||||
clearTimeout(this._reconnectTimer);
|
|
||||||
}
|
|
||||||
this._disconnectionReason = 'cannot-connect';
|
this._disconnectionReason = 'cannot-connect';
|
||||||
this.protocolParser.reset();
|
this.protocolParser.reset();
|
||||||
this.handlers.connecting();
|
this.handlers.connecting();
|
||||||
this.socket = new this.WebSocket(this._uri);
|
this.socket = new this.WebSocket(this._uri);
|
||||||
this.socket.onopen = __bind(function(e) {
|
this.socket.onopen = function(e) {
|
||||||
return this._onopen(e);
|
return _this._onopen(e);
|
||||||
}, this);
|
|
||||||
this.socket.onclose = __bind(function(e) {
|
|
||||||
return this._onclose(e);
|
|
||||||
}, this);
|
|
||||||
this.socket.onmessage = __bind(function(e) {
|
|
||||||
return this._onmessage(e);
|
|
||||||
}, this);
|
|
||||||
return this.socket.onerror = __bind(function(e) {
|
|
||||||
return this._onerror(e);
|
|
||||||
}, this);
|
|
||||||
};
|
};
|
||||||
|
this.socket.onclose = function(e) {
|
||||||
|
return _this._onclose(e);
|
||||||
|
};
|
||||||
|
this.socket.onmessage = function(e) {
|
||||||
|
return _this._onmessage(e);
|
||||||
|
};
|
||||||
|
return this.socket.onerror = function(e) {
|
||||||
|
return _this._onerror(e);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
Connector.prototype.disconnect = function() {
|
Connector.prototype.disconnect = function() {
|
||||||
this._connectionDesired = false;
|
this._connectionDesired = false;
|
||||||
this._reconnectTimer.stop();
|
this._reconnectTimer.stop();
|
||||||
@ -205,6 +212,7 @@ __connector.Connector = Connector = (function() {
|
|||||||
this._disconnectionReason = 'manual';
|
this._disconnectionReason = 'manual';
|
||||||
return this.socket.close();
|
return this.socket.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
Connector.prototype._scheduleReconnection = function() {
|
Connector.prototype._scheduleReconnection = function() {
|
||||||
if (!this._connectionDesired) {
|
if (!this._connectionDesired) {
|
||||||
return;
|
return;
|
||||||
@ -214,20 +222,24 @@ __connector.Connector = Connector = (function() {
|
|||||||
return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);
|
return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Connector.prototype.sendCommand = function(command) {
|
Connector.prototype.sendCommand = function(command) {
|
||||||
if (this.protocol == null) {
|
if (this.protocol == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return this._sendCommand(command);
|
return this._sendCommand(command);
|
||||||
};
|
};
|
||||||
|
|
||||||
Connector.prototype._sendCommand = function(command) {
|
Connector.prototype._sendCommand = function(command) {
|
||||||
return this.socket.send(JSON.stringify(command));
|
return this.socket.send(JSON.stringify(command));
|
||||||
};
|
};
|
||||||
|
|
||||||
Connector.prototype._closeOnError = function() {
|
Connector.prototype._closeOnError = function() {
|
||||||
this._handshakeTimeout.stop();
|
this._handshakeTimeout.stop();
|
||||||
this._disconnectionReason = 'error';
|
this._disconnectionReason = 'error';
|
||||||
return this.socket.close();
|
return this.socket.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
Connector.prototype._onopen = function(e) {
|
Connector.prototype._onopen = function(e) {
|
||||||
var hello;
|
var hello;
|
||||||
this.handlers.socketConnected();
|
this.handlers.socketConnected();
|
||||||
@ -249,16 +261,21 @@ __connector.Connector = Connector = (function() {
|
|||||||
this._sendCommand(hello);
|
this._sendCommand(hello);
|
||||||
return this._handshakeTimeout.start(this.options.handshake_timeout);
|
return this._handshakeTimeout.start(this.options.handshake_timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
Connector.prototype._onclose = function(e) {
|
Connector.prototype._onclose = function(e) {
|
||||||
this.protocol = 0;
|
this.protocol = 0;
|
||||||
this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
|
this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
|
||||||
return this._scheduleReconnection();
|
return this._scheduleReconnection();
|
||||||
};
|
};
|
||||||
|
|
||||||
Connector.prototype._onerror = function(e) {};
|
Connector.prototype._onerror = function(e) {};
|
||||||
|
|
||||||
Connector.prototype._onmessage = function(e) {
|
Connector.prototype._onmessage = function(e) {
|
||||||
return this.protocolParser.process(e.data);
|
return this.protocolParser.process(e.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return Connector;
|
return Connector;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// timer
|
// timer
|
||||||
@ -300,7 +317,7 @@ var Options;
|
|||||||
__options.Options = Options = (function() {
|
__options.Options = Options = (function() {
|
||||||
function Options() {
|
function Options() {
|
||||||
this.host = null;
|
this.host = null;
|
||||||
this.port = 35729;
|
this.port = RACK_LIVERELOAD_PORT;
|
||||||
this.snipver = null;
|
this.snipver = null;
|
||||||
this.ext = null;
|
this.ext = null;
|
||||||
this.extver = null;
|
this.extver = null;
|
||||||
@ -349,9 +366,11 @@ Options.extract = function(document) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// reloader
|
// reloader
|
||||||
var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl;
|
// Generated by CoffeeScript 1.3.1
|
||||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
(function() {
|
||||||
splitUrl = function(url) {
|
var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl;
|
||||||
|
|
||||||
|
splitUrl = function(url) {
|
||||||
var hash, index, params;
|
var hash, index, params;
|
||||||
if ((index = url.indexOf('#')) >= 0) {
|
if ((index = url.indexOf('#')) >= 0) {
|
||||||
hash = url.slice(index);
|
hash = url.slice(index);
|
||||||
@ -370,8 +389,9 @@ splitUrl = function(url) {
|
|||||||
params: params,
|
params: params,
|
||||||
hash: hash
|
hash: hash
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
pathFromUrl = function(url) {
|
|
||||||
|
pathFromUrl = function(url) {
|
||||||
var path;
|
var path;
|
||||||
url = splitUrl(url).url;
|
url = splitUrl(url).url;
|
||||||
if (url.indexOf('file://') === 0) {
|
if (url.indexOf('file://') === 0) {
|
||||||
@ -380,8 +400,9 @@ pathFromUrl = function(url) {
|
|||||||
path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/');
|
path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/');
|
||||||
}
|
}
|
||||||
return decodeURIComponent(path);
|
return decodeURIComponent(path);
|
||||||
};
|
};
|
||||||
pickBestMatch = function(path, objects, pathFunc) {
|
|
||||||
|
pickBestMatch = function(path, objects, pathFunc) {
|
||||||
var bestMatch, object, score, _i, _len;
|
var bestMatch, object, score, _i, _len;
|
||||||
bestMatch = {
|
bestMatch = {
|
||||||
score: 0
|
score: 0
|
||||||
@ -401,8 +422,9 @@ pickBestMatch = function(path, objects, pathFunc) {
|
|||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
numberOfMatchingSegments = function(path1, path2) {
|
|
||||||
|
numberOfMatchingSegments = function(path1, path2) {
|
||||||
var comps1, comps2, eqCount, len;
|
var comps1, comps2, eqCount, len;
|
||||||
path1 = path1.replace(/^\/+/, '').toLowerCase();
|
path1 = path1.replace(/^\/+/, '').toLowerCase();
|
||||||
path2 = path2.replace(/^\/+/, '').toLowerCase();
|
path2 = path2.replace(/^\/+/, '').toLowerCase();
|
||||||
@ -417,11 +439,13 @@ numberOfMatchingSegments = function(path1, path2) {
|
|||||||
++eqCount;
|
++eqCount;
|
||||||
}
|
}
|
||||||
return eqCount;
|
return eqCount;
|
||||||
};
|
};
|
||||||
pathsMatch = function(path1, path2) {
|
|
||||||
|
pathsMatch = function(path1, path2) {
|
||||||
return numberOfMatchingSegments(path1, path2) > 0;
|
return numberOfMatchingSegments(path1, path2) > 0;
|
||||||
};
|
};
|
||||||
IMAGE_STYLES = [
|
|
||||||
|
IMAGE_STYLES = [
|
||||||
{
|
{
|
||||||
selector: 'background',
|
selector: 'background',
|
||||||
styleNames: ['backgroundImage']
|
styleNames: ['backgroundImage']
|
||||||
@ -429,25 +453,35 @@ IMAGE_STYLES = [
|
|||||||
selector: 'border',
|
selector: 'border',
|
||||||
styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage']
|
styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage']
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
__reloader.Reloader = Reloader = (function() {
|
|
||||||
|
__reloader.Reloader = Reloader = (function() {
|
||||||
|
|
||||||
|
Reloader.name = 'Reloader';
|
||||||
|
|
||||||
function Reloader(window, console, Timer) {
|
function Reloader(window, console, Timer) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.console = console;
|
this.console = console;
|
||||||
this.Timer = Timer;
|
this.Timer = Timer;
|
||||||
this.document = this.window.document;
|
this.document = this.window.document;
|
||||||
this.stylesheetGracePeriod = 200;
|
|
||||||
this.importCacheWaitPeriod = 200;
|
this.importCacheWaitPeriod = 200;
|
||||||
this.plugins = [];
|
this.plugins = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
Reloader.prototype.addPlugin = function(plugin) {
|
Reloader.prototype.addPlugin = function(plugin) {
|
||||||
return this.plugins.push(plugin);
|
return this.plugins.push(plugin);
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.analyze = function(callback) {
|
Reloader.prototype.analyze = function(callback) {
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.reload = function(path, options) {
|
Reloader.prototype.reload = function(path, options) {
|
||||||
var plugin, _i, _len, _ref;
|
var plugin, _base, _i, _len, _ref;
|
||||||
|
this.options = options;
|
||||||
|
if ((_base = this.options).stylesheetReloadTimeout == null) {
|
||||||
|
_base.stylesheetReloadTimeout = 15000;
|
||||||
|
}
|
||||||
_ref = this.plugins;
|
_ref = this.plugins;
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
plugin = _ref[_i];
|
plugin = _ref[_i];
|
||||||
@ -470,11 +504,13 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
}
|
}
|
||||||
return this.reloadPage();
|
return this.reloadPage();
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.reloadPage = function() {
|
Reloader.prototype.reloadPage = function() {
|
||||||
return this.window.document.location.reload();
|
return this.window.document.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.reloadImages = function(path) {
|
Reloader.prototype.reloadImages = function(path) {
|
||||||
var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref2, _ref3, _ref4, _results;
|
var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results;
|
||||||
expando = this.generateUniqueString();
|
expando = this.generateUniqueString();
|
||||||
_ref = this.document.images;
|
_ref = this.document.images;
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
@ -484,27 +520,28 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.document.querySelectorAll) {
|
if (this.document.querySelectorAll) {
|
||||||
for (_j = 0, _len2 = IMAGE_STYLES.length; _j < _len2; _j++) {
|
for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) {
|
||||||
_ref2 = IMAGE_STYLES[_j], selector = _ref2.selector, styleNames = _ref2.styleNames;
|
_ref1 = IMAGE_STYLES[_j], selector = _ref1.selector, styleNames = _ref1.styleNames;
|
||||||
_ref3 = this.document.querySelectorAll("[style*=" + selector + "]");
|
_ref2 = this.document.querySelectorAll("[style*=" + selector + "]");
|
||||||
for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) {
|
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
||||||
img = _ref3[_k];
|
img = _ref2[_k];
|
||||||
this.reloadStyleImages(img.style, styleNames, path, expando);
|
this.reloadStyleImages(img.style, styleNames, path, expando);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.document.styleSheets) {
|
if (this.document.styleSheets) {
|
||||||
_ref4 = this.document.styleSheets;
|
_ref3 = this.document.styleSheets;
|
||||||
_results = [];
|
_results = [];
|
||||||
for (_l = 0, _len4 = _ref4.length; _l < _len4; _l++) {
|
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
|
||||||
styleSheet = _ref4[_l];
|
styleSheet = _ref3[_l];
|
||||||
_results.push(this.reloadStylesheetImages(styleSheet, path, expando));
|
_results.push(this.reloadStylesheetImages(styleSheet, path, expando));
|
||||||
}
|
}
|
||||||
return _results;
|
return _results;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) {
|
Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) {
|
||||||
var rule, rules, styleNames, _i, _j, _len, _len2;
|
var rule, rules, styleNames, _i, _j, _len, _len1;
|
||||||
try {
|
try {
|
||||||
rules = styleSheet != null ? styleSheet.cssRules : void 0;
|
rules = styleSheet != null ? styleSheet.cssRules : void 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -520,7 +557,7 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
this.reloadStylesheetImages(rule.styleSheet, path, expando);
|
this.reloadStylesheetImages(rule.styleSheet, path, expando);
|
||||||
break;
|
break;
|
||||||
case CSSRule.STYLE_RULE:
|
case CSSRule.STYLE_RULE:
|
||||||
for (_j = 0, _len2 = IMAGE_STYLES.length; _j < _len2; _j++) {
|
for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) {
|
||||||
styleNames = IMAGE_STYLES[_j].styleNames;
|
styleNames = IMAGE_STYLES[_j].styleNames;
|
||||||
this.reloadStyleImages(rule.style, styleNames, path, expando);
|
this.reloadStyleImages(rule.style, styleNames, path, expando);
|
||||||
}
|
}
|
||||||
@ -530,27 +567,31 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) {
|
Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) {
|
||||||
var newValue, styleName, value, _i, _len;
|
var newValue, styleName, value, _i, _len,
|
||||||
|
_this = this;
|
||||||
for (_i = 0, _len = styleNames.length; _i < _len; _i++) {
|
for (_i = 0, _len = styleNames.length; _i < _len; _i++) {
|
||||||
styleName = styleNames[_i];
|
styleName = styleNames[_i];
|
||||||
value = style[styleName];
|
value = style[styleName];
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
newValue = value.replace(/\burl\s*\(([^)]*)\)/, __bind(function(match, src) {
|
newValue = value.replace(/\burl\s*\(([^)]*)\)/, function(match, src) {
|
||||||
if (pathsMatch(path, pathFromUrl(src))) {
|
if (pathsMatch(path, pathFromUrl(src))) {
|
||||||
return "url(" + (this.generateCacheBustUrl(src, expando)) + ")";
|
return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")";
|
||||||
} else {
|
} else {
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
}, this));
|
});
|
||||||
if (newValue !== value) {
|
if (newValue !== value) {
|
||||||
style[styleName] = newValue;
|
style[styleName] = newValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.reloadStylesheet = function(path) {
|
Reloader.prototype.reloadStylesheet = function(path) {
|
||||||
var imported, link, links, match, style, _i, _j, _k, _len, _len2, _len3, _ref;
|
var imported, link, links, match, style, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1,
|
||||||
|
_this = this;
|
||||||
links = (function() {
|
links = (function() {
|
||||||
var _i, _len, _ref, _results;
|
var _i, _len, _ref, _results;
|
||||||
_ref = this.document.getElementsByTagName('link');
|
_ref = this.document.getElementsByTagName('link');
|
||||||
@ -571,40 +612,48 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
this.collectImportedStylesheets(style, style.sheet, imported);
|
this.collectImportedStylesheets(style, style.sheet, imported);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (_j = 0, _len2 = links.length; _j < _len2; _j++) {
|
for (_j = 0, _len1 = links.length; _j < _len1; _j++) {
|
||||||
link = links[_j];
|
link = links[_j];
|
||||||
this.collectImportedStylesheets(link, link.sheet, imported);
|
this.collectImportedStylesheets(link, link.sheet, imported);
|
||||||
}
|
}
|
||||||
|
if (this.window.StyleFix && this.document.querySelectorAll) {
|
||||||
|
_ref1 = this.document.querySelectorAll('style[data-href]');
|
||||||
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||||
|
style = _ref1[_k];
|
||||||
|
links.push(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets");
|
this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets");
|
||||||
match = pickBestMatch(path, links.concat(imported), function(l) {
|
match = pickBestMatch(path, links.concat(imported), function(l) {
|
||||||
return pathFromUrl(l.href);
|
return pathFromUrl(_this.linkHref(l));
|
||||||
});
|
});
|
||||||
if (match) {
|
if (match) {
|
||||||
if (match.object.rule) {
|
if (match.object.rule) {
|
||||||
this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href);
|
this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href);
|
||||||
this.reattachImportedRule(match.object);
|
this.reattachImportedRule(match.object);
|
||||||
} else {
|
} else {
|
||||||
this.console.log("LiveReload is reloading stylesheet: " + match.object.href);
|
this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object)));
|
||||||
this.reattachStylesheetLink(match.object);
|
this.reattachStylesheetLink(match.object);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one");
|
this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one");
|
||||||
for (_k = 0, _len3 = links.length; _k < _len3; _k++) {
|
for (_l = 0, _len3 = links.length; _l < _len3; _l++) {
|
||||||
link = links[_k];
|
link = links[_l];
|
||||||
this.reattachStylesheetLink(link);
|
this.reattachStylesheetLink(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) {
|
Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) {
|
||||||
var index, rule, rules, _len;
|
var index, rule, rules, _i, _len;
|
||||||
try {
|
try {
|
||||||
rules = styleSheet != null ? styleSheet.cssRules : void 0;
|
rules = styleSheet != null ? styleSheet.cssRules : void 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
if (rules && rules.length) {
|
if (rules && rules.length) {
|
||||||
for (index = 0, _len = rules.length; index < _len; index++) {
|
for (index = _i = 0, _len = rules.length; _i < _len; index = ++_i) {
|
||||||
rule = rules[index];
|
rule = rules[index];
|
||||||
switch (rule.type) {
|
switch (rule.type) {
|
||||||
case CSSRule.CHARSET_RULE:
|
case CSSRule.CHARSET_RULE:
|
||||||
@ -624,29 +673,84 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reloader.prototype.waitUntilCssLoads = function(clone, func) {
|
||||||
|
var callbackExecuted, executeCallback, poll,
|
||||||
|
_this = this;
|
||||||
|
callbackExecuted = false;
|
||||||
|
executeCallback = function() {
|
||||||
|
if (callbackExecuted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callbackExecuted = true;
|
||||||
|
return func();
|
||||||
|
};
|
||||||
|
clone.onload = function() {
|
||||||
|
console.log("onload!");
|
||||||
|
_this.knownToSupportCssOnLoad = true;
|
||||||
|
return executeCallback();
|
||||||
|
};
|
||||||
|
if (!this.knownToSupportCssOnLoad) {
|
||||||
|
(poll = function() {
|
||||||
|
if (clone.sheet) {
|
||||||
|
console.log("polling!");
|
||||||
|
return executeCallback();
|
||||||
|
} else {
|
||||||
|
return _this.Timer.start(50, poll);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Reloader.prototype.linkHref = function(link) {
|
||||||
|
return link.href || link.getAttribute('data-href');
|
||||||
|
};
|
||||||
|
|
||||||
Reloader.prototype.reattachStylesheetLink = function(link) {
|
Reloader.prototype.reattachStylesheetLink = function(link) {
|
||||||
var clone, parent, timer;
|
var clone, parent,
|
||||||
|
_this = this;
|
||||||
if (link.__LiveReload_pendingRemoval) {
|
if (link.__LiveReload_pendingRemoval) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
link.__LiveReload_pendingRemoval = true;
|
link.__LiveReload_pendingRemoval = true;
|
||||||
|
if (link.tagName === 'STYLE') {
|
||||||
|
clone = this.document.createElement('link');
|
||||||
|
clone.rel = 'stylesheet';
|
||||||
|
clone.media = link.media;
|
||||||
|
clone.disabled = link.disabled;
|
||||||
|
} else {
|
||||||
clone = link.cloneNode(false);
|
clone = link.cloneNode(false);
|
||||||
clone.href = this.generateCacheBustUrl(link.href);
|
}
|
||||||
|
clone.href = this.generateCacheBustUrl(this.linkHref(link));
|
||||||
parent = link.parentNode;
|
parent = link.parentNode;
|
||||||
if (parent.lastChild === link) {
|
if (parent.lastChild === link) {
|
||||||
parent.appendChild(clone);
|
parent.appendChild(clone);
|
||||||
} else {
|
} else {
|
||||||
parent.insertBefore(clone, link.nextSibling);
|
parent.insertBefore(clone, link.nextSibling);
|
||||||
}
|
}
|
||||||
timer = new this.Timer(function() {
|
return this.waitUntilCssLoads(clone, function() {
|
||||||
if (link.parentNode) {
|
var additionalWaitingTime;
|
||||||
return link.parentNode.removeChild(link);
|
if (/AppleWebKit/.test(navigator.userAgent)) {
|
||||||
|
additionalWaitingTime = 5;
|
||||||
|
} else {
|
||||||
|
additionalWaitingTime = 200;
|
||||||
}
|
}
|
||||||
|
return _this.Timer.start(additionalWaitingTime, function() {
|
||||||
|
var _ref;
|
||||||
|
if (!link.parentNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
link.parentNode.removeChild(link);
|
||||||
|
clone.onreadystatechange = null;
|
||||||
|
return (_ref = _this.window.StyleFix) != null ? _ref.link(clone) : void 0;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return timer.start(this.stylesheetGracePeriod);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.reattachImportedRule = function(_arg) {
|
Reloader.prototype.reattachImportedRule = function(_arg) {
|
||||||
var href, index, link, media, newRule, parent, rule, tempLink;
|
var href, index, link, media, newRule, parent, rule, tempLink,
|
||||||
|
_this = this;
|
||||||
rule = _arg.rule, index = _arg.index, link = _arg.link;
|
rule = _arg.rule, index = _arg.index, link = _arg.link;
|
||||||
parent = rule.parentStyleSheet;
|
parent = rule.parentStyleSheet;
|
||||||
href = this.generateCacheBustUrl(rule.href);
|
href = this.generateCacheBustUrl(rule.href);
|
||||||
@ -660,7 +764,7 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
if (link.parentNode) {
|
if (link.parentNode) {
|
||||||
link.parentNode.insertBefore(tempLink, link);
|
link.parentNode.insertBefore(tempLink, link);
|
||||||
}
|
}
|
||||||
return this.Timer.start(this.importCacheWaitPeriod, __bind(function() {
|
return this.Timer.start(this.importCacheWaitPeriod, function() {
|
||||||
if (tempLink.parentNode) {
|
if (tempLink.parentNode) {
|
||||||
tempLink.parentNode.removeChild(tempLink);
|
tempLink.parentNode.removeChild(tempLink);
|
||||||
}
|
}
|
||||||
@ -671,24 +775,31 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
parent.deleteRule(index + 1);
|
parent.deleteRule(index + 1);
|
||||||
rule = parent.cssRules[index];
|
rule = parent.cssRules[index];
|
||||||
rule.__LiveReload_newHref = href;
|
rule.__LiveReload_newHref = href;
|
||||||
return this.Timer.start(this.importCacheWaitPeriod, __bind(function() {
|
return _this.Timer.start(_this.importCacheWaitPeriod, function() {
|
||||||
if (rule.__LiveReload_newHref !== href) {
|
if (rule.__LiveReload_newHref !== href) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parent.insertRule(newRule, index);
|
parent.insertRule(newRule, index);
|
||||||
return parent.deleteRule(index + 1);
|
return parent.deleteRule(index + 1);
|
||||||
}, this));
|
});
|
||||||
}, this));
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.generateUniqueString = function() {
|
Reloader.prototype.generateUniqueString = function() {
|
||||||
return 'livereload=' + Date.now();
|
return 'livereload=' + Date.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
Reloader.prototype.generateCacheBustUrl = function(url, expando) {
|
Reloader.prototype.generateCacheBustUrl = function(url, expando) {
|
||||||
var hash, oldParams, params, _ref;
|
var hash, oldParams, params, _ref;
|
||||||
if (expando == null) {
|
if (expando == null) {
|
||||||
expando = this.generateUniqueString();
|
expando = this.generateUniqueString();
|
||||||
}
|
}
|
||||||
_ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params;
|
_ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params;
|
||||||
|
if (this.options.overrideURL) {
|
||||||
|
if (url.indexOf(this.options.serverURL) < 0) {
|
||||||
|
url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) {
|
params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) {
|
||||||
return "" + sep + expando;
|
return "" + sep + expando;
|
||||||
});
|
});
|
||||||
@ -701,23 +812,33 @@ __reloader.Reloader = Reloader = (function() {
|
|||||||
}
|
}
|
||||||
return url + params + hash;
|
return url + params + hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
return Reloader;
|
return Reloader;
|
||||||
})();
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
|
|
||||||
// livereload
|
// livereload
|
||||||
var Connector, LiveReload, Options, Reloader, Timer;
|
var Connector, LiveReload, Options, Reloader, Timer;
|
||||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
||||||
Connector = __connector.Connector;
|
Connector = __connector.Connector;
|
||||||
|
|
||||||
Timer = __timer.Timer;
|
Timer = __timer.Timer;
|
||||||
|
|
||||||
Options = __options.Options;
|
Options = __options.Options;
|
||||||
|
|
||||||
Reloader = __reloader.Reloader;
|
Reloader = __reloader.Reloader;
|
||||||
|
|
||||||
__livereload.LiveReload = LiveReload = (function() {
|
__livereload.LiveReload = LiveReload = (function() {
|
||||||
|
|
||||||
function LiveReload(window) {
|
function LiveReload(window) {
|
||||||
|
var _this = this;
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.listeners = {};
|
this.listeners = {};
|
||||||
this.plugins = [];
|
this.plugins = [];
|
||||||
this.pluginIdentifiers = {};
|
this.pluginIdentifiers = {};
|
||||||
this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.console : {
|
this.console = this.window.location.href.match(/LR-verbose/) && this.window.console && this.window.console.log && this.window.console.error ? this.window.console : {
|
||||||
log: function() {},
|
log: function() {},
|
||||||
error: function() {}
|
error: function() {}
|
||||||
};
|
};
|
||||||
@ -731,87 +852,95 @@ __livereload.LiveReload = LiveReload = (function() {
|
|||||||
}
|
}
|
||||||
this.reloader = new Reloader(this.window, this.console, Timer);
|
this.reloader = new Reloader(this.window, this.console, Timer);
|
||||||
this.connector = new Connector(this.options, this.WebSocket, Timer, {
|
this.connector = new Connector(this.options, this.WebSocket, Timer, {
|
||||||
connecting: __bind(function() {}, this),
|
connecting: function() {},
|
||||||
socketConnected: __bind(function() {}, this),
|
socketConnected: function() {},
|
||||||
connected: __bind(function(protocol) {
|
connected: function(protocol) {
|
||||||
var _base;
|
var _base;
|
||||||
if (typeof (_base = this.listeners).connect === "function") {
|
if (typeof (_base = _this.listeners).connect === "function") {
|
||||||
_base.connect();
|
_base.connect();
|
||||||
}
|
}
|
||||||
this.log("LiveReload is connected to " + this.options.host + ":" + this.options.port + " (protocol v" + protocol + ").");
|
_this.log("LiveReload is connected to " + _this.options.host + ":" + _this.options.port + " (protocol v" + protocol + ").");
|
||||||
return this.analyze();
|
return _this.analyze();
|
||||||
}, this),
|
},
|
||||||
error: __bind(function(e) {
|
error: function(e) {
|
||||||
if (e instanceof ProtocolError) {
|
if (e instanceof ProtocolError) {
|
||||||
return console.log("" + e.message + ".");
|
return console.log("" + e.message + ".");
|
||||||
} else {
|
} else {
|
||||||
return console.log("LiveReload internal error: " + e.message);
|
return console.log("LiveReload internal error: " + e.message);
|
||||||
}
|
}
|
||||||
}, this),
|
},
|
||||||
disconnected: __bind(function(reason, nextDelay) {
|
disconnected: function(reason, nextDelay) {
|
||||||
var _base;
|
var _base;
|
||||||
if (typeof (_base = this.listeners).disconnect === "function") {
|
if (typeof (_base = _this.listeners).disconnect === "function") {
|
||||||
_base.disconnect();
|
_base.disconnect();
|
||||||
}
|
}
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
case 'cannot-connect':
|
case 'cannot-connect':
|
||||||
return this.log("LiveReload cannot connect to " + this.options.host + ":" + this.options.port + ", will retry in " + nextDelay + " sec.");
|
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + ", will retry in " + nextDelay + " sec.");
|
||||||
case 'broken':
|
case 'broken':
|
||||||
return this.log("LiveReload disconnected from " + this.options.host + ":" + this.options.port + ", reconnecting in " + nextDelay + " sec.");
|
return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + ", reconnecting in " + nextDelay + " sec.");
|
||||||
case 'handshake-timeout':
|
case 'handshake-timeout':
|
||||||
return this.log("LiveReload cannot connect to " + this.options.host + ":" + this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec.");
|
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec.");
|
||||||
case 'handshake-failed':
|
case 'handshake-failed':
|
||||||
return this.log("LiveReload cannot connect to " + this.options.host + ":" + this.options.port + " (handshake failed), will retry in " + nextDelay + " sec.");
|
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake failed), will retry in " + nextDelay + " sec.");
|
||||||
case 'manual':
|
case 'manual':
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return this.log("LiveReload disconnected from " + this.options.host + ":" + this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec.");
|
return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec.");
|
||||||
}
|
}
|
||||||
}, this),
|
},
|
||||||
message: __bind(function(message) {
|
message: function(message) {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'reload':
|
case 'reload':
|
||||||
return this.performReload(message);
|
return _this.performReload(message);
|
||||||
case 'alert':
|
case 'alert':
|
||||||
return this.performAlert(message);
|
return _this.performAlert(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, this)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveReload.prototype.on = function(eventName, handler) {
|
LiveReload.prototype.on = function(eventName, handler) {
|
||||||
return this.listeners[eventName] = handler;
|
return this.listeners[eventName] = handler;
|
||||||
};
|
};
|
||||||
|
|
||||||
LiveReload.prototype.log = function(message) {
|
LiveReload.prototype.log = function(message) {
|
||||||
return this.console.log("" + message);
|
return this.console.log("" + message);
|
||||||
};
|
};
|
||||||
|
|
||||||
LiveReload.prototype.performReload = function(message) {
|
LiveReload.prototype.performReload = function(message) {
|
||||||
var _ref, _ref2;
|
var _ref, _ref2;
|
||||||
this.log("LiveReload received reload request for " + message.path + ".");
|
this.log("LiveReload received reload request for " + message.path + ".");
|
||||||
return this.reloader.reload(message.path, {
|
return this.reloader.reload(message.path, {
|
||||||
liveCSS: (_ref = message.liveCSS) != null ? _ref : true,
|
liveCSS: (_ref = message.liveCSS) != null ? _ref : true,
|
||||||
liveImg: (_ref2 = message.liveImg) != null ? _ref2 : true,
|
liveImg: (_ref2 = message.liveImg) != null ? _ref2 : true,
|
||||||
originalPath: message.originalPath || ''
|
originalPath: message.originalPath || '',
|
||||||
|
overrideURL: message.overrideURL || '',
|
||||||
|
serverURL: "http://" + this.options.host + ":" + this.options.port
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
LiveReload.prototype.performAlert = function(message) {
|
LiveReload.prototype.performAlert = function(message) {
|
||||||
return alert(message.message);
|
return alert(message.message);
|
||||||
};
|
};
|
||||||
|
|
||||||
LiveReload.prototype.shutDown = function() {
|
LiveReload.prototype.shutDown = function() {
|
||||||
var _base;
|
var _base;
|
||||||
this.connector.disconnect();
|
this.connector.disconnect();
|
||||||
this.log("LiveReload disconnected.");
|
this.log("LiveReload disconnected.");
|
||||||
return typeof (_base = this.listeners).shutdown === "function" ? _base.shutdown() : void 0;
|
return typeof (_base = this.listeners).shutdown === "function" ? _base.shutdown() : void 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
LiveReload.prototype.hasPlugin = function(identifier) {
|
LiveReload.prototype.hasPlugin = function(identifier) {
|
||||||
return !!this.pluginIdentifiers[identifier];
|
return !!this.pluginIdentifiers[identifier];
|
||||||
};
|
};
|
||||||
|
|
||||||
LiveReload.prototype.addPlugin = function(pluginClass) {
|
LiveReload.prototype.addPlugin = function(pluginClass) {
|
||||||
var plugin;
|
var plugin;
|
||||||
if (this.hasPlugin(pluginClass.identifier)) {
|
var _this = this;
|
||||||
return;
|
if (this.hasPlugin(pluginClass.identifier)) return;
|
||||||
}
|
|
||||||
this.pluginIdentifiers[pluginClass.identifier] = true;
|
this.pluginIdentifiers[pluginClass.identifier] = true;
|
||||||
plugin = new pluginClass(this.window, {
|
plugin = new pluginClass(this.window, {
|
||||||
_livereload: this,
|
_livereload: this,
|
||||||
@ -819,18 +948,17 @@ __livereload.LiveReload = LiveReload = (function() {
|
|||||||
_connector: this.connector,
|
_connector: this.connector,
|
||||||
console: this.console,
|
console: this.console,
|
||||||
Timer: Timer,
|
Timer: Timer,
|
||||||
generateCacheBustUrl: __bind(function(url) {
|
generateCacheBustUrl: function(url) {
|
||||||
return this.reloader.generateCacheBustUrl(url);
|
return _this.reloader.generateCacheBustUrl(url);
|
||||||
}, this)
|
}
|
||||||
});
|
});
|
||||||
this.plugins.push(plugin);
|
this.plugins.push(plugin);
|
||||||
this.reloader.addPlugin(plugin);
|
this.reloader.addPlugin(plugin);
|
||||||
};
|
};
|
||||||
|
|
||||||
LiveReload.prototype.analyze = function() {
|
LiveReload.prototype.analyze = function() {
|
||||||
var plugin, pluginData, pluginsData, _i, _len, _ref;
|
var plugin, pluginData, pluginsData, _i, _len, _ref;
|
||||||
if (!(this.connector.protocol >= 7)) {
|
if (!(this.connector.protocol >= 7)) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
pluginsData = {};
|
pluginsData = {};
|
||||||
_ref = this.plugins;
|
_ref = this.plugins;
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
@ -844,7 +972,9 @@ __livereload.LiveReload = LiveReload = (function() {
|
|||||||
url: this.window.location.href
|
url: this.window.location.href
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return LiveReload;
|
return LiveReload;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// less
|
// less
|
||||||
@ -901,13 +1031,12 @@ __less = LessPlugin = (function() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
// startup
|
// startup
|
||||||
var CustomEvents, LiveReload, k, v;
|
var CustomEvents, LiveReload, k;
|
||||||
CustomEvents = __customevents;
|
CustomEvents = __customevents;
|
||||||
LiveReload = window.LiveReload = new (__livereload.LiveReload)(window);
|
LiveReload = window.LiveReload = new (__livereload.LiveReload)(window);
|
||||||
for (k in window) {
|
for (k in window) {
|
||||||
v = window[k];
|
|
||||||
if (k.match(/^LiveReloadPlugin/)) {
|
if (k.match(/^LiveReloadPlugin/)) {
|
||||||
LiveReload.addPlugin(v);
|
LiveReload.addPlugin(window[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LiveReload.addPlugin(__less);
|
LiveReload.addPlugin(__less);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
require "rack/livereload"
|
require "rack/livereload"
|
||||||
|
|
||||||
class Rack::LiveReload
|
class Rack::LiveReload
|
||||||
VERSION = '0.3.5'
|
VERSION = '0.3.16'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,46 +1,15 @@
|
|||||||
require 'erb'
|
require 'erb'
|
||||||
|
require 'rack/livereload/processing_skip_analyzer'
|
||||||
|
require 'rack/livereload/body_processor'
|
||||||
|
|
||||||
module Rack
|
module Rack
|
||||||
class LiveReload
|
class LiveReload
|
||||||
LIVERELOAD_JS_PATH = '/__rack/livereload.js'
|
|
||||||
LIVERELOAD_LOCAL_URI = 'http://localhost:35729/livereload.js'
|
|
||||||
|
|
||||||
BAD_USER_AGENTS = [ %r{MSIE} ]
|
|
||||||
|
|
||||||
attr_reader :app
|
attr_reader :app
|
||||||
|
|
||||||
def initialize(app, options = {})
|
def initialize(app, options = {})
|
||||||
@app, @options = app, options
|
@app, @options = app, options
|
||||||
end
|
end
|
||||||
|
|
||||||
def use_vendored?
|
|
||||||
return @use_vendored if @use_vendored
|
|
||||||
|
|
||||||
if @options[:source]
|
|
||||||
@use_vendored = (@options[:source] == :vendored)
|
|
||||||
else
|
|
||||||
require 'net/http'
|
|
||||||
require 'uri'
|
|
||||||
|
|
||||||
uri = URI.parse(LIVERELOAD_LOCAL_URI)
|
|
||||||
|
|
||||||
http = Net::HTTP.new(uri.host, uri.port)
|
|
||||||
http.read_timeout = 1
|
|
||||||
|
|
||||||
begin
|
|
||||||
http.send_request('GET', uri.path)
|
|
||||||
@use_vendored = false
|
|
||||||
rescue Timeout::Error, Errno::ECONNREFUSED, EOFError
|
|
||||||
@use_vendored = true
|
|
||||||
rescue => e
|
|
||||||
$stderr.puts e.inspect
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@use_vendored
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
dup._call(env)
|
dup._call(env)
|
||||||
end
|
end
|
||||||
@ -51,58 +20,21 @@ module Rack
|
|||||||
if path == '__rack' && ::File.file?(target = ::File.expand_path("../../../js/#{file}", __FILE__))
|
if path == '__rack' && ::File.file?(target = ::File.expand_path("../../../js/#{file}", __FILE__))
|
||||||
deliver_file(target)
|
deliver_file(target)
|
||||||
else
|
else
|
||||||
status, headers, body = @app.call(env)
|
status, headers, body = result = @app.call(env)
|
||||||
|
|
||||||
new_body = []
|
return result if ProcessingSkipAnalyzer.skip_processing?(result, env, @options)
|
||||||
|
|
||||||
body.each do |line|
|
processor = BodyProcessor.new(body, @options)
|
||||||
new_body << line
|
processor.process!(env)
|
||||||
|
|
||||||
|
headers['Content-Length'] = processor.content_length.to_s
|
||||||
|
|
||||||
|
if processor.livereload_added
|
||||||
|
headers['X-Rack-LiveReload'] = '1'
|
||||||
end
|
end
|
||||||
|
|
||||||
body.close if body.respond_to?(:close)
|
[ status, headers, processor.new_body ]
|
||||||
|
|
||||||
if !ignored?(env['PATH_INFO']) && !bad_browser?(env['HTTP_USER_AGENT'])
|
|
||||||
if headers['Content-Type'] && headers['Content-Type'][%r{text/html}]
|
|
||||||
content_length = 0
|
|
||||||
|
|
||||||
new_body.each do |line|
|
|
||||||
if !headers['X-Rack-LiveReload'] && line['</head>']
|
|
||||||
host_to_use = (@options[:host] || env['HTTP_HOST'] || 'localhost').gsub(%r{:.*}, '')
|
|
||||||
|
|
||||||
if use_vendored?
|
|
||||||
src = LIVERELOAD_JS_PATH.dup + "?host=#{host_to_use}"
|
|
||||||
else
|
|
||||||
src = LIVERELOAD_LOCAL_URI.dup.gsub('localhost', host_to_use) + '?'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
src << "&mindelay=#{@options[:min_delay]}" if @options[:min_delay]
|
|
||||||
src << "&maxdelay=#{@options[:max_delay]}" if @options[:max_delay]
|
|
||||||
src << "&port=#{@options[:port]}" if @options[:port]
|
|
||||||
|
|
||||||
template = ERB.new(::File.read(::File.expand_path('../../../skel/livereload.html.erb', __FILE__)))
|
|
||||||
|
|
||||||
line.gsub!('</head>', %{#{template.result(binding)}</head>})
|
|
||||||
|
|
||||||
headers["X-Rack-LiveReload"] = '1'
|
|
||||||
end
|
|
||||||
|
|
||||||
content_length += line.bytesize
|
|
||||||
end
|
|
||||||
|
|
||||||
headers['Content-Length'] = content_length.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
[ status, headers, new_body ]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def ignored?(path_info)
|
|
||||||
@options[:ignore] and @options[:ignore].any? { |filter| path_info[filter] }
|
|
||||||
end
|
|
||||||
|
|
||||||
def bad_browser?(user_agent)
|
|
||||||
BAD_USER_AGENTS.any? { |pattern| (user_agent || '')[pattern] }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -116,14 +48,6 @@ module Rack
|
|||||||
|
|
||||||
[ 200, { 'Content-Type' => type, 'Content-Length' => ::File.size(file).to_s }, [ ::File.read(file) ] ]
|
[ 200, { 'Content-Type' => type, 'Content-Length' => ::File.size(file).to_s }, [ ::File.read(file) ] ]
|
||||||
end
|
end
|
||||||
|
|
||||||
def force_swf?
|
|
||||||
@options[:force_swf]
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_swf?
|
|
||||||
!@options[:no_swf]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
116
lib/rack/livereload/body_processor.rb
Normal file
116
lib/rack/livereload/body_processor.rb
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
require 'rack/livereload'
|
||||||
|
|
||||||
|
module Rack
|
||||||
|
class LiveReload
|
||||||
|
class BodyProcessor
|
||||||
|
LIVERELOAD_JS_PATH = '/__rack/livereload.js'
|
||||||
|
HEAD_TAG_REGEX = /<head>|<head[^(er)][^<]*>/
|
||||||
|
LIVERELOAD_PORT = 35729
|
||||||
|
|
||||||
|
attr_reader :content_length, :new_body, :livereload_added
|
||||||
|
|
||||||
|
def protocol
|
||||||
|
@options[:protocol] || "http"
|
||||||
|
end
|
||||||
|
|
||||||
|
def livereload_local_uri
|
||||||
|
"#{protocol}://localhost:#{@options[:live_reload_port]}/livereload.js"
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(body, options)
|
||||||
|
@body, @options = body, options
|
||||||
|
@options[:live_reload_port] ||= LIVERELOAD_PORT
|
||||||
|
|
||||||
|
@processed = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def force_swf?
|
||||||
|
@options[:force_swf]
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_swf?
|
||||||
|
!@options[:no_swf]
|
||||||
|
end
|
||||||
|
|
||||||
|
def use_vendored?
|
||||||
|
return @use_vendored if @use_vendored
|
||||||
|
|
||||||
|
if @options[:source]
|
||||||
|
@use_vendored = (@options[:source] == :vendored)
|
||||||
|
else
|
||||||
|
require 'net/http'
|
||||||
|
require 'uri'
|
||||||
|
|
||||||
|
uri = URI.parse(livereload_local_uri)
|
||||||
|
|
||||||
|
http = Net::HTTP.new(uri.host, uri.port)
|
||||||
|
http.read_timeout = 1
|
||||||
|
|
||||||
|
begin
|
||||||
|
http.send_request('GET', uri.path)
|
||||||
|
@use_vendored = false
|
||||||
|
rescue ::Timeout::Error, Errno::ECONNREFUSED, EOFError, IOError
|
||||||
|
@use_vendored = true
|
||||||
|
rescue => e
|
||||||
|
$stderr.puts e.inspect
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@use_vendored
|
||||||
|
end
|
||||||
|
|
||||||
|
def processed?
|
||||||
|
@processed
|
||||||
|
end
|
||||||
|
|
||||||
|
def process!(env)
|
||||||
|
@env = env
|
||||||
|
@body.close if @body.respond_to?(:close)
|
||||||
|
|
||||||
|
@new_body = [] ; @body.each { |line| @new_body << line.to_s }
|
||||||
|
|
||||||
|
@content_length = 0
|
||||||
|
@livereload_added = false
|
||||||
|
|
||||||
|
@new_body.each do |line|
|
||||||
|
if !@livereload_added && line['<head']
|
||||||
|
line.gsub!(HEAD_TAG_REGEX) { |match| %{#{match}#{template.result(binding)}} }
|
||||||
|
|
||||||
|
@livereload_added = true
|
||||||
|
end
|
||||||
|
|
||||||
|
@content_length += line.bytesize
|
||||||
|
@processed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def app_root
|
||||||
|
ENV['RAILS_RELATIVE_URL_ROOT'] || ''
|
||||||
|
end
|
||||||
|
|
||||||
|
def host_to_use
|
||||||
|
(@options[:host] || @env['HTTP_HOST'] || 'localhost').gsub(%r{:.*}, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
def template
|
||||||
|
ERB.new(::File.read(::File.expand_path('../../../../skel/livereload.html.erb', __FILE__)))
|
||||||
|
end
|
||||||
|
|
||||||
|
def livereload_source
|
||||||
|
if use_vendored?
|
||||||
|
src = "#{app_root}#{LIVERELOAD_JS_PATH.dup}?host=#{host_to_use}"
|
||||||
|
else
|
||||||
|
src = livereload_local_uri.dup.gsub('localhost', host_to_use) + '?'
|
||||||
|
end
|
||||||
|
|
||||||
|
src << "&mindelay=#{@options[:min_delay]}" if @options[:min_delay]
|
||||||
|
src << "&maxdelay=#{@options[:max_delay]}" if @options[:max_delay]
|
||||||
|
src << "&port=#{@options[:port]}" if @options[:port]
|
||||||
|
|
||||||
|
src
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
49
lib/rack/livereload/processing_skip_analyzer.rb
Normal file
49
lib/rack/livereload/processing_skip_analyzer.rb
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
require 'rack/livereload'
|
||||||
|
|
||||||
|
module Rack
|
||||||
|
class LiveReload
|
||||||
|
class ProcessingSkipAnalyzer
|
||||||
|
BAD_USER_AGENTS = [ %r{MSIE} ]
|
||||||
|
|
||||||
|
def self.skip_processing?(result, env, options)
|
||||||
|
new(result, env, options).skip_processing?
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(result, env, options)
|
||||||
|
@env, @options = env, options
|
||||||
|
|
||||||
|
@status, @headers, @body = result
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip_processing?
|
||||||
|
!html? || chunked? || inline? || ignored? || bad_browser? || !get?
|
||||||
|
end
|
||||||
|
|
||||||
|
def chunked?
|
||||||
|
@headers['Transfer-Encoding'] == 'chunked'
|
||||||
|
end
|
||||||
|
|
||||||
|
def inline?
|
||||||
|
@headers['Content-Disposition'] =~ %r{^inline}
|
||||||
|
end
|
||||||
|
|
||||||
|
def ignored?
|
||||||
|
path = @env['QUERY_STRING'].empty? ? @env['PATH_INFO'] : "#{@env['PATH_INFO']}?#{@env['QUERY_STRING']}"
|
||||||
|
@options[:ignore] and @options[:ignore].any? { |filter| path[filter] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def bad_browser?
|
||||||
|
BAD_USER_AGENTS.any? { |pattern| @env['HTTP_USER_AGENT'] =~ pattern }
|
||||||
|
end
|
||||||
|
|
||||||
|
def html?
|
||||||
|
@headers['Content-Type'] =~ %r{text/html}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get?
|
||||||
|
@env['REQUEST_METHOD'] == 'GET'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -8,6 +8,7 @@ Gem::Specification.new do |s|
|
|||||||
s.authors = ["John Bintz"]
|
s.authors = ["John Bintz"]
|
||||||
s.email = ["john@coswellproductions.com"]
|
s.email = ["john@coswellproductions.com"]
|
||||||
s.homepage = ""
|
s.homepage = ""
|
||||||
|
s.license = "MIT"
|
||||||
s.summary = %q{Insert LiveReload into your app easily as Rack middleware}
|
s.summary = %q{Insert LiveReload into your app easily as Rack middleware}
|
||||||
s.description = %q{Insert LiveReload into your app easily as Rack middleware}
|
s.description = %q{Insert LiveReload into your app easily as Rack middleware}
|
||||||
|
|
||||||
@ -32,7 +33,8 @@ Gem::Specification.new do |s|
|
|||||||
s.add_development_dependency "guard-cucumber"
|
s.add_development_dependency "guard-cucumber"
|
||||||
s.add_development_dependency "guard-livereload"
|
s.add_development_dependency "guard-livereload"
|
||||||
s.add_development_dependency "webmock"
|
s.add_development_dependency "webmock"
|
||||||
|
s.add_development_dependency "nokogiri", ("< 1.6" if RUBY_VERSION < "1.9") # Nokogiri >= 1.6 requires Ruby >= 1.9
|
||||||
|
s.add_development_dependency 'appraisal', '~> 0.4'
|
||||||
s.add_runtime_dependency "rack"
|
s.add_runtime_dependency "rack"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -5,8 +5,11 @@
|
|||||||
WEB_SOCKET_FORCE_FLASH = true;
|
WEB_SOCKET_FORCE_FLASH = true;
|
||||||
<% end %>
|
<% end %>
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="/__rack/swfobject.js"></script>
|
<script type="text/javascript" src="<%= app_root %>/__rack/swfobject.js"></script>
|
||||||
<script type="text/javascript" src="/__rack/web_socket.js"></script>
|
<script type="text/javascript" src="<%= app_root %>/__rack/web_socket.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
<script type="text/javascript" src="<%= src %>"></script>
|
<script type="text/javascript">
|
||||||
|
RACK_LIVERELOAD_PORT = <%= @options[:live_reload_port] %>;
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="<%= livereload_source %>"></script>
|
||||||
|
|
||||||
|
200
spec/rack/livereload/body_processor_spec.rb
Normal file
200
spec/rack/livereload/body_processor_spec.rb
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'nokogiri'
|
||||||
|
|
||||||
|
describe Rack::LiveReload::BodyProcessor do
|
||||||
|
describe 'head tag regex' do
|
||||||
|
let(:regex) { described_class::HEAD_TAG_REGEX }
|
||||||
|
subject { regex }
|
||||||
|
|
||||||
|
it { should be_kind_of(Regexp) }
|
||||||
|
|
||||||
|
it 'only picks a valid <head> tag' do
|
||||||
|
regex.match("<head></head>").to_s.should eq('<head>')
|
||||||
|
regex.match("<head><title></title></head>").to_s.should eq('<head>')
|
||||||
|
regex.match("<head attribute='something'><title></title></head>").to_s.should eq("<head attribute='something'>")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds false when no head tag' do
|
||||||
|
regex.match("<header></header>").should be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:processor) { described_class.new(body, options) }
|
||||||
|
let(:body) { [ page_html ] }
|
||||||
|
let(:options) { {} }
|
||||||
|
let(:page_html) { '<head></head>' }
|
||||||
|
|
||||||
|
let(:processor_result) do
|
||||||
|
if !processor.processed?
|
||||||
|
processor.process!(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
processor
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { processor }
|
||||||
|
|
||||||
|
describe "livereload local uri" do
|
||||||
|
context 'does not exist' do
|
||||||
|
before do
|
||||||
|
stub_request(:any, 'localhost:35729/livereload.js').to_timeout
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should use_vendored }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'exists' do
|
||||||
|
before do
|
||||||
|
stub_request(:any, 'localhost:35729/livereload.js')
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should_not use_vendored }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with custom port' do
|
||||||
|
let(:options) { {:live_reload_port => '12348'}}
|
||||||
|
|
||||||
|
context 'exists' do
|
||||||
|
before do
|
||||||
|
stub_request(:any, 'localhost:12348/livereload.js')
|
||||||
|
end
|
||||||
|
it { should_not use_vendored }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'specify vendored' do
|
||||||
|
let(:options) { { :source => :vendored } }
|
||||||
|
|
||||||
|
it { should use_vendored }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'specify LR' do
|
||||||
|
let(:options) { { :source => :livereload } }
|
||||||
|
|
||||||
|
it { should_not use_vendored }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'text/html' do
|
||||||
|
before do
|
||||||
|
processor.stubs(:use_vendored?).returns(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:host) { 'host' }
|
||||||
|
let(:env) { { 'HTTP_HOST' => host } }
|
||||||
|
|
||||||
|
let(:processed_body) { processor_result.new_body.join('') }
|
||||||
|
let(:length) { processor_result.content_length }
|
||||||
|
|
||||||
|
let(:page_html) { '<head></head>' }
|
||||||
|
|
||||||
|
context 'vendored' do
|
||||||
|
it 'should add the vendored livereload js script tag' do
|
||||||
|
processed_body.should include("script")
|
||||||
|
processed_body.should include(described_class::LIVERELOAD_JS_PATH)
|
||||||
|
|
||||||
|
length.to_s.should == processed_body.length.to_s
|
||||||
|
|
||||||
|
described_class::LIVERELOAD_JS_PATH.should_not include(host)
|
||||||
|
|
||||||
|
processed_body.should include('swfobject')
|
||||||
|
processed_body.should include('web_socket')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'at the top of the head tag' do
|
||||||
|
let(:page_html) { '<head attribute="attribute"><script type="text/javascript" insert="first"></script><script type="text/javascript" insert="before"></script></head>' }
|
||||||
|
|
||||||
|
let(:body_dom) { Nokogiri::XML(processed_body) }
|
||||||
|
|
||||||
|
it 'should add the livereload js script tag before all other script tags' do
|
||||||
|
body_dom.at_css("head")[:attribute].should == 'attribute'
|
||||||
|
body_dom.at_css("script:eq(5)")[:src].should include(described_class::LIVERELOAD_JS_PATH)
|
||||||
|
body_dom.at_css("script:last-child")[:insert].should == "before"
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a relative URL root is specified' do
|
||||||
|
before do
|
||||||
|
ENV['RAILS_RELATIVE_URL_ROOT'] = '/a_relative_path'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should prepend the relative path to the script src' do
|
||||||
|
body_dom.at_css("script:eq(5)")[:src].should match(%r{^/a_relative_path/})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "LIVERELOAD_PORT value" do
|
||||||
|
let(:options) { { :live_reload_port => 12345 }}
|
||||||
|
|
||||||
|
it "sets the variable at the top of the file" do
|
||||||
|
processed_body.should include 'RACK_LIVERELOAD_PORT = 12345'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in header tags' do
|
||||||
|
let(:page_html) { "<header class='hero'><h1>Just a normal header tag</h1></header>" }
|
||||||
|
|
||||||
|
let(:body_dom) { Nokogiri::XML(processed_body) }
|
||||||
|
|
||||||
|
it 'should not add the livereload js' do
|
||||||
|
body_dom.at_css("header")[:class].should == 'hero'
|
||||||
|
body_dom.css('script').should be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'not vendored' do
|
||||||
|
before do
|
||||||
|
processor.stubs(:use_vendored?).returns(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add the LR livereload js script tag' do
|
||||||
|
processed_body.should include("script")
|
||||||
|
processed_body.should include(processor.livereload_local_uri.gsub('localhost', 'host'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'set options' do
|
||||||
|
let(:options) { { :host => new_host, :port => port, :min_delay => min_delay, :max_delay => max_delay } }
|
||||||
|
let(:min_delay) { 5 }
|
||||||
|
let(:max_delay) { 10 }
|
||||||
|
let(:port) { 23 }
|
||||||
|
let(:new_host) { 'myhost' }
|
||||||
|
|
||||||
|
it 'should add the livereload.js script tag' do
|
||||||
|
processed_body.should include("mindelay=#{min_delay}")
|
||||||
|
processed_body.should include("maxdelay=#{max_delay}")
|
||||||
|
processed_body.should include("port=#{port}")
|
||||||
|
processed_body.should include("host=#{new_host}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'force flash' do
|
||||||
|
let(:options) { { :force_swf => true } }
|
||||||
|
|
||||||
|
it 'should not add the flash shim' do
|
||||||
|
processed_body.should include('WEB_SOCKET_FORCE_FLASH')
|
||||||
|
processed_body.should include('swfobject')
|
||||||
|
processed_body.should include('web_socket')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'no flash' do
|
||||||
|
let(:options) { { :no_swf => true } }
|
||||||
|
|
||||||
|
it 'should not add the flash shim' do
|
||||||
|
processed_body.should_not include('swfobject')
|
||||||
|
processed_body.should_not include('web_socket')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'no host at all' do
|
||||||
|
let(:env) { {} }
|
||||||
|
|
||||||
|
it 'should use localhost' do
|
||||||
|
processed_body.should include('localhost')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
137
spec/rack/livereload/processing_skip_analyzer_spec.rb
Normal file
137
spec/rack/livereload/processing_skip_analyzer_spec.rb
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Rack::LiveReload::ProcessingSkipAnalyzer do
|
||||||
|
subject { described_class.new(result, env, options) }
|
||||||
|
|
||||||
|
let(:result) { [ status, headers, body ] }
|
||||||
|
let(:env) { { 'HTTP_USER_AGENT' => user_agent } }
|
||||||
|
let(:options) { {} }
|
||||||
|
|
||||||
|
let(:user_agent) { 'Firefox' }
|
||||||
|
let(:status) { 200 }
|
||||||
|
let(:headers) { {} }
|
||||||
|
let(:body) { [] }
|
||||||
|
|
||||||
|
describe '#skip_processing?' do
|
||||||
|
it "should skip processing" do
|
||||||
|
subject.skip_processing?.should be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#ignored?' do
|
||||||
|
let(:options) { { :ignore => [ %r{file} ] } }
|
||||||
|
|
||||||
|
context 'path contains ignore pattern' do
|
||||||
|
let(:env) { { 'PATH_INFO' => '/this/file', 'QUERY_STRING' => '' } }
|
||||||
|
|
||||||
|
it { should be_ignored }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'root path' do
|
||||||
|
let(:env) { { 'PATH_INFO' => '/', 'QUERY_STRING' => '' } }
|
||||||
|
|
||||||
|
it { should_not be_ignored }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#chunked?' do
|
||||||
|
context 'regular response' do
|
||||||
|
it { should_not be_chunked }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'chunked response' do
|
||||||
|
let(:headers) { { 'Transfer-Encoding' => 'chunked' } }
|
||||||
|
|
||||||
|
it { should be_chunked }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#inline?' do
|
||||||
|
context 'inline disposition' do
|
||||||
|
let(:headers) { { 'Content-Disposition' => 'inline; filename=my_inlined_file' } }
|
||||||
|
|
||||||
|
it { should be_inline }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#ignored?' do
|
||||||
|
let(:path_info) { 'path info' }
|
||||||
|
let(:query_string) { 'query_string' }
|
||||||
|
let(:env) { { 'PATH_INFO' => path_info, 'QUERY_STRING' => query_string } }
|
||||||
|
|
||||||
|
context 'no ignore set' do
|
||||||
|
it { should_not be_ignored }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'ignore set' do
|
||||||
|
let(:options) { { :ignore => [ %r{#{path_info}} ] } }
|
||||||
|
|
||||||
|
it { should be_ignored }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'ignore set including query_string' do
|
||||||
|
let(:options) { { :ignore => [ %r{#{path_info}\?#{query_string}} ] } }
|
||||||
|
|
||||||
|
it { should be_ignored }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#bad_browser?' do
|
||||||
|
context 'Firefox' do
|
||||||
|
it { should_not be_bad_browser }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'BAD browser' do
|
||||||
|
let(:user_agent) { described_class::BAD_USER_AGENTS.first.source }
|
||||||
|
|
||||||
|
it { should be_bad_browser }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#html?' do
|
||||||
|
context 'HTML content' do
|
||||||
|
let(:headers) { { 'Content-Type' => 'text/html' } }
|
||||||
|
|
||||||
|
it { should be_html }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'PDF content' do
|
||||||
|
let(:headers) { { 'Content-Type' => 'application/pdf' } }
|
||||||
|
|
||||||
|
it { should_not be_html }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#get?' do
|
||||||
|
context 'GET request' do
|
||||||
|
let(:env) { { 'REQUEST_METHOD' => 'GET' } }
|
||||||
|
|
||||||
|
it { should be_get }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'PUT request' do
|
||||||
|
let(:env) { { 'REQUEST_METHOD' => 'PUT' } }
|
||||||
|
|
||||||
|
it { should_not be_get }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'POST request' do
|
||||||
|
let(:env) { { 'REQUEST_METHOD' => 'POST' } }
|
||||||
|
|
||||||
|
it { should_not be_get }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'DELETE request' do
|
||||||
|
let(:env) { { 'REQUEST_METHOD' => 'DELETE' } }
|
||||||
|
|
||||||
|
it { should_not be_get }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'PATCH request' do
|
||||||
|
let(:env) { { 'REQUEST_METHOD' => 'PATCH' } }
|
||||||
|
|
||||||
|
it { should_not be_get }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,4 +1,5 @@
|
|||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
require 'nokogiri'
|
||||||
|
|
||||||
describe Rack::LiveReload do
|
describe Rack::LiveReload do
|
||||||
let(:middleware) { described_class.new(app, options) }
|
let(:middleware) { described_class.new(app, options) }
|
||||||
@ -6,196 +7,23 @@ describe Rack::LiveReload do
|
|||||||
|
|
||||||
subject { middleware }
|
subject { middleware }
|
||||||
|
|
||||||
its(:app) { should == app }
|
it 'should be an app' do
|
||||||
|
middleware.app.should be == app
|
||||||
|
end
|
||||||
|
|
||||||
let(:env) { {} }
|
let(:env) { {} }
|
||||||
let(:options) { {} }
|
let(:options) { {} }
|
||||||
|
|
||||||
describe described_class::LIVERELOAD_LOCAL_URI do
|
|
||||||
context 'does not exist' do
|
|
||||||
before do
|
|
||||||
stub_request(:any, 'localhost:35729/livereload.js').to_timeout
|
|
||||||
end
|
|
||||||
|
|
||||||
it { should use_vendored }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'exists' do
|
|
||||||
before do
|
|
||||||
stub_request(:any, 'localhost:35729/livereload.js')
|
|
||||||
end
|
|
||||||
|
|
||||||
it { should_not use_vendored }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'specify vendored' do
|
|
||||||
let(:options) { { :source => :vendored } }
|
|
||||||
|
|
||||||
it { should use_vendored }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'specify LR' do
|
|
||||||
let(:options) { { :source => :livereload } }
|
|
||||||
|
|
||||||
it { should_not use_vendored }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'not text/html' do
|
|
||||||
let(:ret) { [ 200, { 'Content-Type' => 'image/png' }, [ '<head></head>' ] ] }
|
|
||||||
|
|
||||||
before do
|
|
||||||
app.stubs(:call).with(env).returns(ret)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should pass through' do
|
|
||||||
middleware.call(env).should == ret
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'unknown Content-Type' do
|
|
||||||
let(:ret) { [ 200, {}, [ 'hey ho' ] ] }
|
|
||||||
|
|
||||||
before do
|
|
||||||
app.stubs(:call).with(env).returns(ret)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should not break' do
|
|
||||||
middleware.call(env).should_not raise_error(NoMethodError, /You have a nil object/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'text/html' do
|
|
||||||
before do
|
|
||||||
app.stubs(:call).with(env).returns([ 200, { 'Content-Type' => 'text/html', 'Content-Length' => 0 }, [ '<head></head>' ] ])
|
|
||||||
middleware.stubs(:use_vendored?).returns(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:host) { 'host' }
|
|
||||||
let(:env) { { 'HTTP_HOST' => host } }
|
|
||||||
|
|
||||||
let(:ret) { middleware._call(env) }
|
|
||||||
let(:body) { ret.last.join }
|
|
||||||
let(:length) { ret[1]['Content-Length'] }
|
|
||||||
|
|
||||||
context 'vendored' do
|
|
||||||
it 'should add the vendored livereload js script tag' do
|
|
||||||
body.should include("script")
|
|
||||||
body.should include(described_class::LIVERELOAD_JS_PATH)
|
|
||||||
|
|
||||||
length.should == body.length.to_s
|
|
||||||
|
|
||||||
described_class::LIVERELOAD_JS_PATH.should_not include(host)
|
|
||||||
|
|
||||||
body.should include('swfobject')
|
|
||||||
body.should include('web_socket')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'not vendored' do
|
|
||||||
before do
|
|
||||||
middleware.stubs(:use_vendored?).returns(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should add the LR livereload js script tag' do
|
|
||||||
body.should include("script")
|
|
||||||
body.should include(described_class::LIVERELOAD_LOCAL_URI.gsub('localhost', 'host'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'set options' do
|
|
||||||
let(:middleware) { described_class.new(app, :host => new_host, :port => port, :min_delay => min_delay, :max_delay => max_delay) }
|
|
||||||
let(:min_delay) { 5 }
|
|
||||||
let(:max_delay) { 10 }
|
|
||||||
let(:port) { 23 }
|
|
||||||
let(:new_host) { 'myhost' }
|
|
||||||
|
|
||||||
it 'should add the livereload.js script tag' do
|
|
||||||
body.should include("mindelay=#{min_delay}")
|
|
||||||
body.should include("maxdelay=#{max_delay}")
|
|
||||||
body.should include("port=#{port}")
|
|
||||||
body.should include("host=#{new_host}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'force flash' do
|
|
||||||
let(:middleware) { described_class.new(app, :force_swf => true) }
|
|
||||||
|
|
||||||
it 'should not add the flash shim' do
|
|
||||||
body.should include('WEB_SOCKET_FORCE_FLASH')
|
|
||||||
body.should include('swfobject')
|
|
||||||
body.should include('web_socket')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'no flash' do
|
|
||||||
let(:middleware) { described_class.new(app, :no_swf => true) }
|
|
||||||
|
|
||||||
it 'should not add the flash shim' do
|
|
||||||
body.should_not include('swfobject')
|
|
||||||
body.should_not include('web_socket')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'no host at all' do
|
|
||||||
let(:env) { {} }
|
|
||||||
|
|
||||||
it 'should use localhost' do
|
|
||||||
body.should include('localhost')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'ignored' do
|
|
||||||
let(:options) { { :ignore => [ %r{file} ] } }
|
|
||||||
|
|
||||||
context 'not root' do
|
|
||||||
let(:env) { { 'PATH_INFO' => '/this/file' } }
|
|
||||||
|
|
||||||
it 'should have no change' do
|
|
||||||
body.should_not include('script')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'root' do
|
|
||||||
let(:env) { { 'PATH_INFO' => '/' } }
|
|
||||||
|
|
||||||
it 'should have script' do
|
|
||||||
body.should include('script')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context '/__rack/livereload.js' do
|
context '/__rack/livereload.js' do
|
||||||
let(:env) { { 'PATH_INFO' => described_class::LIVERELOAD_JS_PATH } }
|
let(:env) { { 'PATH_INFO' => described_class::BodyProcessor::LIVERELOAD_JS_PATH } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
middleware.expects(:deliver_file).returns(true)
|
middleware.expects(:deliver_file).returns(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should return the js file' do
|
it 'should return the js file' do
|
||||||
middleware._call(env).should be_true
|
middleware._call(env).should be_truthy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#ignored?' do
|
|
||||||
let(:path_info) { 'path info' }
|
|
||||||
|
|
||||||
context 'no ignore set' do
|
|
||||||
it { should_not be_ignored(path_info) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'ignore set' do
|
|
||||||
let(:options) { { :ignore => [ %r{#{path_info}} ] } }
|
|
||||||
|
|
||||||
it { should be_ignored(path_info) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#bad_browser?' do
|
|
||||||
let(:user_agent) { described_class::BAD_USER_AGENTS.first.source }
|
|
||||||
|
|
||||||
it { should be_bad_browser(user_agent) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
require 'mocha'
|
require 'mocha/api'
|
||||||
require 'webmock/rspec'
|
require 'webmock/rspec'
|
||||||
|
|
||||||
require 'rack-livereload'
|
require 'rack-livereload'
|
||||||
|
Loading…
Reference in New Issue
Block a user