Created
February 13, 2012 23:19
-
-
Save amcgregor/1821386 to your computer and use it in GitHub Desktop.
Nginx HTTP Push Module adapter for jQuery.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A jQuery extension to handle Nginx HTTP Push Module (NHPM) communication. | |
// alice@gothcandy.com -- Copyright (c) 2012 Alice Bevan-McGregor | |
// Released under an MIT license. | |
var Channel = function(options) { | |
this.settings = jQuery.extend({}, Channel.defaults, options); | |
this.alive = true; | |
this.failures = 0; | |
if ( this.settings.onMessage ) | |
jQuery(this).bind('channel.message', this.settings.onMessage); | |
if ( this.settings.onError ) | |
jQuery(this).bind('channel.', this.settings.onMessage); | |
this.listen(); | |
return this; | |
}; | |
Channel.defaults = { | |
publish: '/publish', | |
subscribe: '/subscribe', | |
channel: 'general', | |
accept: 'text/plain, application/json', | |
type: 'json', | |
retry: 500, // retry after 5 seconds | |
timeout: 300000 // 5 minutes | |
}; | |
Channel.prototype.listen = function() { | |
var self = this; | |
function closure() { | |
jQuery.ajax(this.settings.subscribe, { | |
accept: this.settings.accept, | |
cache: true, | |
data: {channel: this.settings.channel}, | |
dataType: this.settings.type, | |
global: false, | |
headers: {}, | |
ifModified: true, | |
type: 'GET', | |
context: this, | |
timeout: this.settings.timeout, | |
}).done(this.success).fail(this.failure).complete(this.done); | |
} | |
if ( this.failures ) { | |
setTimeout(function(){closure.apply(self)}, this.settings.retry); | |
return; | |
} | |
setTimeout(function(){closure.apply(self)}, 0); | |
} | |
Channel.prototype.error = function(xhr, status) { | |
if ( status == 'abort' ) | |
this.alive = false; | |
else if ( status == 'error' || status == 'parsererror' ) | |
this.failures++; | |
if ( this.failures > 3 ) | |
this.alive = false; | |
window.console.log("Failure:", xhr, status); | |
}; | |
Channel.prototype.success = function(data, status, xhr) { | |
// Reset failure count. | |
this.failures = 0; | |
window.console.log("Success:", data, status, xhr); | |
$(this).trigger('channel.message', [data, xhr]); | |
}; | |
Channel.prototype.done = function(xhr, status) { | |
window.console.log("Done:", status); | |
// notmodified, error, timeout, abort, parsererror | |
if ( status != 'success' ) | |
jQuery(this).trigger('channel.' + status, [xhr]); | |
if ( this.alive ) | |
this.listen() | |
}; | |
Channel.prototype.send = function(data) { | |
var encoded = JSON.stringify(data, null, 2); | |
var url = this.settings.publish + '?' + $.param({channel: this.settings.channel}); | |
return jQuery.ajax(url, { | |
accept: this.settings.accept, | |
cache: true, | |
data: encoded, | |
global: false, | |
type: 'POST', | |
context: this, | |
mimeType: 'application/json' | |
}); | |
}; | |
jQuery.channel = function(options) { | |
var channel = new Channel(options); | |
return channel; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
push_max_reserved_memory 16M; | |
server { | |
server_name push.site; | |
listen *:80; | |
gzip on; | |
gzip_http_version 1.1; | |
gzip_vary on; | |
gzip_comp_level 7; | |
gzip_proxied any; | |
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript text/x-python; | |
gzip_buffers 16 8k; | |
gzip_disable "MSIE [1-6]\.(?!.*SV1)"; | |
root /home/amcgregor/apps/push; | |
index index.html; | |
location /publish { | |
set $push_channel_id $arg_channel; | |
push_publisher; | |
push_message_timeout 2h; | |
push_max_message_buffer_length 10; | |
} | |
location /subscribe { | |
set $push_channel_id $arg_channel; | |
push_subscriber; | |
push_subscriber_concurrency broadcast; | |
default_type text/plain; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Push Chat Example</title> | |
<style type="text/css" media="screen"> | |
/* Reset */ | |
* { outline: none; border: none; margin: 0; padding: 0; border: none; color: inherit; background-color: transparent; line-height: 1.3; font-size: 10pt; font-weight: normal; list-style: none; font-family: 'Lucida Grande', 'Verdana', sans-serif; cursor: default; } | |
/* Base */ | |
html, body { min-height: 100%; } | |
</style> | |
<style type="text/css" media="screen"> | |
/* Button Common */ | |
button, button label { display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; } | |
button label { display: inline-block; } | |
button.icon label { padding-left: 22px; background: transparent url(icn/help.svg) center left no-repeat; background-size: 16px; } | |
</style> | |
<style type="text/css" media="screen"> | |
/* Button Clean */ | |
button { | |
background: #e3e3e3; | |
border: 1px solid #bbb; | |
-webkit-border-radius: 3px; | |
-moz-border-radius: 3px; | |
-ms-border-radius: 3px; | |
-o-border-radius: 3px; | |
border-radius: 3px; | |
-webkit-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-moz-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-ms-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-o-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
box-shadow: inset 0 0 1px 1px #f6f6f6; | |
color: #333; | |
line-height: 1; | |
padding: 5px 8px 6px; | |
text-align: center; | |
text-shadow: 0 1px 0 #fff; | |
} | |
button.pill { | |
-webkit-border-radius: 1.1em; | |
-moz-border-radius: 1.1em; | |
-ms-border-radius: 1.1em; | |
-o-border-radius: 1.1em; | |
border-radius: 1.1em; | |
} | |
button:hover { | |
background: #d9d9d9; | |
-webkit-box-shadow: inset 0 0 1px 1px #eaeaea; | |
-moz-box-shadow: inset 0 0 1px 1px #eaeaea; | |
-ms-box-shadow: inset 0 0 1px 1px #eaeaea; | |
-o-box-shadow: inset 0 0 1px 1px #eaeaea; | |
box-shadow: inset 0 0 1px 1px #eaeaea; | |
color: #222; | |
} | |
button:active, button:focus { | |
background: #d0d0d0; | |
-webkit-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-moz-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-ms-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-o-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
box-shadow: inset 0 0 1px 1px #e3e3e3; | |
color: #000; | |
} | |
button.default { | |
background: #d0d0d0; | |
-webkit-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-moz-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-ms-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-o-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
box-shadow: inset 0 0 1px 1px #f6f6f6; | |
color: #111; | |
} | |
button.default label { | |
font-weight: bold; | |
} | |
button.default:hover { | |
background: #d9d9d9; | |
-webkit-box-shadow: inset 0 0 1px 1px #eaeaea; | |
-moz-box-shadow: inset 0 0 1px 1px #eaeaea; | |
-ms-box-shadow: inset 0 0 1px 1px #eaeaea; | |
-o-box-shadow: inset 0 0 1px 1px #eaeaea; | |
box-shadow: inset 0 0 1px 1px #eaeaea; | |
color: #222; | |
} | |
button.default:active, button.default:focus { | |
background: #d0d0d0; | |
-webkit-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-moz-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-ms-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-o-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
box-shadow: inset 0 0 1px 1px #e3e3e3; | |
color: #000; | |
} | |
button:disabled, button:disabled:hover, button:disabled:active, button:disabled:focus { | |
background: #e3e3e3; | |
-webkit-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-moz-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-ms-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-o-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
box-shadow: inset 0 0 1px 1px #f6f6f6; | |
color: #777; | |
} | |
button.selected { | |
background: #444; | |
-webkit-box-shadow: inset 0 0 1px 1px #111; | |
-moz-box-shadow: inset 0 0 1px 1px #111; | |
-ms-box-shadow: inset 0 0 1px 1px #111; | |
-o-box-shadow: inset 0 0 1px 1px #111; | |
box-shadow: inset 0 0 1px 1px #111; | |
color: #ddd; | |
border-color: #999; | |
text-shadow: 0 1px 0 #000; | |
} | |
button.selected:hover { | |
background: #222; | |
color: #fff; | |
} | |
button.selected:active, button.selected:focus { | |
background: #d0d0d0; | |
-webkit-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-moz-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-ms-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
-o-box-shadow: inset 0 0 1px 1px #e3e3e3; | |
box-shadow: inset 0 0 1px 1px #e3e3e3; | |
text-shadow: 0 1px 0 #fff; | |
color: #000; | |
} | |
button.selected:disabled { | |
background: #444; | |
-webkit-box-shadow: inset 0 0 1px 1px #111; | |
-moz-box-shadow: inset 0 0 1px 1px #111; | |
-ms-box-shadow: inset 0 0 1px 1px #111; | |
-o-box-shadow: inset 0 0 1px 1px #111; | |
box-shadow: inset 0 0 1px 1px #111; | |
color: #999; | |
border-color: #999; | |
text-shadow: 0 1px 0 #000; | |
} | |
::-webkit-input-placeholder { color: #aaa; font-style: italic; text-shadow: 0 1px 0 #fff; } | |
input:-moz-placeholder { color: #aaa; font-style: italic; text-shadow: 0 1px 0 #fff; } | |
input, textarea { | |
background: #f0f0f0; | |
border: 1px solid #bbb; | |
-webkit-border-radius: 3px; | |
-moz-border-radius: 3px; | |
-ms-border-radius: 3px; | |
-o-border-radius: 3px; | |
border-radius: 3px; | |
-webkit-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-moz-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-ms-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
-o-box-shadow: inset 0 0 1px 1px #f6f6f6; | |
box-shadow: inset 0 0 1px 1px #f6f6f6; | |
color: #333; | |
line-height: 1; | |
padding: 4px 4px; | |
text-shadow: 0 1px 0 #fff; | |
} | |
</style> | |
<style type="text/css" media="screen"> | |
body { padding: 10px; } | |
.messages { width: 400px; height: 500px; position: relative; border: 1px solid #999; margin: 0 auto; } | |
.messages dl { position: absolute; overflow-y: auto; left: 0; top: 0; height: 445px; width: 280px; padding: 10px; } | |
.messages dt { clear: left; float: left; line-height: 1; margin-right: 1ex; } | |
.messages dd { line-height: 1; margin-bottom: 1ex; display: block; } | |
#messages { display: none; } | |
.messages form.send { position: absolute; bottom: 0; left: 0; width: 100%; background-color: #ccc; border-top: 1px solid #999; display: none; } | |
.messages form.send div { padding: 5px; } | |
.messages form.send input { margin: 0; display: inline-block; margin-left: 31px; width: 262px; } | |
.messages form.send button { position: absolute; right: 5px; top: 5px; width: 50px; } | |
.messages form.join { width: 100%; height: 500px; background-color: #ccc } | |
.messages form.join label { position: absolute; width: 270px; left: 10px; top: 175px; font-weight: bold; } | |
.messages form.join input { position: absolute; width: 270px; left: 10px; top: 200px; } | |
.messages form.join button { position: absolute; right: 10px; top: 235px; } | |
dt.message:after { content: ":"; } | |
</style> | |
</head> | |
<body> | |
<div class="messages"> | |
<form class="join"> | |
<label for="handle">To join, you will need a name:</label> | |
<input id="handle" type="text" name="handle" placeholder="Enter your handle here." autofocus> | |
<button>Join the Conversation</button> | |
</form> | |
<dl id="messages"></dl> | |
<form class="send"> | |
<div> | |
<button style="position: absolute; left: 5px; top: 5px; height: 26px; width: 26px; text-indent: -10000em;">Target</button> | |
<input id="message" type="text" name="text" placeholder="Enter your text here..." autofocus> | |
<button style="position: absolute; right: 36px; top: 5px; width: 50px;" class="default">Send</button> | |
<button style="position: absolute; right: 5px; top: 5px; height: 26px; width: 26px; text-indent: -10000em;">Settings</button> | |
</div> | |
</form> | |
</div> | |
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> | |
<script src="/jquery.nhpm.js"></script> | |
<script type="text/javascript"> | |
if (typeof String.prototype.startsWith != 'function') { | |
String.prototype.startsWith = function (str){ | |
return this.slice(0, str.length) == str; | |
}; | |
} | |
var Chat = function(channel, handle, UI) { | |
this.channel = channel; | |
this.handle = handle; | |
$(this.channel).on('channel.message', this.process); | |
this.ui = new UI(this, this.channel); | |
this.channel.send({s: 'muIC', a: 'join', h: this.handle}); | |
return this; | |
} | |
Chat.prototype.process = function(e, data, xhr) { | |
// Check which subsystem the message is directed to. | |
if ( data.s != 'muIC' ) return; | |
// If this is a message for us, prevent other handlers from wasting their time. | |
e.stopImmediatePropagation(); | |
$(this).trigger('chat.' + data.a, [data, xhr]); | |
} | |
Chat.prototype.send = function(text) { | |
this.channel.send({s: 'muIC', a: 'text', h: this.handle, t: text}); | |
}; | |
var DefaultChatInterface = function(chat, channel) { | |
this.chat = chat; | |
this.channel = channel; | |
this.container = $('#messages'); | |
var self = this; | |
$(channel).on({ | |
'chat.join': $.proxy(self.join, this), | |
'chat.part': $.proxy(self.part, this), | |
'chat.nick': $.proxy(self.nick, this), | |
'chat.text': $.proxy(self.message, this), | |
'chat.announce': $.proxy(self.announce, this), | |
'chat.state': $.proxy(self.state, this) | |
}); | |
} | |
DefaultChatInterface.prototype.join = function(e, data, xhr) { | |
this.printNotice(data.h, 'has joined the channel.', 'join'); | |
this.channel.send({s: 'muIC', a: 'announce', h: this.chat.handle}); | |
} | |
DefaultChatInterface.prototype.part = function(e, data, xhr) { | |
this.printNotice(data.h, 'has left the channel.', 'part'); | |
} | |
DefaultChatInterface.prototype.nick = function(e, data, xhr) { | |
this.printNotice(data.h, 'is now known as ' + (data.n || "Unknown User") + '.', 'nick'); | |
} | |
DefaultChatInterface.prototype.message = function(e, data, xhr) { | |
this.printMessage(data.h, data.t); | |
} | |
DefaultChatInterface.prototype.notify = function(e, data, xhr) { | |
this.printNotice(data.h, 'has joined the channel.'); | |
} | |
DefaultChatInterface.prototype.announce = function(e, data, xhr) { | |
// Update known users list. | |
} | |
DefaultChatInterface.prototype.state = function(e, data, xhr) { | |
this.printNotice(data.h, 'has joined the channel.'); | |
} | |
DefaultChatInterface.prototype.print = function(handle, text, css) { | |
if ( css === null || css === undefined ) css = ''; | |
$('<dt>'+handle+'</dt><dd>'+text+'</dd>').addClass(css).appendTo(this.container); | |
this.container[0].scrollTop = this.container[0].scrollHeight; | |
} | |
DefaultChatInterface.prototype.printNotice = function(handle, text, css) { | |
if ( css === null || css === undefined ) css = ''; | |
this.print(handle, text, 'notice ' + css); | |
} | |
DefaultChatInterface.prototype.printMessage = function(handle, text, css) { | |
if ( css === null || css === undefined ) css = ''; | |
this.print(handle, text, 'message ' + css); | |
} | |
var channel = $.channel(); | |
var chat = null; | |
$('form.join').submit(function(e){ | |
e.preventDefault(); | |
chat = new Chat(channel, $('#handle').val(), DefaultChatInterface); | |
$('.join').hide(); | |
$('#messages,.send').show(); | |
return false; | |
}); | |
$('form.send').submit(function(e){ | |
e.preventDefault(); | |
chat.send($('#message').val()); | |
$('#message').val(''); | |
return false; | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment