thywill
Version:
A Node.js clustered framework for single page web applications based on asynchronous messaging.
273 lines (244 loc) • 8.18 kB
JavaScript
/**
* @fileOverview
* Display class definition, a trivial example application.
*/
var util = require('util');
var path = require('path');
var fs = require('fs');
var async = require('async');
var Thywill = require('thywill');
var bootstrapManifest = require('./bootstrapManifest');
//-----------------------------------------------------------
// Class Definition
//-----------------------------------------------------------
/**
* @class
* A trivial example application.
*
* This application relays the cross-talk between clustered server
* processes down to the clients, where it is displayed.
*/
function Display (id) {
Display.super_.call(this, id);
// The channel used to broadcast display data.
this.channelId = 'display';
}
util.inherits(Display, Thywill.getBaseClass('Application'));
var p = Display.prototype;
//-----------------------------------------------------------
// Initialization
//-----------------------------------------------------------
/**
* @see Application#_defineBootstrapResources
*/
p._defineBootstrapResources = function (callback) {
var self = this;
// Text encoding throughout.
var encoding = 'utf8';
// An array of functions load up bootstrap resources.
var fns = [
// Add resources from files listed in the bootstrap manifest.
function (asyncCallback) {
self.storeBootstrapResourcesFromManifest(bootstrapManifest, asyncCallback);
},
// Add the Echo client Javascript separately, as it needs to be rendered
// as a template.
function (asyncCallback) {
// Load the file.
var originFilePath = path.resolve(__dirname, '../client/js/displayClient.js');
var data = fs.readFileSync(originFilePath, encoding);
// A little templating.
var clusterMembers = self.thywill.cluster.getClusterMemberIds().map(function (clusterMemberId, index, array) {
return {
id: clusterMemberId
};
});
clusterMembers = JSON.stringify(clusterMembers);
data = self.thywill.templateEngine.render(data, {
applicationId: self.id,
clusterMembers: clusterMembers,
uiTemplateId: 'display-template-ui',
textTemplateId: 'display-template-text',
connectionTemplateId: 'display-template-connection'
});
// Create and store the resource.
var resource = self.thywill.resourceManager.createResource(data, {
clientPath: '/display/js/displayClient.js',
encoding: encoding,
originFilePath: originFilePath,
weight: 50
});
self.storeBootstrapResource(resource, asyncCallback);
}
];
async.series(fns, callback);
};
/**
* @see Application#_setup
*/
p._setup = function (callback) {
var self = this;
// Start listening on generic cluster events.
this.thywill.cluster.on(this.thywill.cluster.eventNames.CLUSTER_MEMBER_DOWN, function (data) {
self.sendText(data.clusterMemberId + ' is down.');
});
this.thywill.cluster.on(this.thywill.cluster.eventNames.CLUSTER_MEMBER_UP, function (data) {
self.sendText(data.clusterMemberId + ' is up.');
});
// Listen in on clientTracker-specific cluster events.
this.thywill.cluster.on(this.thywill.clientTracker.clusterTask.connectionData, function (data) {
self.sendText('Connection data delivered from ' + data.clusterMemberId);
});
this.thywill.cluster.on(this.thywill.clientTracker.clusterTask.connectionDataRequest, function (data) {
self.sendText('Request for connection data from ' + data.clusterMemberId);
});
callback();
};
//-----------------------------------------------------------
// Methods
//-----------------------------------------------------------
/**
* @see Application#receive
*/
p.receivedFromClient = function (client, message) {
// Not used in this application - the clients are passive listeners.
};
/**
* Send a text message down to the clients for display.
*
* @param {string} text
*/
p.sendText = function (text) {
this.publish({
type: 'text',
text: text
});
};
/**
* Tell the clients that a user connected to this process.
*
* @param {string} connectionId
*/
p.sendConnectionNotice = function (connectionId) {
this.publish({
type: 'connection',
connectionId: connectionId
});
};
/**
* Give a specific client a list of current connections by cluster process.
*
* @param {object} connectionData
*/
p.sendConnectionList = function (connectionId) {
var self = this;
this.thywill.clientTracker.getConnectionData(function (error, data) {
if (error) {
self.thywill.log.error(error);
return;
}
var connections = {};
for (var clusterMemberId in data) {
connections[clusterMemberId] = Object.keys(data[clusterMemberId].connections);
}
var messageData = {
type: 'connectionList',
connections: connections
};
self.sendToConnection(connectionId, messageData);
});
};
/**
* Tell the clients that one or more users disconnected from this process.
*
* @param {string} connectionId
*/
p.sendDisconnectionNotice = function (connectionId) {
this.publish({
type: 'disconnection',
connectionId: connectionId
});
};
/**
* Publish this data to all clients.
*
* @param {object} data
* Data to send to the client.
*/
p.publish = function (data) {
data.clusterMemberId = this.thywill.cluster.getLocalClusterMemberId();
this.sendToChannel(this.channelId, data);
};
/**
* @see Application#connection
*/
p.connection = function (client) {
var self = this;
var connectionId = client.getConnectionId();
this.thywill.log.debug('Display: Client connected: ' + connectionId);
var fns = {
// Every client is subscribed to the same channel, used to broadcast updates.
subscribe: function (asyncCallback) {
self.subscribe(client, self.channelId, asyncCallback);
}
};
async.series(fns, function (error) {
if (error) {
self.thywill.log.error(error);
}
self.sendConnectionList(connectionId);
self.sendConnectionNotice(connectionId);
});
};
/**
* @see Application#connectionTo
*/
p.connectionTo = function (clusterMemberId, client) {
if (clusterMemberId !== this.thywill.cluster.getLocalClusterMemberId()) {
this.sendText(client.getConnectionId() + ' connected to ' + clusterMemberId + '.');
}
};
/**
* @see Application#disconnection
*/
p.disconnection = function (client) {
var connectionId = client.getConnectionId();
this.thywill.log.debug('Display: Client disconnected: ' + connectionId);
this.sendDisconnectionNotice(connectionId);
};
/**
* @see Application#disconnectionFrom
*/
p.disconnectionFrom = function (clusterMemberId, client) {
if (clusterMemberId !== this.thywill.cluster.getLocalClusterMemberId()) {
this.sendText(client.getConnectionId() + ' disconnected from ' + clusterMemberId + '.');
}
};
/**
* @see Application#clusterMemberDown
*/
p.clusterMemberDown = function (clusterMemberId, connectionData) {
var self = this;
// If the designated handler, update the remaining clients with the list
// of now-disconnected connection IDs. The clients will all be reconnecting
// (or already reconnected) to another process, but those will be using new
// connection IDs and will be correctly handled.
this.thywill.cluster.isDesignatedHandlerFor(clusterMemberId, function (error, isDesignatedHandler) {
if (error) {
self.thywill.log.error(error);
}
if (isDesignatedHandler) {
var connections = {};
connections[clusterMemberId] = Object.keys(connectionData.connections);
var data = {
type: 'disconnectionList',
connections: connections
};
self.sendToChannel(self.channelId, data);
}
});
};
//-----------------------------------------------------------
// Exports - Class Constructor
//-----------------------------------------------------------
module.exports = Display;