This is the third post of our 4-part series on Advanced Functions programming techniques. We’ve also talked about Encryption, triggering 3rd party web services, and the Key-Value Store.
What are Event Handlers?
An Event Handler (EH
) is the user-defined Javascript code which runs when triggered by a given event handler type, against a given keyset and channel(s) – it is the logic to perform against a message meeting certain criteria.
Once written and satisfactory tested, an EH
can be deployed to the PubNub DSN via Functions.
What are Event Handler Types (ie. Functions Event Types)?
Functions feature allows developers to attach custom code to those message events in one of tw0 types of event handlers, either as a “before publish or fire” event which allows modification of message attributes, or as an “after publish or fire” event which takes place after the message has been published to the message channel.
The “before publish or fire” event type is ideal for applications like rate limiting or IP banning (since it can veto messages) or message attribute decoration (from remote APIs that don’t slow down message publishing). Conversely, it’s less ideal for latency-critical applications or other situations where having processing (and potential errors) directly in line with message publishing isn’t the best idea.
The “after publish or fire” event type is ideal for applications where behavior is asynchronous with respect to message processing (for example bridging to email or SMS), there is no desire to alter message attributes, and/or any failures should be isolated from message publishing.
In addition to high-performance, high-throughput data streams, PubNub offers Presence features that augment channels with the complete status of attached devices (including online/offline and custom attributes such as geolocation).
For these Presence applications, Functions offers the “after presence” event type. It allows developers to attach custom JavaScript logic to any presence event, including joining a channel, leaving a channel, timeout due to network connectivity, or state events where the user updates attributes such as geolocation.
Using the “After Presence” Event Type
In this advanced Functions tutorial, we dive into a simple example of how to handle data stream presence events to implement a “Welcome Bot” using custom serverless event handling in a real-time Angular 2 web app.
The sample application is an Angular 2 messaging app that we extend with a “Welcome Bot” to process presence events inside the network using a block running custom JavaScript. We'll create a PubNub BLOCK of JavaScript that runs entirely in the network and implements a presence feature to welcome users so that the web client UI code can stay simple and just display the messages.
Although our example is small using fixed messages, it could easily be extended to call remote web services for natural language detection, bridging to external message systems and more. With Functions, it’s very easy to integrate third-party applications into your data stream processing. You can find the complete catalog of pre-made Functions here.
Obtaining Your PubNub Developer Keys
The first thing you’ll need before you can create a real-time application with PubNub are publish/subscribe keys. Just in case you haven’t already, you can create an account, get your keys and be ready to use the PubNub network in less than 60 seconds using the handy signup form. Your keys are in your PubNub Admin Dashboard.
Once you do that, the publish and subscribe keys look like UUIDs and start with “pub-c-” and “sub-c-” prefixes respectively. Keep those handy – you’ll need to plug them in when initializing the PubNub object in your HTML5 app below.
Setting Up the Block
With Functions, it’s really easy to create code to run in the network. Here’s how to make it happen:
First, go to the application instance on the PubNub Admin Dashboard.
Create a new “after presence” block.
Paste in the block code from the next section.
Start the block, and test it using the “publish message” button and payload on the left-hand side of the screen. That’s all it takes to create your serverless code running in the cloud!
Diving into the Code – the block
You’ll want to grab the 30 lines of block JavaScript and save them to a file, say, pubnub_events_block.js
. It’s available as a Gist on GitHub for your convenience.
First up, we create a function to handle incoming messages. We include a dependency on the ‘pubnub' module so that we can send messages to the channel.
export default (request) => { const pubnub = require('pubnub');
Next, we set up the “welcome” message and terminal punctuation based on the presence message action (these are documented in the Presence API).
var message = "Welcome, "; var punctuation = "!"; if (request.message.action == 'join') { message = "Welcome, "; punctuation = "!"; } else if (request.message.action == 'timeout') { message = "Still there, "; punctuation = "?"; } else if (request.message.action == 'leave') { message = "Goodbye, "; punctuation = "."; } else if (request.message.action == 'state') { message = "New state from "; punctuation = "."; }
Finally, we publish the welcome message to the channel, including “PresenceBot” as the from
attribute, and pass the message on unmodified. (In the case of “after presence” Functions, we cannot modify the message anyway since it has already been published).
return pubnub.publish({ channel:'presence-channel', message:{ text:message + request.message.uuid + punctuation, from:'PresenceBot' } }).then(() => { return request.ok(); }); }
All in all, it doesn’t take a lot of code to add custom event processing to our application. We like that!
OK, let’s move on to the UI!
Diving into the Code – the User Interface
You’ll want to grab these 101 lines of HTML & JavaScript and save them to a file, say, pubnub_events_ui.html
.
The first thing you should do after saving the code is to replace two values in the JavaScript:
- YOUR_PUB_KEY: with the PubNub publish key mentioned above.
- YOUR_SUB_KEY: with the PubNub subscribe key mentioned above.
If you don’t, the UI will not be able to communicate with anything and probably clutter your console log with entirely too many errors.
Dependencies
First up, we have the JavaScript code & CSS dependencies of our application.
<!DOCTYPE html> <html> <head> <title>Angular 2</title> <script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script> <script src="https://unpkg.com/zone.js@0.7.2/dist/zone.js"></script> <script src="https://unpkg.com/reflect-metadata@0.1.9/Reflect.js"></script> <script src="https://unpkg.com/rxjs@5.0.1/bundles/Rx.js"></script> <script src="https://unpkg.com/@angular/core/bundles/core.umd.js"></script> <script src="https://unpkg.com/@angular/common/bundles/common.umd.js"></script> <script src="https://unpkg.com/@angular/compiler/bundles/compiler.umd.js"></script> <script src="https://unpkg.com/@angular/platform-browser/bundles/platform-browser.umd.js"></script> <script src="https://unpkg.com/@angular/forms/bundles/forms.umd.js"></script> <script src="https://unpkg.com/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script> <script src="https://unpkg.com/pubnub@4.3.3/dist/web/pubnub.js"></script> <script src="https://unpkg.com/pubnub-angular2@1.0.0-beta.8/dist/pubnub-angular2.js"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" /> </head>
For folks who have done front-end implementation with Angular2 before, these should be the usual suspects:
- CoreJS ES6 Shim, Zone.JS, Metadata Reflection, and RxJS : Dependencies of Angular2.
- Angular2 : core, common, compiler, platform-browser, forms, and dynamic platform browser modules.
- PubNub JavaScript client: to connect to our data stream integration channel.
- PubNub Angular2 JavaScript client: provides PubNub services in Angular2 quite nicely indeed.
In addition, we bring in the CSS features:
- Bootstrap: in this app, we use it just for vanilla UI presentation.
Overall, we were pretty pleased that we could build a nifty UI with so few dependencies. And with that… on to the UI!
The User Interface
Here’s what we intend the UI to look like:
main-component
tag that is managed by a single component that we’ll set up in the Angular2 code.<body> <main-component> Loading... </main-component>
Let’s skip forward and show that Angular2 component template. The h3
heading should be pretty self-explanatory. We provide a text box for the message, a simple button to perform the publish()
action to send a request to be processed by the BLOCK, and finally another simple button to leave()
the application and unsubscribe from all channels.
<div class="container"> <pre> NOTE: make sure to update the PubNub keys below with your keys, and ensure that the BLOCK settings are configured properly! </pre> <h3>MyApp Functions Event Types Integration</h3> <br /> Message: <br /> <input type="text" [(ngModel)]="toSend" placeholder="message" /> <input type="button" (click)="publish()" value="Send!" /> <br /> <br /> <input type="button" (click)="leave()" value="Leave!" /> <hr/> <br/> <br/> <ul> <li *ngFor="let item of messages.slice()"> <div>[{{item.message.from || 'PresenceBot'}}] {{JSON.stringify(item.message)}}</div> </li> </ul> </div>
The component UI consists of a simple list of events (in our case, the message objects). We iterate over the messages in the component scope using a trusty ngFor
. Each message includes the data from the network. In our case, the payloads include a text
message attribute as well as a from
user ID attribute to simplify the UI code.
And that’s it – a functioning real-time UI in just a handful of code (thanks, Angular2)!
The Angular2 Code
Right on! Now we’re ready to dive into the Angular2 code. It’s not a ton of JavaScript, so this should hopefully be pretty straightforward.
The first lines we encounter set up our application (with a necessary dependency on the PubNub AngularJS service) and a single component (which we dub main-component
).
<script> var app = window.app = {}; app.main_component = ng.core.Component({ selector: 'main-component', template: `...see previous...`
The component has a constructor that takes care of initializing the PubNub service and configuring the channel name, uuid, and initial values. NOTE: make sure the channel matches the channel specified by your BLOCK configuration and the BLOCK itself!
}).Class({ constructor: [PubNubAngular, function(pubnubService){ var self = this; self.pubnubService = pubnubService; self.uuid = 'user' + parseInt(Math.random() * 1000); self.JSON = JSON; self.channelName = 'presence-channel'; self.messages = []; self.toSend = "";
Early on, we initialize the pubnubService
with our credentials. Note the use of the uuid
parameter to specify the user’s unique identifier.
pubnubService.init({ publishKey: 'YOUR_PUB_KEY', subscribeKey: 'YOUR_SUB_KEY', uuid: self.uuid, ssl: true });
We subscribe to the relevant channel, create a dynamic attribute for the messages collection, and configure a blank event handler since the messages are presented unchanged from the incoming channel.
pubnubService.subscribe({channels: [self.channelName], triggerEvents: true}); self.messages = pubnubService.getMessage(this.channelName,function(){ // no handler necessary, dynamic collection of msg objects }); }],
We create a publish()
event handler that performs the action of publishing the new message to the PubNub channel.
publish: function(){ this.pubnubService.publish({ channel: this.channelName, message: {text:this.toSend, from:this.uuid} }); this.toSend = ""; },
We also make a leave()
event handler that performs the action of unsubscribing from all channels (to trigger the presence ‘leave' event type).
leave: function(){ this.pubnubService.unsubscribeAll(); } });
Now that we have a new component, we can create a main module for the Angular2 app that uses it. This is pretty standard boilerplate that configures dependencies on the Browser and Forms modules and the PubNubAngular service.
app.main_module = ng.core.NgModule({ imports: [ng.platformBrowser.BrowserModule, ng.forms.FormsModule], declarations: [app.main_component], providers: [PubNubAngular], bootstrap: [app.main_component] }).Class({ constructor: function(){} });
Finally, we bind the application bootstrap initialization to the browser DOM content loaded event.
document.addEventListener('DOMContentLoaded', function(){ ng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(app.main_module); });
We mustn’t forget close out the HTML tags accordingly.
}); </script> </body> </html>
Not too shabby for about 101 lines of HTML & JavaScript!
Functions References & Diving Deeper
There are a couple other things worth mentioning with respect to Functions event types and Data Stream features.
- Server-Side Filtering : using meta attributes and the Stream Controller API
- History and Replay : using the Storage & Playback APIs
- Presence : real-time status and custom state using the Presence API
All in all, we found it pretty easy to use advanced event type features on PubNub data streams using the API, and we look forward to using some of the more advanced capabilities in our future messaging applications!
Conclusion
Thank you so much for joining us in the custom event types post of our Advanced Functions Techniques series! Hopefully it’s been a useful experience learning about working with message and presence event types for various use cases. In future articles, we’ll dive deeper into additional techniques for crafting Functions and integrating other nifty services in real time web applications.
Stay tuned, and please reach out anytime if you feel especially inspired or need any help!