Sending Client-To-Client Realtime Messages With The PubNub JavaScript Library
Last night, I finally got around to playing with the PubNub JavaScript library. PubNub provides an API for "push" communication that can broadcast textual messages to a wide range of devices including desktop, TV, and mobile. And, unlike some other realtime messaging libraries, PubNub allows clients to communicate directly with each other using nothing more than the PubNub API. The PubNub website appears to have a tremendous amount of information on it (and is, quite frankly, a bit overwhelming); as such, I'm quite sure that my understanding of the service only scratches the surface. With just a little bit of JavaScript, however, I was able to get this demo up and running quite quickly. And, without having to set up any ColdFusion or Node.js servers, the barrier to entry is incredibly low.
NOTE: In the title to this post, the term "client-to-client" is not meant literally; not as in peer-to-peer. I simply meant that the service does not require you to provide an additional server-side entity in order for the publish and subscribe feature to work.
With PubNub, your server-side application needs to be involved only as much as is necessary - the clients can push messages directly to each other. Your server-side application also has the ability to push messages to the clients. And, depending on the type of server you are running, your server-side application can either subscribe to the PubSub API; or, it can request a message history using a more traditional request/response lifecycle.
Ok, so that's about as deeply as I understand the PubNub service at this point (which is not even as much information as they have on their FAQ page). But, in about an hour or so, I was able to get a client-to-client demo working, complete with iPhone functionality:
<!DOCTYPE html>
<html>
<head>
<title>Using PubNub For Publish And Subscribe Communication</title>
<!-- Mobile viewport configuration. -->
<meta name="viewport" content = "width=device-width, user-scalable=no" />
<!-- Chat styles. -->
<link rel="stylesheet" type="text/css" href="./styles.css" />
<!-- jQuery. -->
<script type="text/javascript" src="./jquery-1.6.1.min.js"></script>
</head>
<body>
<!-- This is simple CHAT - what can I say, it's realtime. -->
<div class="messageLog">
<ul>
<!-- This will be populated dynamically. -->
</ul>
</div>
<form>
<input type="text" name="message" size="" />
<button type="submit" disabled="disabled">Send</button>
</form>
<!-- --------------------------------------------------- -->
<!-- --------------------------------------------------- -->
<!-- --------------------------------------------------- -->
<!-- --------------------------------------------------- -->
<!-- PubNub configuration details. -->
<div
id="pubnub"
pub-key="pub-7b6592f6-4ddb-4af6-b1b3-0e74cefe818d"
sub-key="sub-f4baaac5-87e0-11e0-b5b4-1fcb5dd0ecb4"
origin="pubsub.pubnub.com"
ssl="off">
</div>
<!--
Include PubNub from THEIR content delivery netrwork. In the
documentation, they recommend this as the only way to build
things appropriately; it allows them to continually update
the security features.
NOTE: The PubNub script MUST BE included AFTER the above DIV
tag that provides the configuration keys.
-->
<script type="text/javascript" src="http://cdn.pubnub.com/pubnub-3.1.min.js"></script>
<script type="text/javascript">
// This is the user object. Each user has a unique ID that
// allows it to be differentiated from all other clients on
// the same subscribed channel.
var user = {
uuid: null,
subscribed: false
};
// Cache frequent DOM references.
dom = {};
dom.messageLog = $( "div.messageLog" );
dom.messageLogItems = dom.messageLog.find( "> ul" );
dom.form = $( "form" );
dom.formInput = dom.form.find( "input" );
dom.formSubmit = dom.form.find( "button" );
// Override form submit to PUSH message.
dom.form.submit(
function( event ){
// Cancel the default event.
event.preventDefault();
// Make sure there is a message to send and that the
// user is subscribed.
if (
!user.subscribed ||
!dom.formInput.val().length
){
// Nothing more we can do with this request.
return;
}
// Send the message to the current channel.
sendMessage( dom.formInput.val() );
// Clear and focus the current message so the
// user can keep typing new messages.
dom.formInput
.val( "" )
.focus()
;
}
);
// I append the given message to the message log.
function appendMessage( message, isFromMe ){
// Creat the message item.
var messageItem = $( "<li />" ).text( message );
// If the message is form me (ie. the local user) then
// add the appopriate class for visual distinction.
if (isFromMe){
messageItem.addClass( "mine" );
}
// Add the message element to the list.
dom.messageLogItems.append( messageItem );
}
// I send the given message to all subscribed clients.
function sendMessage( message ){
// Immediately add the message to the UI so the user
// feels like the interface is super responsive.
appendMessage( message, true );
// Push the message to PubNub. Attach the user UUID as
// part of the message so we can filter it out when it
// gets echoed back (as part of our subscription).
PUBNUB.publish({
channel: "hello_world",
message: {
uuid: user.uuid,
message: message
}
});
};
// I receive the message on the current channel.
function receiveMessage( message ){
// Check to make sure the message is not just being
// echoed back.
if (message.uuid === user.uuid){
// This message has already been handled locally.
return;
}
// Add the message to the chat log.
appendMessage( message.message );
}
// -------------------------------------------------- //
// -------------------------------------------------- //
// In order to initialize the system, we have to wait for the
// client to receive a UUID and for the subscription to the
// PubNub server to be established.
var init = $.when(
// Get the user ID.
getUUID(),
// Subscribe to the PubNub channel.
$.Deferred(
function( deferred ){
// When the PubNub connection has been
// established, resolve the deferred container.
PUBNUB.subscribe({
channel: "hello_world",
callback: receiveMessage,
connect: deferred.resolve,
error: deferred.fail
});
}
)
);
// When the UUID has come back, prepare the user for use
// within the system.
init.done(
function( uuid ){
// Store the UUID with the user.
user.uuid = uuid;
// Flag the user as subscribed.
user.subscribed = true;
// Enable the message form.
dom.formSubmit.removeAttr( "disabled" );
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// -------------------------------------------------- //
// -------------------------------------------------- //
// NOTE: The following are just PubNub utility methods that
// have been converted from callback-based responses to
// deferred-based promises.
// I get a UUID from the PUBNUB server. I return a promise
// of the value to be returned.
function getUUID(){
// Since the core UUID method uses a callback, we need to
// create our own intermediary deferred object to wire
// the two workflows together.
var deferred = $.Deferred();
// Ask PubNub for a UUID.
PUBNUB.uuid(
function( uuid ){
// Resolve the uuid promise.
deferred.resolve( uuid );
}
);
// Return the UUID promise.
return( deferred.promise() );
}
</script>
</body>
</html>
As with all things "realtime," the first demo anyone wants to try out is a chat application. And, since PubNub provides an API that allows for both publish and subscribe functionality to be executed on the client, the above code is the only thing powering this demo.
I tried to write the code in a top-down manner; I'm using jQuery's Deferred objects and management to ensure that the user is fully subscribed and assigned a PubNub-provided UUID before they are allowed to send messages. Then, messages are added to the chat log as they are both sent out (for perceived performance) and received to and from the PubNub API respectively.
Once I got the code working, I tried to create some bi-directional communication between my Mac and my iPhone. And, much to my delight it worked perfectly and with better performance than I would have anticipated. Even when I was using the 3G network, the latency on the iPhone was entirely acceptable.
Here's what the conversation looked like on my Mac:
... and here's what the other side of the conversation looked like on my iPhone:
One thing that was especially interesting on the iPhone was that the messages appeared to sync up even after the mobile Safari browser was no longer in use. So, if I put the phone to sleep or if I exited the browser in order to use another application, any back-logged messages appeared to sync to the phone once the browser was re-focused. Pretty cool!
Overall, the PubNub service and JavaScript library appear to be quite promising. The thing that I found most frustrating about putting this together was the PubNub website. It's just a tab bit overwhelming. There's a huge number of links and demos; but, at the same time, there doesn't appear to be any consolidated documentation. For example, what's with that "pubnub" DIV in my demo? Honestly, I have no idea - that's just how one of their tutorials worked. Even the pricing is unclear. It seems that there is a certain amount of free broadcasting that one can perform (which is awesome for developers looking to dive in); but, I wasn't sure how to quantify it, nor did I really see how my credits related to my demo activity.
That said, the PubNub website definitely leaves you with the feeling that this realtime platform is no joke. It looks like they've put a tremendous amount of effort into putting it together and I'm looking forward to playing with it some more.
Want to use code from this post? Check out the license.
Reader Comments
What is your code for the "styles.css" page and the "jquery-1.6.1.min.js" page in your example?
@Allan,
Just some styling for the markup. And the other is the latest jQuery library (www.jquery.com). The CSS was pretty minimal, so I didn't bother posting it. The demo would work just the same with or without it (just not as pretty).
Important Update:
In my demo, I am including the "config" DIV tag after the Script tag that created the PUBNUB namespace. As such, the demo is actually running in the "demo" mode. In order for the keys to be used in the configuration, you actually have to include the Script tag from the CDN after the DIV tag.
I have updated the demo code to put the config DIV before the Script tag.
Ben
Here is your code embed as html5 page.
Chat Page:
http://www.alhanson.com/alhanson/tabfive/Fd05Mn09.html
=====================================================================
well view source to get the code then - the embed code
======================================================================
The chat folder contents:
benNadel.html
jquery-1.6.1.min.js
Server i am on is running PHP5.5 Meaning:
alhanson.com (the .com part) is in RAM
running on the Server.
Allan
Update: I have gotten the <embed> to work loading html page in to Windows IE 9 and Opera. To get Ben's page to work in my sandbox "alhanson.com" The Chat, you will have to call a friend on the phone or something, and have then login to chat. I have chatted with Hadrian from Caledonia located somewhere between Australia and New Zealand. You have to enter a lot "testing text" hit enter to push the text box down the page to get the scroll bar to pop out, and you can see Ben's embed page appear with your text scrolling up under my page. Because we embed a html document we have also embed the http protocol along with it. So if Ben had his page published as an html document some where else on the WWW i could link to it through the embed tag with full address. This fundamentally changes everything.
Basically html5 is a lot of nav bar links; section, article, and aside containers to embed powerful scripts behind. It is a layer of simple html formatted with a cascading style sheet run on a layer of powerful scripts, which seems to have a truly organic nature and evolving life of it own on the web.
Of course the next task is to be able to pass text from the simple html layer "the html5 page" to the embed html5 page with the script on it.
@Allan,
I just looked at the source of that page -- I'm still shocked that Embed tag works with an HTML page. That's just the craziest thing I've ever seen :D I really like the idea and it sounds like you're making some progress in where you can get it to work.
HTML5, in general, has some really cool stuff. I need to take some time to wrap my head around all the semantic tags. I get them from a philosophical standpoint; but, I just need to figure out how to organize them practically.
@All,
Also, it should be noted that pub/sub keys on the DIV can actually be placed right on the Script tag as well (so long as it as the id="pubnub" on it). This way, the same tag that includes the PubNub namespace can also define the keys.
Ben,
I have taken things on step farther. On my site a have a folder called "chat" with your html page in it benNadel.html, pubnub-3.1.min.js, jquery-1.6.1.min.js, and the chat.manifest. The chat.manifest the links pubnub-3.1.min.js, jquery-1.6.1.min.js to be cached on the two client's computers that have benNadel.html open. The link on your page links to pubnub-3.1.min.js on my site now. Seems to be working! I will attach the chat folder in am email and send it to you.
al
@Allan,
It's interesting - when I go to your chat page in Chrome, it actually says "Plug-in Missing".
Hello Ben,
I put your code in my php server and while I open it, it only have a disable send button. Could you please kindly provide more detail tutorial page for us?
Many Thanks,
Henry
"It's interesting - when I go to your chat page in Chrome, it actually says "Plug-in Missing". "
@ Ben that's the error I get in Firefox. I believe because Firefox see it as a "Plug-in" and not an object. I believe you will fined you are running an older version of Chrome. With the new version of Chrome on all of the computers I have tested on it has loaded. With the newer Version of Opera I have gotten it to open some of the time, sometimes you will get the "Plug-in Missing error" then you click refresh and it will load. Windows has a new online office just coming out and the page is load in the new version of IE running on 7.
I have added a link to a PhP web site I wrote a few years back. It is below the client to client chat link. The whole web site embeds in the html5. It needs some explanation. The whole web site loads as cached object or components when the first page loads. These objects are called to construct the rest of the pages of the web site from client side. The text that appears is queried from the SQL data base, filtered through an "includes PhP script", and spit out in html on to the page to be formatted by its CCS. Thus you have a web site that runs on little overhead of band width, spits out twits. "like twitter", which provides link to dynamic PDF type object. The dynamic PDF type object would be capable of running on it own client side scripts. The dynamic PDF type object would be like a Chapter\Short Story for third grade reader; let's say. It would contain a vocabulary list with a mouse over pronunciations, a data base of multiple choice questions, and a system of scoring the achievement of the client. The dynamic PDF type object would to have the ability off line Too. I just put this project on the back burner, because where was I going to find any programmers that were interested in creating any dynamic PDF type objects.
When wrote the swtchelp.com web site it was to help student connect to campus recourses over slow 28kps dialup connection that was available to local students. No one at the campus really cared or understood the problem. As long as the campus could connect to local school systems everything was fine. I wrote the swtchhelp.com web site to run over a satellite connection. A satellite connection has a slow ramp up, however I design my home page to appear in the middle of the ramp up process with functional navigation to the PDF object which could be pulled down off the satellite at T1 speeds 1500 kps.
In my vision, a barefoot nine year old boy in the third world begins his journey to a town with a satellite. He is carrying his village's Chrome Book type 3 pad with second solid sate hard drive for utilities and copy of OS, and third solid state hard drive for cache manifest back up. After taking care of business, He connects to the satellite and looks at the world, and as he does everything he sees is collected to the catch manifest to be carried back to his village. Lastly he goes to an online school and caches dynamic PDF objects for lessons on how to read. Back at his village under the yellow glow of a Kerosene lamp He and his friends peer in to the wonder of the computer screen in the excitement they see in the world about them. Power by an old car battery and charged through a solar panel in the day. It only takes time and a few pennies to help people help themselves and change the world about us.
@Henry,
Unfortunately, I know very little about PHP - I haven't programmed it in years. Sorry :(
@Allan,
Good sir, I love your vision! I felt good just reading it :)
I have successed put your code on my php website, however, no matter what I type in the sender side, the message I received only shows "object.object" .
@Henry,
That probably means you're trying to use an object rather than a property of that object. Whatever that object is, try logging it to the Firebug console or something to see what it is.
Bemused that in your video you talk about "realtime messaging", 'without a server' as something novel and exciting!
XMPP was ratified by the IETF in 2004 (and was jabber from 1999) and is the IETF's standard for 'real time' client to client messaging, incorporating IM and publish subscribe.
It's not new exciting tech, it's been around for 10+ years now and many companies offer XMPP hosting, no server needed :)
@David,
I talk about things that are novel and exciting for *me*. On this blog, I almost never talk about when things were created and started to exist... unless to express remorse that I haven't heard about it until now.
And even still, just cause something was around for a while doesn't mean that it was necessarily very accessible to the public. Take phone integration, for example. Sure, you could make phone systems for a long time. BUT, it's only since Twilio where that has become an extremely low barrier to entry with extremely low pricing that has made previously existing technologies so accessible.
David I found Bens video refreshing and exciting. The only people that would be bemused and find the post novel would represent the position of the Baby Bells. They have been mitigating the problem for years. The funny part to all this is it looks like they have painted them self's in to a corner. David I take it you haven't driven any of these ["tech" that have been around for 10 years] around the block.
The bottom line is: Why should the Baby Bells be allowed to charge twice for the same service? Charge you for a block of data [5 Gig of 50 bucks] smartphone/touchpad then turn around and charge you for Voice (which now they send you as data) going against the data that has already been paid for
I could still not find consolidated PubNub documentation; having a bit of a BlazeDS/WebSync background, I am just wondering about the following:
- Local Handling of generated messages: I know that WebSync does NOT send a message to the originating location by default; which is reasonable ! I benefitted from Your UUID approach to avoid this most often unwanted behaviour with PubNub; but superfluous messages are still sent around ...
- Channel Naming: I know that BlazeDS can handle hierarchical names; and wildcarding ! Any pointer to what is known about PubNub's approach ?
Hi!
I know its been a while since this post was written, anyway here is my question:
What if I want 3 users in the same channel and I want user A to be able to send a message to user B that user C can't (isn't allowed) to see?
hello sir,
i want to know that how to get all hostory from pubnub channel and is there any way to set start and end parameter to pubnub.history function.
please help me sir