This time I'd like to share some results of experimenting with
Firebase and
Socket.IO while playing with real-time content delivery with
WebSockets a bit.
FireBase - NoSQL database, has a powerful API to store and sync data in real-time using WebSocket. It also has perfect
Security Rules system, which allows to restrict read and write access to data in the manner I was dreaming about when playing with
CouchDB back
in 2011. The goal was to let everyone access only his or her data right from the browser. So, here is an example of security rules that suites this need:
{
"rules": {
".read": false,
".write": false,
"users": {
"$user": {
".read": "auth.uid === $user",
".write": "auth.uid === $user"
}
},
"data": {
"$data": {
".read": "auth.uid === data.child('uid').val()",
".write": "auth.uid === data.child('uid').val()"
}
}
}
}
Then there are many ways to authenticate. With
Simple Login, it could be static web site
hosted on FireBase, similarly to
CouchApp, fantastic! But I decided to try custom authentication, and integrate
Firebase with
Atlassian Connect Add-On in
Node.js using
atlassian-connect-express.
Here is relevant part of client javascript. That simple to get own data:
addon.js
var rootRef = new Firebase("https://example-multi-tenant.firebaseio.com/data");
rootRef.auth(firebaseToken, function(error) {
rootRef.on("value", function(snapshot) {
var data = snapshot.val();
And
firebaseToken
is generated serverside basing on
authentication information provided by Atlassian Connect. So I map clientKey to uid for Firebase security rules:
routes/index.js:
var FirebaseTokenGenerator = require("firebase-token-generator");
var tokenGenerator = new FirebaseTokenGenerator(process.env.FIREBASE_SECRET);
app.get('/whoslooking', addon.authenticate(), function (req, res) {
var clientKey = req.session.clientKey;
var firebaseToken = tokenGenerator.createToken({ "uid": clientKey });
I did not come to particular useful use-case yet, and have tried to clone existing
Who's Looking add-on for JIRA, but there must be better ideas to try out with this approach.
For such simple case, it looked overcomplicated to me, so I've given
Socket.IO a try, and ended-up actually within
Engine.IO, which is the WebSocket semantics transport layer Socket.IO uses.
The main interesting part for me is authenticating connections, so that only add-on could connect. Here is the trick to leverage atlassian-connect-express authentication within allowRequest function that can decide whether to allow WebSocket request or not in
app.js:
var authentication = require('atlassian-connect-express/lib/middleware/authentication.js');
var authenticationHandler = authentication.authenticate(addon, true);
var server = http.createServer(app);
var io = require('engine.io').attach(server, {allowRequest: function(req, next) {
req.query = req._query;
req.header = function(name) {
return req.headers[name];
};
var res = {
send: function(code, msg) {
next(msg); // error
},
setHeader: function() {}
};
authenticationHandler(req, res, function(err) {
next(err, !err); // success
});
}});
Then Who's Looking logic itself is very simple. State is just kept in memory
serverside, and is updated when connection is open or closed, and every time simple message is sent to
client with all users currently looking at JIRA issue, no heart-beat (or long polling) and database. Nice and easy, isn't it?
Such add-on can be deployed to
Heroku, by the way, it has
support for WebSockets already!