pushify 1.0.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.
@@ -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