Skip to content

Instantly share code, notes, and snippets.

@amcgregor
Created February 13, 2012 23:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save amcgregor/1821386 to your computer and use it in GitHub Desktop.
Save amcgregor/1821386 to your computer and use it in GitHub Desktop.
Nginx HTTP Push Module adapter for jQuery.
// 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;
};
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;
}
}
<!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