pushify 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,97 @@
1
+ # ======================
2
+ # Juggernaut Options
3
+ # ======================
4
+
5
+ # === Subscription authentication ===
6
+ # Leave all subscription options uncommented to allow anyone to subscribe.
7
+
8
+ # If specified, subscription_url is called everytime a client subscribes.
9
+ # Parameters passed are: session_id, client_id and an array of channels.
10
+ #
11
+ # The server should check that the session_id matches up to the client_id
12
+ # and that the client is allowed to access the specified channels.
13
+ #
14
+ # If a status code other than 200 is encountered, the subscription_request fails
15
+ # and the client is disconnected.
16
+ #
17
+ # :subscription_url: http://localhost:3000/sessions/juggernaut_subscription
18
+
19
+ # === Broadcast and query authentication ===
20
+ # Leave all broadcast/query options uncommented to allow anyone to broadcast/query.
21
+ #
22
+ # Broadcast authentication in a production environment is very importantant since broadcasters
23
+ # can execute JavaScript on subscribed clients, leaving you vulnerable to cross site scripting
24
+ # attacks if broadcasters aren't authenticated.
25
+
26
+ # 1) Via IP address
27
+ #
28
+ # If specified, if a client has an ip that is specified in allowed_ips, than it is automatically
29
+ # authenticated, even if a secret_key isn't provided.
30
+ #
31
+ # This is the recommended method for broadcast authentication.
32
+ #
33
+ :allowed_ips:
34
+ - 127.0.0.1
35
+ # - 192.168.0.1
36
+
37
+ # 2) Via HTTP request
38
+ #
39
+ # If specified, if a client attempts a broadcast/query, without a secret_key or using an IP
40
+ # no included in allowed_ips, then broadcast_query_login_url will be called.
41
+ # Parameters passed, if given, are: session_id, client_id, channels and type.
42
+ #
43
+ # The server should check that the session_id matches up to the client id, and the client
44
+ # is allowed to perform that particular type of broadcast/query.
45
+ #
46
+ # If a status code other than 200 is encountered, the broadcast_query_login_url fails
47
+ # and the client is disconnected.
48
+ #
49
+ # :broadcast_query_login_url: http://localhost:3000/sessions/juggernaut_broadcast
50
+
51
+ # 3) Via shared secret key
52
+ #
53
+ # This secret key must be sent with any query/broadcast commands.
54
+ # It must be the same as the one in the Rails config file.
55
+ #
56
+ # You shouldn't authenticate broadcasts from subscribed clients using this method
57
+ # since the secret_key will be easily visible in the page (and not so secret any more)!
58
+ #
59
+ # :secret_key: ddcb9de8a2e4361cd74730792b424fd8a1ac0a9a
60
+
61
+ # == Subscription Logout ==
62
+
63
+ # If specified, logout_connection_url is called everytime a specific connection from a subscribed client disconnects.
64
+ # Parameters passed are session_id, client_id and an array of channels specific to that connection.
65
+ #
66
+ # :logout_connection_url: http://localhost:3000/sessions/juggernaut_connection_logout
67
+
68
+ # Logout url is called when all connections from a subscribed client are closed.
69
+ # Parameters passed are session_id and client_id.
70
+ #
71
+ # :logout_url: http://localhost:3000/sessions/juggernaut_logout
72
+
73
+ # === Miscellaneous ===
74
+
75
+ # timeout defaults to 10. A timeout is the time between when a client closes a connection
76
+ # and a logout_request or logout_connection_request is made. The reason for this is that a client
77
+ # may only temporarily be disconnected, and may attempt a reconnect very soon.
78
+ #
79
+ # :timeout: 10
80
+
81
+ # store_messages defaults to false. If this option is true, messages send to connections will be stored.
82
+ # This is useful since a client can then receive broadcasted message that it has missed (perhaps it was disconnected).
83
+ #
84
+ # :store_messages: false
85
+
86
+ # === Server ===
87
+
88
+ # Host defaults to "0.0.0.0". You shouldn't need to change this.
89
+ # :host: 0.0.0.0
90
+
91
+ # Port is mandatory
92
+ :port: 5001
93
+
94
+ # Defaults to value of :port. If you are doing port forwarding you'll need to configure this to the same
95
+ # value as :public_port in the juggernaut_hosts.yml file
96
+ # :public_port: 5001
97
+
@@ -0,0 +1,18 @@
1
+ # You should list any juggernaut hosts here.
2
+ # You need only specify the secret key if you're using that type of authentication (see juggernaut.yml)
3
+ #
4
+ # Name: Mapping:
5
+ # :port internal push server's port
6
+ # :host internal push server's host/ip
7
+ # :public_host public push server's host/ip (accessible from external clients)
8
+ # :public_port public push server's port
9
+ # :secret_key (optional) shared secret (should map to the key specified in the push server's config)
10
+ # :environment (optional) limit host to a particular RAILS_ENV
11
+
12
+ :hosts:
13
+ - :port: 5001
14
+ :host: 127.0.0.1
15
+ :public_host: 127.0.0.1
16
+ :public_port: 5001
17
+ # :secret_key: your_secret_key
18
+ # :environment: :development
@@ -0,0 +1,81 @@
1
+ var Pushify = (function() {
2
+ var root = window.location.protocol + "//" + window.location.host;
3
+
4
+ var strip = function(str) {
5
+ return str.replace(/^\s+/, '').replace(/\s+$/, '');
6
+ };
7
+
8
+ var handleCssFile = function(file) {
9
+ var links = document.getElementsByTagName("link");
10
+ var css = "/stylesheets" + file;
11
+ for (var i=0, length=links.length; i<length; i++) {
12
+ var l = links[i];
13
+ var link = strip(l.href).toLowerCase();
14
+
15
+ var compare = link.indexOf(root) >= 0 ? root + css : css;
16
+ if (link == compare || link.indexOf(compare + "?") >= 0) {
17
+ l.href = compare + "?" + Math.random();
18
+ }
19
+ }
20
+ };
21
+
22
+ var handleScssFile = function(file) {
23
+ var cssFile = "/compiled/" + file.match(/(.*)\.scss($|\?)/)[1];
24
+ handleCssFile(cssFile);
25
+ setTimeout(function() {
26
+ handleCssFile(cssFile);
27
+ }, 1000);
28
+ setTimeout(function() {
29
+ handleCssFile(cssFile);
30
+ }, 2000);
31
+ };
32
+
33
+ var handleImageFile = function(file) {
34
+ var images = document.getElementsByTagName("img");
35
+ var imgSrc = "/images" + file;
36
+ for (var i=0, length=images.length; i<length; i++) {
37
+ var img = images[i];
38
+ var src = strip(img.src).toLowerCase();
39
+
40
+ var compare = src.indexOf(root) >= 0 ? root + imgSrc : imgSrc;
41
+ if (src == compare || src.indexOf(compare + "?") >= 0) {
42
+ img.src = compare + "?" + Math.random();
43
+ }
44
+ }
45
+ };
46
+
47
+ var handleJsFile = function(file) {
48
+ var scripts = document.getElementsByTagName("script");
49
+ var scriptSrc = "/javascripts" + file;
50
+ for (var i=0, length=scripts.length; i<length; i++) {
51
+ var script = scripts[i];
52
+ var src = strip(script.src).toLowerCase();
53
+
54
+ var compare = src.indexOf(root) >= 0 ? root + scriptSrc : scriptSrc;
55
+ if (src == compare || src.indexOf(compare + "?") >= 0) {
56
+ var newScript = document.createElement("script");
57
+ newScript.src = compare + "?" + Math.random();
58
+ script.parentNode.replaceChild(newScript, script);
59
+ }
60
+ }
61
+ };
62
+
63
+
64
+ return {
65
+ touch: function(files) {
66
+
67
+ for (var i=0, length=files.length; i<length; i++) {
68
+ var file = strip(files[i]).toLowerCase();
69
+ if (file.match(/\.scss($|\?)/)) {
70
+ handleCssFile(file);
71
+ } else if (file.match(/\.css($|\?)/)) {
72
+ handleCssFile(file);
73
+ } else if (file.match(/\.js($|\?)/)) {
74
+ handleJsFile(file);
75
+ } else {
76
+ handleImageFile(file);
77
+ }
78
+ }
79
+ }
80
+ };
81
+ })();
@@ -0,0 +1,5 @@
1
+ /* SWFObject v2.0 <http://code.google.com/p/swfobject/>
2
+ Copyright (c) 2007 Geoff Stearns, Michael Williams, and Bobby van der Sluis
3
+ This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
4
+ */
5
+ var swfobject=function(){var Z="undefined",P="object",B="Shockwave Flash",h="ShockwaveFlash.ShockwaveFlash",W="application/x-shockwave-flash",K="SWFObjectExprInst",G=window,g=document,N=navigator,f=[],H=[],Q=null,L=null,T=null,S=false,C=false;var a=function(){var l=typeof g.getElementById!=Z&&typeof g.getElementsByTagName!=Z&&typeof g.createElement!=Z&&typeof g.appendChild!=Z&&typeof g.replaceChild!=Z&&typeof g.removeChild!=Z&&typeof g.cloneNode!=Z,t=[0,0,0],n=null;if(typeof N.plugins!=Z&&typeof N.plugins[B]==P){n=N.plugins[B].description;if(n){n=n.replace(/^.*\s+(\S+\s+\S+$)/,"$1");t[0]=parseInt(n.replace(/^(.*)\..*$/,"$1"),10);t[1]=parseInt(n.replace(/^.*\.(.*)\s.*$/,"$1"),10);t[2]=/r/.test(n)?parseInt(n.replace(/^.*r(.*)$/,"$1"),10):0}}else{if(typeof G.ActiveXObject!=Z){var o=null,s=false;try{o=new ActiveXObject(h+".7")}catch(k){try{o=new ActiveXObject(h+".6");t=[6,0,21];o.AllowScriptAccess="always"}catch(k){if(t[0]==6){s=true}}if(!s){try{o=new ActiveXObject(h)}catch(k){}}}if(!s&&o){try{n=o.GetVariable("$version");if(n){n=n.split(" ")[1].split(",");t=[parseInt(n[0],10),parseInt(n[1],10),parseInt(n[2],10)]}}catch(k){}}}}var v=N.userAgent.toLowerCase(),j=N.platform.toLowerCase(),r=/webkit/.test(v)?parseFloat(v.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,i=false,q=j?/win/.test(j):/win/.test(v),m=j?/mac/.test(j):/mac/.test(v);/*@cc_on i=true;@if(@_win32)q=true;@elif(@_mac)m=true;@end@*/return{w3cdom:l,pv:t,webkit:r,ie:i,win:q,mac:m}}();var e=function(){if(!a.w3cdom){return }J(I);if(a.ie&&a.win){try{g.write("<script id=__ie_ondomload defer=true src=//:><\/script>");var i=c("__ie_ondomload");if(i){i.onreadystatechange=function(){if(this.readyState=="complete"){this.parentNode.removeChild(this);V()}}}}catch(j){}}if(a.webkit&&typeof g.readyState!=Z){Q=setInterval(function(){if(/loaded|complete/.test(g.readyState)){V()}},10)}if(typeof g.addEventListener!=Z){g.addEventListener("DOMContentLoaded",V,null)}M(V)}();function V(){if(S){return }if(a.ie&&a.win){var m=Y("span");try{var l=g.getElementsByTagName("body")[0].appendChild(m);l.parentNode.removeChild(l)}catch(n){return }}S=true;if(Q){clearInterval(Q);Q=null}var j=f.length;for(var k=0;k<j;k++){f[k]()}}function J(i){if(S){i()}else{f[f.length]=i}}function M(j){if(typeof G.addEventListener!=Z){G.addEventListener("load",j,false)}else{if(typeof g.addEventListener!=Z){g.addEventListener("load",j,false)}else{if(typeof G.attachEvent!=Z){G.attachEvent("onload",j)}else{if(typeof G.onload=="function"){var i=G.onload;G.onload=function(){i();j()}}else{G.onload=j}}}}}function I(){var l=H.length;for(var j=0;j<l;j++){var m=H[j].id;if(a.pv[0]>0){var k=c(m);if(k){H[j].width=k.getAttribute("width")?k.getAttribute("width"):"0";H[j].height=k.getAttribute("height")?k.getAttribute("height"):"0";if(O(H[j].swfVersion)){if(a.webkit&&a.webkit<312){U(k)}X(m,true)}else{if(H[j].expressInstall&&!C&&O("6.0.65")&&(a.win||a.mac)){D(H[j])}else{d(k)}}}}else{X(m,true)}}}function U(m){var k=m.getElementsByTagName(P)[0];if(k){var p=Y("embed"),r=k.attributes;if(r){var o=r.length;for(var n=0;n<o;n++){if(r[n].nodeName.toLowerCase()=="data"){p.setAttribute("src",r[n].nodeValue)}else{p.setAttribute(r[n].nodeName,r[n].nodeValue)}}}var q=k.childNodes;if(q){var s=q.length;for(var l=0;l<s;l++){if(q[l].nodeType==1&&q[l].nodeName.toLowerCase()=="param"){p.setAttribute(q[l].getAttribute("name"),q[l].getAttribute("value"))}}}m.parentNode.replaceChild(p,m)}}function F(i){if(a.ie&&a.win&&O("8.0.0")){G.attachEvent("onunload",function(){var k=c(i);if(k){for(var j in k){if(typeof k[j]=="function"){k[j]=function(){}}}k.parentNode.removeChild(k)}})}}function D(j){C=true;var o=c(j.id);if(o){if(j.altContentId){var l=c(j.altContentId);if(l){L=l;T=j.altContentId}}else{L=b(o)}if(!(/%$/.test(j.width))&&parseInt(j.width,10)<310){j.width="310"}if(!(/%$/.test(j.height))&&parseInt(j.height,10)<137){j.height="137"}g.title=g.title.slice(0,47)+" - Flash Player Installation";var n=a.ie&&a.win?"ActiveX":"PlugIn",k=g.title,m="MMredirectURL="+G.location+"&MMplayerType="+n+"&MMdoctitle="+k,p=j.id;if(a.ie&&a.win&&o.readyState!=4){var i=Y("div");p+="SWFObjectNew";i.setAttribute("id",p);o.parentNode.insertBefore(i,o);o.style.display="none";G.attachEvent("onload",function(){o.parentNode.removeChild(o)})}R({data:j.expressInstall,id:K,width:j.width,height:j.height},{flashvars:m},p)}}function d(j){if(a.ie&&a.win&&j.readyState!=4){var i=Y("div");j.parentNode.insertBefore(i,j);i.parentNode.replaceChild(b(j),i);j.style.display="none";G.attachEvent("onload",function(){j.parentNode.removeChild(j)})}else{j.parentNode.replaceChild(b(j),j)}}function b(n){var m=Y("div");if(a.win&&a.ie){m.innerHTML=n.innerHTML}else{var k=n.getElementsByTagName(P)[0];if(k){var o=k.childNodes;if(o){var j=o.length;for(var l=0;l<j;l++){if(!(o[l].nodeType==1&&o[l].nodeName.toLowerCase()=="param")&&!(o[l].nodeType==8)){m.appendChild(o[l].cloneNode(true))}}}}}return m}function R(AE,AC,q){var p,t=c(q);if(typeof AE.id==Z){AE.id=q}if(a.ie&&a.win){var AD="";for(var z in AE){if(AE[z]!=Object.prototype[z]){if(z=="data"){AC.movie=AE[z]}else{if(z.toLowerCase()=="styleclass"){AD+=' class="'+AE[z]+'"'}else{if(z!="classid"){AD+=" "+z+'="'+AE[z]+'"'}}}}}var AB="";for(var y in AC){if(AC[y]!=Object.prototype[y]){AB+='<param name="'+y+'" value="'+AC[y]+'" />'}}t.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+AD+">"+AB+"</object>";F(AE.id);p=c(AE.id)}else{if(a.webkit&&a.webkit<312){var AA=Y("embed");AA.setAttribute("type",W);for(var x in AE){if(AE[x]!=Object.prototype[x]){if(x=="data"){AA.setAttribute("src",AE[x])}else{if(x.toLowerCase()=="styleclass"){AA.setAttribute("class",AE[x])}else{if(x!="classid"){AA.setAttribute(x,AE[x])}}}}}for(var w in AC){if(AC[w]!=Object.prototype[w]){if(w!="movie"){AA.setAttribute(w,AC[w])}}}t.parentNode.replaceChild(AA,t);p=AA}else{var s=Y(P);s.setAttribute("type",W);for(var v in AE){if(AE[v]!=Object.prototype[v]){if(v.toLowerCase()=="styleclass"){s.setAttribute("class",AE[v])}else{if(v!="classid"){s.setAttribute(v,AE[v])}}}}for(var u in AC){if(AC[u]!=Object.prototype[u]&&u!="movie"){E(s,u,AC[u])}}t.parentNode.replaceChild(s,t);p=s}}return p}function E(k,i,j){var l=Y("param");l.setAttribute("name",i);l.setAttribute("value",j);k.appendChild(l)}function c(i){return g.getElementById(i)}function Y(i){return g.createElement(i)}function O(k){var j=a.pv,i=k.split(".");i[0]=parseInt(i[0],10);i[1]=parseInt(i[1],10);i[2]=parseInt(i[2],10);return(j[0]>i[0]||(j[0]==i[0]&&j[1]>i[1])||(j[0]==i[0]&&j[1]==i[1]&&j[2]>=i[2]))?true:false}function A(m,j){if(a.ie&&a.mac){return }var l=g.getElementsByTagName("head")[0],k=Y("style");k.setAttribute("type","text/css");k.setAttribute("media","screen");if(!(a.ie&&a.win)&&typeof g.createTextNode!=Z){k.appendChild(g.createTextNode(m+" {"+j+"}"))}l.appendChild(k);if(a.ie&&a.win&&typeof g.styleSheets!=Z&&g.styleSheets.length>0){var i=g.styleSheets[g.styleSheets.length-1];if(typeof i.addRule==P){i.addRule(m,j)}}}function X(k,i){var j=i?"visible":"hidden";if(S){c(k).style.visibility=j}else{A("#"+k,"visibility:"+j)}}return{registerObject:function(l,i,k){if(!a.w3cdom||!l||!i){return }var j={};j.id=l;j.swfVersion=i;j.expressInstall=k?k:false;H[H.length]=j;X(l,false)},getObjectById:function(l){var i=null;if(a.w3cdom&&S){var j=c(l);if(j){var k=j.getElementsByTagName(P)[0];if(!k||(k&&typeof j.SetVariable!=Z)){i=j}else{if(typeof k.SetVariable!=Z){i=k}}}}return i},embedSWF:function(n,u,r,t,j,m,k,p,s){if(!a.w3cdom||!n||!u||!r||!t||!j){return }r+="";t+="";if(O(j)){X(u,false);var q=(typeof s==P)?s:{};q.data=n;q.width=r;q.height=t;var o=(typeof p==P)?p:{};if(typeof k==P){for(var l in k){if(k[l]!=Object.prototype[l]){if(typeof o.flashvars!=Z){o.flashvars+="&"+l+"="+k[l]}else{o.flashvars=l+"="+k[l]}}}}J(function(){R(q,o,u);if(q.id==u){X(u,true)}})}else{if(m&&!C&&O("6.0.65")&&(a.win||a.mac)){X(u,false);J(function(){var i={};i.id=i.altContentId=u;i.width=r;i.height=t;i.expressInstall=m;D(i)})}}},getFlashPlayerVersion:function(){return{major:a.pv[0],minor:a.pv[1],release:a.pv[2]}},hasFlashPlayerVersion:O,createSWF:function(k,j,i){if(a.w3cdom&&S){return R(k,j,i)}else{return undefined}},createCSS:function(j,i){if(a.w3cdom){A(j,i)}},addDomLoadEvent:J,addLoadEvent:M,getQueryParamValue:function(m){var l=g.location.search||g.location.hash;if(m==null){return l}if(l){var k=l.substring(1).split("&");for(var j=0;j<k.length;j++){if(k[j].substring(0,k[j].indexOf("="))==m){return k[j].substring((k[j].indexOf("=")+1))}}}return""},expressInstallCallback:function(){if(C&&L){var i=c(K);if(i){i.parentNode.replaceChild(L,i);if(T){X(T,true);if(a.ie&&a.win){L.style.display="block"}}L=null;T=null;C=false}}}}}();
@@ -0,0 +1,39 @@
1
+ require "yaml"
2
+ require "socket"
3
+ require "erb"
4
+
5
+ module Pushify # :nodoc:
6
+ module Helper
7
+
8
+ def pushify
9
+ Pushify.javascript_src
10
+ end
11
+
12
+ end
13
+
14
+ def self.javascript_src
15
+ '<script type="text/javascript" src="/pushify/pushify.js"></script>'
16
+ end
17
+
18
+ def self.juggernaut_src(options = {})
19
+ hosts = Pushify::Juggernaut::CONFIG[:hosts].select {|h| !h[:environment] or h[:environment] == ENV['RAILS_ENV'].to_sym }
20
+ random_host = hosts[rand(hosts.length)]
21
+ options = {
22
+ :host => (random_host[:public_host] || random_host[:host]),
23
+ :port => (random_host[:public_port] || random_host[:port]),
24
+ :width => '0px',
25
+ :height => '0px',
26
+ :swf_address => "/pushify/juggernaut.swf",
27
+ :ei_swf_address => "/pushify/expressinstall.swf",
28
+ :flash_version => 8,
29
+ :flash_color => "#fff",
30
+ :swf_name => "juggernaut_flash",
31
+ :bridge_name => "juggernaut",
32
+ :debug => false,
33
+ :reconnect_attempts => 3,
34
+ :reconnect_intervals => 3
35
+ }.merge(options)
36
+
37
+ "new Juggernaut(#{options.to_json});"
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ require 'fileutils'
2
+
3
+ module Pushify
4
+ module Install
5
+ def self.install!
6
+ File.exist?(File.join(Dir.pwd, "Gemfile")) ? self.install_3! : self.install_2!
7
+ end
8
+
9
+ def self.install_2!
10
+ return false if File.open('config/environments/development.rb', 'r').read.match(/\s*config\.gem\s+['"]pushify['"]/)
11
+
12
+ File.open('config/environments/development.rb', 'a') do |f|
13
+ f.write("\n\nconfig.gem 'pushify'\n")
14
+ end
15
+ true
16
+ end
17
+
18
+ def self.install_3!
19
+ return false if File.open('Gemfile', 'r').read.match(/\s*gem\s+['"]pushify['"]/)
20
+
21
+ File.open('Gemfile', 'a') do |f|
22
+ f.write("\n\ngem 'pushify'\n")
23
+ end
24
+ true
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,58 @@
1
+ ROOT = Dir.pwd unless defined? ROOT
2
+
3
+ module Pushify
4
+ module Juggernaut
5
+
6
+ if (File.exist?("#{ROOT}/config/juggernaut_hosts.yml"))
7
+ CONFIG_FILE = "#{ROOT}/config/juggernaut_hosts.yml"
8
+ else
9
+ CONFIG_FILE = "#{File.dirname(__FILE__)}/../../install/juggernaut_hosts.yml"
10
+ end
11
+
12
+ CONFIG = YAML::load(ERB.new(IO.read(CONFIG_FILE)).result).freeze
13
+ CR = "\0"
14
+
15
+ class << self
16
+
17
+ def send_to_all(data)
18
+ fc = {
19
+ :command => :broadcast,
20
+ :body => data,
21
+ :type => :to_channels,
22
+ :channels => []
23
+ }
24
+ send_data(fc)
25
+ end
26
+
27
+ def send_data(hash, response = false)
28
+ hash[:channels] = Array(hash[:channels]) if hash[:channels]
29
+ hash[:client_ids] = Array(hash[:client_ids]) if hash[:client_ids]
30
+
31
+ res = []
32
+ hosts.each do |address|
33
+ begin
34
+ hash[:secret_key] = address[:secret_key] if address[:secret_key]
35
+
36
+ @socket = TCPSocket.new(address[:host], address[:port])
37
+ # the \0 is to mirror flash
38
+ @socket.print(hash.to_json + CR)
39
+ @socket.flush
40
+ res << @socket.readline(CR) if response
41
+ ensure
42
+ @socket.close if @socket and !@socket.closed?
43
+ end
44
+ end
45
+ res.collect {|r| ActiveSupport::JSON.decode(r.chomp!(CR)) } if response
46
+ end
47
+
48
+ private
49
+
50
+ def hosts
51
+ CONFIG[:hosts].select {|h|
52
+ !h[:environment] or h[:environment].to_s == ENV['RAILS_ENV']
53
+ }
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,26 @@
1
+ module Pushify
2
+ class Rack
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ path = env["REQUEST_URI"]
9
+ if (Pushify::Rails::Assets.includes?(path))
10
+ Pushify::Rails::Assets.response(path)
11
+ else
12
+ status, headers, response = @app.call(env)
13
+
14
+ is_html = !response.is_a?(Array) && (headers["Content-Type"].nil? || headers["Content-Type"].include?("text/html"))
15
+
16
+ if (is_html && response.body.match(/<\/body>/))
17
+ pushify_src = Pushify.javascript_src
18
+ response.body = response.body.gsub(/(<\/body>)/, "#{pushify_src}</body>")
19
+ headers["Content-Length"] = (headers["Content-Length"].to_i + pushify_src.size).to_s
20
+ end
21
+
22
+ [status, headers, response]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,54 @@
1
+ require "pushify"
2
+ require "pushify/rack"
3
+
4
+ module Pushify
5
+ module Rails
6
+ def self.initialize
7
+ ActionView::Base.send(:include, Pushify::Helper)
8
+
9
+ if defined?(::Rails.configuration) && ::Rails.configuration.respond_to?(:middleware)
10
+ ::Rails.configuration.middleware.use("Pushify::Rack")
11
+ end
12
+ end
13
+
14
+ class Assets
15
+
16
+ def self.path_to_asset(asset)
17
+ File.join(File.dirname(__FILE__), "..", "..", "install", asset)
18
+ end
19
+
20
+ def self.assets
21
+ {
22
+ "/pushify/pushify.js" => ["text/application", "pushify.js"],
23
+ "/pushify/juggernaut.swf" => ["application/x-shockwave-flash", "juggernaut.swf"],
24
+ "/pushify/expressinstall.swf" => ["application/x-shockwave-flash", "/pushify/expressinstall.swf"]
25
+ }
26
+ end
27
+
28
+ def self.asset_body(asset)
29
+ if (asset == "pushify.js")
30
+ [
31
+ File.open(self.path_to_asset("json.js")).read,
32
+ File.open(self.path_to_asset("swfobject.js")).read,
33
+ File.open(self.path_to_asset("juggernaut.js")).read,
34
+ File.open(self.path_to_asset("pushify.js")).read,
35
+ Pushify.juggernaut_src
36
+ ].join("\n\n")
37
+ else
38
+ File.open(self.path_to_asset(asset)).read
39
+ end
40
+ end
41
+
42
+ def self.includes?(path)
43
+ self.assets.keys.include?(path)
44
+ end
45
+
46
+ def self.response(path)
47
+ asset = self.assets[path]
48
+ [200, {"Content-Type" => asset[0]}, [self.asset_body(asset[1])]]
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ Pushify::Rails.initialize
@@ -0,0 +1,85 @@
1
+ require "find"
2
+ require File.join(File.dirname(__FILE__), "juggernaut")
3
+
4
+ ROOT = Dir.pwd unless defined? ROOT
5
+
6
+ module Pushify
7
+ class Server
8
+ def self.run
9
+ self.new.run
10
+ end
11
+
12
+ attr_accessor :directories, :last_mtime
13
+
14
+ def initialize
15
+ self.directories = [
16
+ File.join(ROOT, "public", "stylesheets"),
17
+ File.join(ROOT, "public", "images"),
18
+ File.join(ROOT, "public", "javascripts"),
19
+ File.join(ROOT, "app", "stylesheets")
20
+ ]
21
+ self.last_mtime = Time.now
22
+ end
23
+
24
+ def run
25
+ if (!Pushify::Juggernaut)
26
+ puts "Juggernaut needs to be running for autospec to work"
27
+ return
28
+ end
29
+ begin
30
+ loop do
31
+ wait_for_changes
32
+ files = find_files_to_broadcast
33
+ self.last_mtime = files.values.map {|d| d[:mtime] }.max
34
+ broadcast_changes(files)
35
+ end
36
+ rescue Interrupt
37
+ puts
38
+ # Quit with ^C
39
+ end
40
+ end
41
+
42
+ def broadcast_changes(files)
43
+ puts "\n\nBroadcasting updates for: \n"
44
+ puts files.values.map{|d| d[:rio_name]}.join(", ")
45
+
46
+ Pushify::Juggernaut.send_to_all("Pushify.touch([#{ files.values.map{|d| "'" + d[:rio_name] + "'"}.join(", ") }])")
47
+ end
48
+
49
+ def wait_for_changes
50
+ Kernel.sleep 1 until !find_files_to_broadcast.empty?
51
+ end
52
+
53
+ def find_files_to_broadcast
54
+ files = find_files
55
+ files.each do |filename, data|
56
+ files.delete(filename) unless self.last_mtime < data[:mtime]
57
+ end
58
+ files
59
+ end
60
+
61
+ def find_files
62
+ result = {}
63
+ targets = self.directories
64
+
65
+ # from ZenTest
66
+ targets.each do |target|
67
+ Find.find(target) do |f|
68
+
69
+ next if test ?d, f
70
+ next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
71
+ next if f =~ /(\.svn)/ # svn files
72
+ next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
73
+ next if f =~ /\.DS_Store/ # OSX metadata
74
+
75
+ filename = f.sub(/^\.\//, '')
76
+
77
+ rio_name = Regexp.new("^#{Regexp.escape(target)}(.*)").match(filename)[1]
78
+ result[filename] = { :mtime => File.stat(filename).mtime, :rio_name => rio_name } rescue next
79
+ end
80
+ end
81
+
82
+ return result
83
+ end
84
+ end
85
+ end
data/lib/pushify.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "pushify/helper"
2
+ require "pushify/rails"
3
+ require "pushify/juggernaut"
4
+
5
+ module Pushify # :nodoc:
6
+ def self.install
7
+ ActionView::Base.send(:include, Pushify::Helper)
8
+ end
9
+ end
Binary file
data/pushify.gemspec ADDED
@@ -0,0 +1,60 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{pushify}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jason Tillery"]
12
+ s.date = %q{2011-01-20}
13
+ s.default_executable = %q{pushify}
14
+ s.email = %q{tilleryj@thinklinkr.com}
15
+ s.executables = ["pushify"]
16
+ s.files = [
17
+ ".specification",
18
+ "LICENSE",
19
+ "README.markdown",
20
+ "ROADMAP",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "bin/pushify",
24
+ "install/expressinstall.swf",
25
+ "install/json.js",
26
+ "install/juggernaut.js",
27
+ "install/juggernaut.swf",
28
+ "install/juggernaut.yml",
29
+ "install/juggernaut_hosts.yml",
30
+ "install/pushify.js",
31
+ "install/swfobject.js",
32
+ "lib/pushify.rb",
33
+ "lib/pushify/helper.rb",
34
+ "lib/pushify/install.rb",
35
+ "lib/pushify/juggernaut.rb",
36
+ "lib/pushify/rack.rb",
37
+ "lib/pushify/rails.rb",
38
+ "lib/pushify/server.rb",
39
+ "pkg/pushify-1.0.0.gem",
40
+ "pushify.gemspec",
41
+ "spec/css_push_spec.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+ s.homepage = %q{http://github.com/tilleryj/pushify}
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.7}
48
+ s.summary = %q{See updates you make to css, html, javascript, and images appear immediately in all of your browsers without having to refresh.}
49
+
50
+ if s.respond_to? :specification_version then
51
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
+ else
56
+ end
57
+ else
58
+ end
59
+ end
60
+
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "CssPush" do
4
+ it "should do nothing" do
5
+
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'CSS-Push'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end