rack-livereload 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Guardfile CHANGED
@@ -7,3 +7,6 @@ guard 'rspec', :version => 2 do
7
7
  watch('spec/spec_helper.rb') { "spec" }
8
8
  end
9
9
 
10
+ guard 'livereload' do
11
+ watch('index.html')
12
+ end
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  Hey, you've got [LiveReload](http://www.livereload.com/) in my [Rack](http://rack.rubyforge.org/)!
2
2
  No need for browser extensions anymore! Just plug it in your middleware stack and go!
3
+ Even supports browsers without WebSockets!
3
4
 
4
5
  Use this with [guard-livereload](http://github.com/guard/guard-livereload) for maximum fun!
5
6
 
@@ -56,5 +57,25 @@ you'll connect and be LiveReloading away!
56
57
  * You can force the use of either one (and save on the cost of checking to see if that file
57
58
  is available) with the middleware option `:source => :vendored` or `:source => :livereload`.
58
59
 
60
+ ### How about non-WebSocket-enabled browsers?
61
+
62
+ For browsers that don't support WebSockets, but do support Flash, [web-socket-js](https://github.com/gimite/web-socket-js)
63
+ is loaded. By default, this is done transparently, so you'll get a copy of swfobject.js and web_socket.js loaded even if
64
+ your browser doesn't need it. The SWF WebSocket implementor won't be loaded unless your browser has no native
65
+ WebSockets support or if you force it in the middleware stack:
66
+
67
+ ``` ruby
68
+ use Rack::LiveReload, :force_swf => true
69
+ ```
70
+
71
+ If you don't want any of the web-sockets-js code included at all, use the `no_swf` option:
72
+
73
+ ``` ruby
74
+ use Rack::LiveReload, :no_swf => true
75
+ ```
76
+
77
+ Once more browsers support WebSockets than don't, this option will be reversed and you'll have
78
+ to explicitly include the Flash shim.
79
+
59
80
  As usual, super-alpha!
60
81
 
data/Rakefile CHANGED
@@ -9,8 +9,20 @@ task :update_livereload_js do
9
9
  }
10
10
  end
11
11
 
12
+ desc 'Update web-socket-js'
13
+ task :update_web_socket_js do
14
+ require 'httparty'
15
+
16
+ %w{swfobject.js web_socket.js WebSocketMain.swf}.each do |file|
17
+ File.open("js/#{file}", 'wb') do |fh|
18
+ fh.print HTTParty.get("https://raw.github.com/gimite/web-socket-js/master/#{file}").body
19
+ end
20
+ end
21
+ end
22
+
12
23
  require 'rspec/core/rake_task'
13
24
 
14
25
  RSpec::Core::RakeTask.new(:spec)
15
26
 
16
27
  task :default => :spec
28
+
data/config.ru CHANGED
@@ -3,10 +3,11 @@ $: << 'lib'
3
3
 
4
4
  require 'rack/livereload'
5
5
 
6
+ use Rack::Logger
6
7
  use Rack::LiveReload
7
8
 
8
9
  get '/' do
9
- "<html><head><title>Hi</title></head><body>Hi</body></html>"
10
+ File.read('index.html')
10
11
  end
11
12
 
12
13
  run Sinatra::Application
data/index.html ADDED
@@ -0,0 +1,2 @@
1
+ <html>
2
+ <head><title>Hi</title></head><body>Lats</body></html>
Binary file
data/js/swfobject.js ADDED
@@ -0,0 +1,4 @@
1
+ /* SWFObject v2.2 <http://code.google.com/p/swfobject/>
2
+ is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
3
+ */
4
+ var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}();
data/js/web_socket.js ADDED
@@ -0,0 +1,379 @@
1
+ // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2
+ // License: New BSD License
3
+ // Reference: http://dev.w3.org/html5/websockets/
4
+ // Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
5
+
6
+ (function() {
7
+
8
+ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return;
9
+
10
+ var logger;
11
+ if (window.WEB_SOCKET_LOGGER) {
12
+ logger = WEB_SOCKET_LOGGER;
13
+ } else if (window.console && window.console.log && window.console.error) {
14
+ // In some environment, console is defined but console.log or console.error is missing.
15
+ logger = window.console;
16
+ } else {
17
+ logger = {log: function(){ }, error: function(){ }};
18
+ }
19
+
20
+ // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
21
+ if (swfobject.getFlashPlayerVersion().major < 10) {
22
+ logger.error("Flash Player >= 10.0.0 is required.");
23
+ return;
24
+ }
25
+ if (location.protocol == "file:") {
26
+ logger.error(
27
+ "WARNING: web-socket-js doesn't work in file:///... URL " +
28
+ "unless you set Flash Security Settings properly. " +
29
+ "Open the page via Web server i.e. http://...");
30
+ }
31
+
32
+ /**
33
+ * This class represents a faux web socket.
34
+ * @param {string} url
35
+ * @param {array or string} protocols
36
+ * @param {string} proxyHost
37
+ * @param {int} proxyPort
38
+ * @param {string} headers
39
+ */
40
+ WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
41
+ var self = this;
42
+ self.__id = WebSocket.__nextId++;
43
+ WebSocket.__instances[self.__id] = self;
44
+ self.readyState = WebSocket.CONNECTING;
45
+ self.bufferedAmount = 0;
46
+ self.__events = {};
47
+ if (!protocols) {
48
+ protocols = [];
49
+ } else if (typeof protocols == "string") {
50
+ protocols = [protocols];
51
+ }
52
+ // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
53
+ // Otherwise, when onopen fires immediately, onopen is called before it is set.
54
+ self.__createTask = setTimeout(function() {
55
+ WebSocket.__addTask(function() {
56
+ self.__createTask = null;
57
+ WebSocket.__flash.create(
58
+ self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
59
+ });
60
+ }, 0);
61
+ };
62
+
63
+ /**
64
+ * Send data to the web socket.
65
+ * @param {string} data The data to send to the socket.
66
+ * @return {boolean} True for success, false for failure.
67
+ */
68
+ WebSocket.prototype.send = function(data) {
69
+ if (this.readyState == WebSocket.CONNECTING) {
70
+ throw "INVALID_STATE_ERR: Web Socket connection has not been established";
71
+ }
72
+ // We use encodeURIComponent() here, because FABridge doesn't work if
73
+ // the argument includes some characters. We don't use escape() here
74
+ // because of this:
75
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
76
+ // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
77
+ // preserve all Unicode characters either e.g. "\uffff" in Firefox.
78
+ // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
79
+ // additional testing.
80
+ var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
81
+ if (result < 0) { // success
82
+ return true;
83
+ } else {
84
+ this.bufferedAmount += result;
85
+ return false;
86
+ }
87
+ };
88
+
89
+ /**
90
+ * Close this web socket gracefully.
91
+ */
92
+ WebSocket.prototype.close = function() {
93
+ if (this.__createTask) {
94
+ clearTimeout(this.__createTask);
95
+ this.__createTask = null;
96
+ this.readyState = WebSocket.CLOSED;
97
+ return;
98
+ }
99
+ if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
100
+ return;
101
+ }
102
+ this.readyState = WebSocket.CLOSING;
103
+ WebSocket.__flash.close(this.__id);
104
+ };
105
+
106
+ /**
107
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
108
+ *
109
+ * @param {string} type
110
+ * @param {function} listener
111
+ * @param {boolean} useCapture
112
+ * @return void
113
+ */
114
+ WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
115
+ if (!(type in this.__events)) {
116
+ this.__events[type] = [];
117
+ }
118
+ this.__events[type].push(listener);
119
+ };
120
+
121
+ /**
122
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
123
+ *
124
+ * @param {string} type
125
+ * @param {function} listener
126
+ * @param {boolean} useCapture
127
+ * @return void
128
+ */
129
+ WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
130
+ if (!(type in this.__events)) return;
131
+ var events = this.__events[type];
132
+ for (var i = events.length - 1; i >= 0; --i) {
133
+ if (events[i] === listener) {
134
+ events.splice(i, 1);
135
+ break;
136
+ }
137
+ }
138
+ };
139
+
140
+ /**
141
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
142
+ *
143
+ * @param {Event} event
144
+ * @return void
145
+ */
146
+ WebSocket.prototype.dispatchEvent = function(event) {
147
+ var events = this.__events[event.type] || [];
148
+ for (var i = 0; i < events.length; ++i) {
149
+ events[i](event);
150
+ }
151
+ var handler = this["on" + event.type];
152
+ if (handler) handler.apply(this, [event]);
153
+ };
154
+
155
+ /**
156
+ * Handles an event from Flash.
157
+ * @param {Object} flashEvent
158
+ */
159
+ WebSocket.prototype.__handleEvent = function(flashEvent) {
160
+
161
+ if ("readyState" in flashEvent) {
162
+ this.readyState = flashEvent.readyState;
163
+ }
164
+ if ("protocol" in flashEvent) {
165
+ this.protocol = flashEvent.protocol;
166
+ }
167
+
168
+ var jsEvent;
169
+ if (flashEvent.type == "open" || flashEvent.type == "error") {
170
+ jsEvent = this.__createSimpleEvent(flashEvent.type);
171
+ } else if (flashEvent.type == "close") {
172
+ jsEvent = this.__createSimpleEvent("close");
173
+ jsEvent.wasClean = flashEvent.wasClean ? true : false;
174
+ jsEvent.code = flashEvent.code;
175
+ jsEvent.reason = flashEvent.reason;
176
+ } else if (flashEvent.type == "message") {
177
+ var data = decodeURIComponent(flashEvent.message);
178
+ jsEvent = this.__createMessageEvent("message", data);
179
+ } else {
180
+ throw "unknown event type: " + flashEvent.type;
181
+ }
182
+
183
+ this.dispatchEvent(jsEvent);
184
+
185
+ };
186
+
187
+ WebSocket.prototype.__createSimpleEvent = function(type) {
188
+ if (document.createEvent && window.Event) {
189
+ var event = document.createEvent("Event");
190
+ event.initEvent(type, false, false);
191
+ return event;
192
+ } else {
193
+ return {type: type, bubbles: false, cancelable: false};
194
+ }
195
+ };
196
+
197
+ WebSocket.prototype.__createMessageEvent = function(type, data) {
198
+ if (document.createEvent && window.MessageEvent && !window.opera) {
199
+ var event = document.createEvent("MessageEvent");
200
+ event.initMessageEvent("message", false, false, data, null, null, window, null);
201
+ return event;
202
+ } else {
203
+ // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
204
+ return {type: type, data: data, bubbles: false, cancelable: false};
205
+ }
206
+ };
207
+
208
+ /**
209
+ * Define the WebSocket readyState enumeration.
210
+ */
211
+ WebSocket.CONNECTING = 0;
212
+ WebSocket.OPEN = 1;
213
+ WebSocket.CLOSING = 2;
214
+ WebSocket.CLOSED = 3;
215
+
216
+ WebSocket.__flash = null;
217
+ WebSocket.__instances = {};
218
+ WebSocket.__tasks = [];
219
+ WebSocket.__nextId = 0;
220
+
221
+ /**
222
+ * Load a new flash security policy file.
223
+ * @param {string} url
224
+ */
225
+ WebSocket.loadFlashPolicyFile = function(url){
226
+ WebSocket.__addTask(function() {
227
+ WebSocket.__flash.loadManualPolicyFile(url);
228
+ });
229
+ };
230
+
231
+ /**
232
+ * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
233
+ */
234
+ WebSocket.__initialize = function() {
235
+ if (WebSocket.__flash) return;
236
+
237
+ if (WebSocket.__swfLocation) {
238
+ // For backword compatibility.
239
+ window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
240
+ }
241
+ if (!window.WEB_SOCKET_SWF_LOCATION) {
242
+ logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
243
+ return;
244
+ }
245
+ if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
246
+ !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
247
+ WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
248
+ var swfHost = RegExp.$1;
249
+ if (location.host != swfHost) {
250
+ logger.error(
251
+ "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
252
+ "('" + location.host + "' != '" + swfHost + "'). " +
253
+ "See also 'How to host HTML file and SWF file in different domains' section " +
254
+ "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
255
+ "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
256
+ }
257
+ }
258
+ var container = document.createElement("div");
259
+ container.id = "webSocketContainer";
260
+ // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
261
+ // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
262
+ // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
263
+ // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
264
+ // the best we can do as far as we know now.
265
+ container.style.position = "absolute";
266
+ if (WebSocket.__isFlashLite()) {
267
+ container.style.left = "0px";
268
+ container.style.top = "0px";
269
+ } else {
270
+ container.style.left = "-100px";
271
+ container.style.top = "-100px";
272
+ }
273
+ var holder = document.createElement("div");
274
+ holder.id = "webSocketFlash";
275
+ container.appendChild(holder);
276
+ document.body.appendChild(container);
277
+ // See this article for hasPriority:
278
+ // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
279
+ swfobject.embedSWF(
280
+ WEB_SOCKET_SWF_LOCATION,
281
+ "webSocketFlash",
282
+ "1" /* width */,
283
+ "1" /* height */,
284
+ "10.0.0" /* SWF version */,
285
+ null,
286
+ null,
287
+ {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
288
+ null,
289
+ function(e) {
290
+ if (!e.success) {
291
+ logger.error("[WebSocket] swfobject.embedSWF failed");
292
+ }
293
+ });
294
+ };
295
+
296
+ /**
297
+ * Called by Flash to notify JS that it's fully loaded and ready
298
+ * for communication.
299
+ */
300
+ WebSocket.__onFlashInitialized = function() {
301
+ // We need to set a timeout here to avoid round-trip calls
302
+ // to flash during the initialization process.
303
+ setTimeout(function() {
304
+ WebSocket.__flash = document.getElementById("webSocketFlash");
305
+ WebSocket.__flash.setCallerUrl(location.href);
306
+ WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
307
+ for (var i = 0; i < WebSocket.__tasks.length; ++i) {
308
+ WebSocket.__tasks[i]();
309
+ }
310
+ WebSocket.__tasks = [];
311
+ }, 0);
312
+ };
313
+
314
+ /**
315
+ * Called by Flash to notify WebSockets events are fired.
316
+ */
317
+ WebSocket.__onFlashEvent = function() {
318
+ setTimeout(function() {
319
+ try {
320
+ // Gets events using receiveEvents() instead of getting it from event object
321
+ // of Flash event. This is to make sure to keep message order.
322
+ // It seems sometimes Flash events don't arrive in the same order as they are sent.
323
+ var events = WebSocket.__flash.receiveEvents();
324
+ for (var i = 0; i < events.length; ++i) {
325
+ WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
326
+ }
327
+ } catch (e) {
328
+ logger.error(e);
329
+ }
330
+ }, 0);
331
+ return true;
332
+ };
333
+
334
+ // Called by Flash.
335
+ WebSocket.__log = function(message) {
336
+ logger.log(decodeURIComponent(message));
337
+ };
338
+
339
+ // Called by Flash.
340
+ WebSocket.__error = function(message) {
341
+ logger.error(decodeURIComponent(message));
342
+ };
343
+
344
+ WebSocket.__addTask = function(task) {
345
+ if (WebSocket.__flash) {
346
+ task();
347
+ } else {
348
+ WebSocket.__tasks.push(task);
349
+ }
350
+ };
351
+
352
+ /**
353
+ * Test if the browser is running flash lite.
354
+ * @return {boolean} True if flash lite is running, false otherwise.
355
+ */
356
+ WebSocket.__isFlashLite = function() {
357
+ if (!window.navigator || !window.navigator.mimeTypes) {
358
+ return false;
359
+ }
360
+ var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
361
+ if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
362
+ return false;
363
+ }
364
+ return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
365
+ };
366
+
367
+ if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
368
+ if (window.addEventListener) {
369
+ window.addEventListener("load", function(){
370
+ WebSocket.__initialize();
371
+ }, false);
372
+ } else {
373
+ window.attachEvent("onload", function(){
374
+ WebSocket.__initialize();
375
+ });
376
+ }
377
+ }
378
+
379
+ })();
@@ -1,6 +1,6 @@
1
1
  require "rack/livereload"
2
2
 
3
3
  class Rack::LiveReload
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
6
6
 
@@ -1,3 +1,5 @@
1
+ require 'erb'
2
+
1
3
  module Rack
2
4
  class LiveReload
3
5
  LIVERELOAD_JS_PATH = '/__rack/livereload.js'
@@ -39,8 +41,10 @@ module Rack
39
41
  end
40
42
 
41
43
  def call(env)
42
- if env['PATH_INFO'] == LIVERELOAD_JS_PATH
43
- deliver_file(::File.expand_path('../../../js/livereload.js', __FILE__))
44
+ _, path, file = (env['PATH_INFO'] || '').split('/')
45
+
46
+ if path == '__rack' && ::File.file?(target = ::File.expand_path("../../../js/#{file}", __FILE__))
47
+ deliver_file(target)
44
48
  else
45
49
  status, headers, body = @app.call(env)
46
50
 
@@ -62,7 +66,9 @@ module Rack
62
66
  src << "&maxdelay=#{@options[:max_delay]}" if @options[:max_delay]
63
67
  src << "&port=#{@options[:port]}" if @options[:port]
64
68
 
65
- line.gsub!('</head>', %{<script type="text/javascript" src="#{src}"></script></head>})
69
+ template = ERB.new(::File.read(::File.expand_path('../../../skel/livereload.html.erb', __FILE__)))
70
+
71
+ line.gsub!('</head>', %{#{template.result(binding)}</head>})
66
72
 
67
73
  headers["X-Rack-LiveReload"] = '1'
68
74
  end
@@ -79,7 +85,22 @@ module Rack
79
85
 
80
86
  private
81
87
  def deliver_file(file)
82
- [ 200, { 'Content-Type' => 'text/javascript', 'Content-Length' => ::File.size(file).to_s }, [ ::File.read(file) ] ]
88
+ type = case ::File.extname(file)
89
+ when '.js'
90
+ 'text/javascript'
91
+ when '.swf'
92
+ 'application/swf'
93
+ end
94
+
95
+ [ 200, { 'Content-Type' => type, 'Content-Length' => ::File.size(file).to_s }, [ ::File.read(file) ] ]
96
+ end
97
+
98
+ def force_swf?
99
+ @options[:force_swf]
100
+ end
101
+
102
+ def with_swf?
103
+ !@options[:no_swf]
83
104
  end
84
105
  end
85
106
  end
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.add_development_dependency "mocha"
29
29
  s.add_development_dependency "guard"
30
30
  s.add_development_dependency "guard-rspec"
31
+ s.add_development_dependency "guard-livereload"
31
32
  s.add_development_dependency "webmock"
32
33
 
33
34
  s.add_runtime_dependency "rack"
@@ -0,0 +1,12 @@
1
+ <% if with_swf? %>
2
+ <script type="text/javascript">
3
+ WEB_SOCKET_SWF_LOCATION = "/__rack/WebSocketMain.swf";
4
+ <% if force_swf? %>
5
+ WEB_SOCKET_FORCE_FLASH = true;
6
+ <% end %>
7
+ </script>
8
+ <script type="text/javascript" src="/__rack/swfobject.js"></script>
9
+ <script type="text/javascript" src="/__rack/web_socket.js"></script>
10
+ <% end %>
11
+ <script type="text/javascript" src="<%= src %>"></script>
12
+
@@ -74,6 +74,9 @@ describe Rack::LiveReload do
74
74
  length.should == body.length.to_s
75
75
 
76
76
  described_class::LIVERELOAD_JS_PATH.should_not include(host)
77
+
78
+ body.should include('swfobject')
79
+ body.should include('web_socket')
77
80
  end
78
81
  end
79
82
 
@@ -102,6 +105,25 @@ describe Rack::LiveReload do
102
105
  body.should include("host=#{new_host}")
103
106
  end
104
107
  end
108
+
109
+ context 'force flash' do
110
+ let(:middleware) { described_class.new(app, :force_swf => true) }
111
+
112
+ it 'should not add the flash shim' do
113
+ body.should include('WEB_SOCKET_FORCE_FLASH')
114
+ body.should include('swfobject')
115
+ body.should include('web_socket')
116
+ end
117
+ end
118
+
119
+ context 'no flash' do
120
+ let(:middleware) { described_class.new(app, :no_swf => true) }
121
+
122
+ it 'should not add the flash shim' do
123
+ body.should_not include('swfobject')
124
+ body.should_not include('web_socket')
125
+ end
126
+ end
105
127
  end
106
128
 
107
129
  context '/__rack/livereload.js' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-livereload
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-14 00:00:00.000000000Z
12
+ date: 2011-11-17 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &2165025800 !ruby/object:Gem::Requirement
16
+ requirement: &2154613040 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *2165025800
24
+ version_requirements: *2154613040
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: httparty
27
- requirement: &2165025380 !ruby/object:Gem::Requirement
27
+ requirement: &2154612620 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2165025380
35
+ version_requirements: *2154612620
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: sinatra
38
- requirement: &2165024940 !ruby/object:Gem::Requirement
38
+ requirement: &2154612200 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2165024940
46
+ version_requirements: *2154612200
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: shotgun
49
- requirement: &2165024480 !ruby/object:Gem::Requirement
49
+ requirement: &2154628140 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2165024480
57
+ version_requirements: *2154628140
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: thin
60
- requirement: &2165024040 !ruby/object:Gem::Requirement
60
+ requirement: &2154627720 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2165024040
68
+ version_requirements: *2154627720
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
- requirement: &2165023520 !ruby/object:Gem::Requirement
71
+ requirement: &2154627300 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2165023520
79
+ version_requirements: *2154627300
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: mocha
82
- requirement: &2165023000 !ruby/object:Gem::Requirement
82
+ requirement: &2154626880 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *2165023000
90
+ version_requirements: *2154626880
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: guard
93
- requirement: &2165022480 !ruby/object:Gem::Requirement
93
+ requirement: &2154626460 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *2165022480
101
+ version_requirements: *2154626460
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: guard-rspec
104
- requirement: &2165022060 !ruby/object:Gem::Requirement
104
+ requirement: &2154626040 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ! '>='
@@ -109,10 +109,21 @@ dependencies:
109
109
  version: '0'
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *2165022060
112
+ version_requirements: *2154626040
113
+ - !ruby/object:Gem::Dependency
114
+ name: guard-livereload
115
+ requirement: &2154625620 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: *2154625620
113
124
  - !ruby/object:Gem::Dependency
114
125
  name: webmock
115
- requirement: &2165021600 !ruby/object:Gem::Requirement
126
+ requirement: &2154625200 !ruby/object:Gem::Requirement
116
127
  none: false
117
128
  requirements:
118
129
  - - ! '>='
@@ -120,10 +131,10 @@ dependencies:
120
131
  version: '0'
121
132
  type: :development
122
133
  prerelease: false
123
- version_requirements: *2165021600
134
+ version_requirements: *2154625200
124
135
  - !ruby/object:Gem::Dependency
125
136
  name: rack
126
- requirement: &2165021120 !ruby/object:Gem::Requirement
137
+ requirement: &2154624780 !ruby/object:Gem::Requirement
127
138
  none: false
128
139
  requirements:
129
140
  - - ! '>='
@@ -131,7 +142,7 @@ dependencies:
131
142
  version: '0'
132
143
  type: :runtime
133
144
  prerelease: false
134
- version_requirements: *2165021120
145
+ version_requirements: *2154624780
135
146
  description: Insert LiveReload into your app easily as Rack middleware
136
147
  email:
137
148
  - john@coswellproductions.com
@@ -145,10 +156,15 @@ files:
145
156
  - README.md
146
157
  - Rakefile
147
158
  - config.ru
159
+ - index.html
160
+ - js/WebSocketMain.swf
148
161
  - js/livereload.js
162
+ - js/swfobject.js
163
+ - js/web_socket.js
149
164
  - lib/rack-livereload.rb
150
165
  - lib/rack/livereload.rb
151
166
  - rack-livereload.gemspec
167
+ - skel/livereload.html.erb
152
168
  - spec/rack/livereload_spec.rb
153
169
  - spec/spec_helper.rb
154
170
  homepage: ''