Browse files

client, user & channel model logic + admin monitor improve

  • Loading branch information...
1 parent ad416b7 commit df931c0c9e7467997eccd9f1f77c5e2566c83fb4 @daraosn committed May 13, 2012
Showing with 257 additions and 110 deletions.
  1. +173 −71 lib/mustekala.js
  2. +31 −13 lib/routes.js
  3. +24 −19 lib/socket.js
  4. +2 −0 package.json
  5. +1 −1 public/example.html
  6. 0 public/{ → javascripts}/jquery.js
  7. +26 −5 views/admin.jade
  8. +0 −1 views/layout.jade
View
244 lib/mustekala.js
@@ -9,8 +9,13 @@ module.exports=function() {
,authKeys: {}
,authUsers: {}
- ,clientCount: 0 // to avoid counting object each time
- ,clients: {} // connected sockets
+ // Models:
+ // ,Client: {} // connected sockets
+ // ,Channel: {} // online channels
+ // ,User: {} // presence users
+
+ // Non-models:
+ ,clients: {}
,channels: {}
,users: {}
}
@@ -30,12 +35,69 @@ module.exports=function() {
require('./routes');
require('./utils');
+ // m.configureModels();
+
// m.generateAuthKey(m.config.password);
// setInterval(function() {
// console.log(m.authKeys);
// }, 1000);
}
+ // m.configureModels = function() {
+ // var Schema = require('jugglingdb').Schema;
+ // // var schema = new Schema('memory');
+ // var schema = new Schema('redis', {port: 6379});
+//
+ // // Setup models
+ // m.User = schema.define('User', {
+ // id: String
+ // ,data: Object
+ // ,clients: Array
+ // });
+ // m.Client = schema.define('Client', {
+ // id: String
+ // ,userId: String
+ // });
+ // m.Channel = schema.define('Channel', {
+ // id: String
+ // ,type: String
+ // ,clients: Array
+ // ,users: Array
+ // });
+ // }
+//
+ // m.addClient=function(client) {
+ // m.Client.updateOrCreate({
+ // id: client.id
+ // });
+ // }
+//
+ // m.removeClient=function(client) {
+ // m.Client.find(client.id, function(err, client) {
+ // client.destroy();
+ // });
+ // }
+//
+ // m.addChannel=function(channelName, channelType, callback) {
+ // m.Channel.updateOrCreate({
+ // id: channelName
+ // ,type: channelType
+ // }, function(error, channel) {
+ // if(callback) callback(channel);
+ // });
+ // }
+//
+ // m.subscribeClient=function(client, channelName, channelType) {
+ // m.addChannel(channelName, channelType, function(channel) {
+//
+ // channel.clients.push(client.id);
+ // });
+ // }
+//
+ // m.authUser=function(user, channelName, client) {
+//
+ // }
+
m.trigger=function(password, channel, action, data) {
if(password==mustekala.config.password) {
if(channel) {
@@ -64,113 +126,153 @@ module.exports=function() {
return false;
}
}
-
- // Debug
-// setInterval(function() {
- // console.log('****************************')
- // console.log(mustekala.clients,mustekala.channels,mustekala.users)
-// }, 1000);
-//
- m.addClient = function(client) {
- // console.log('m.addClient', client.id)
- m.clients[client.id]={
- 'client': client
- ,'channels': {}
- ,'channelCount': 0
+
+// *********************
+// Relations:
+// 'user' has many 'clients' // not in use
+// 'user' has many 'channels' // not in use
+// 'client' has one 'user' // used to associate user to client and notify others when client disconnects
+// 'client' has many 'channels' // used to notify memberLeft when client disconnects
+// 'channel' has many 'users' // used to get members on client side
+// 'channel' has many 'clients' // not in use
+// *********************
+
+ m.addClient = function(clientId) {
+ console.log('m.addClient', clientId)
+ m.clients[clientId]={
+ 'id': clientId
,'userId': null
+ ,'channels': {}
}
- m.clientCount++;
}
- m.addClientChannel = function(client, channelName, channelType) {
+ m.addChannel = function(channelName, channelType) {
+ console.log('m.addChannel', channelName, channelType);
if(!m.channels[channelName]) {
// create channel
m.channels[channelName]={
- 'type': channelType
+ 'name': channelName
+ ,'type': channelType
,'clients': {}
- ,'clientCount': 0
+ ,'users': {}
}
}
- if(!m.channels[channelName].clients[client.id]) {
+ }
+
+ m.addClientToChannel = function(clientId, channelName) {
+ console.log('m.addClientToChannel', clientId, channelName);
+ console.log('mmmm', m.channels[channelName], m.clients[clientId], m.channels[channelName].clients[clientId])
+ if(m.channels[channelName]&&m.clients[clientId]&&!m.channels[channelName].clients[clientId]) {
// create associations
- m.channels[channelName].clientCount++
- m.channels[channelName].clients[client.id]=new Date().getTime();
- m.clients[client.id].channelCount++;
- m.clients[client.id].channels[channelName]=new Date().getTime();
+ m.channels[channelName].clients[clientId]=new Date().getTime();
+ m.clients[clientId].channels[channelName]=new Date().getTime();
return true;
}
- return false; // client already subscribed this channeñ
+ console.log('m.addClientToChannel', false);
+ return false; // client already subscribed this channel
}
- m.removeClientChannel = function(client, channelName) {
- if(m.channels[channelName]) {
- // destroy associations
- m.clients[client.id].channelCount--;
- delete m.clients[client.id].channels[channelName];
- m.channels[channelName].clientCount--;
- delete m.channels[channelName].clients[client.id];
-
- // destroy channel if empty
- if(m.channels[channelName].clientCount===0) {
- delete m.channels[channelName];
+ m.addUser = function(userId, userData) {
+ if(!m.users[userId]) {
+ // create channel
+ m.users[userId]={
+ 'id': userId
+ ,'data': userData
+ ,'channels': {}
+ ,'clients': {}
}
+ }
+ }
+
+ m.getUser = function(userId) {
+ var user=m.users[userId];
+ return !user ? null : {
+ id: user.id
+ ,data: user.data
+ }
+ }
+
+ m.addUserToClient = function(userId, clientId) {
+ if(m.clients[clientId]&&!m.clients[clientId].userId) {
+ m.clients[clientId].userId=userId;
+ m.users[userId].clients[clientId]=new Date().getTime();
return true;
}
return false;
}
- m.eachClientChannel = function(client, handler) {
- // console.log('m.eachClientChannel', client.id)
- if(m.clients[client.id].channels) {
- for(var channelName in m.clients[client.id].channels) {
- // console.log('m.eachClientChannel.callback', channelName, m.clients[client.id].channels[channelName])
- handler(channelName, m.channels[channelName]);
- }
+ m.getClientUser = function(clientId) {
+ var client=m.clients[clientId];
+ return !client ? null : m.getUser(client.userId);
+ }
+
+ m.addUserToChannel = function(userId, channelName) {
+ console.log('m.addUserToChannel', userId, channelName);
+ if(m.channels[channelName]&&!m.channels[channelName].users[userId]) {
+ // create associations
+ m.users[userId].channels[channelName]=new Date().getTime();
+ m.channels[channelName].users[userId]=new Date().getTime();
+ return true;
}
+ return false; // user already subscribed this channel
}
- m.addUserClient = function(user, client) {
- console.log('m.addUserClient', user.id)
- if(!m.users[user.id]) {
- m.users[user.id] = {
- 'id': user.id
- ,'data': user.data
- ,'clients': {}
- ,'clientCount': 0
+ m.eachClientChannel = function(clientId, callback) {
+ var channels=m.clients[clientId].channels;
+ if(channels) {
+ for(var channelName in channels) {
+ callback(channelName, m.channels[channelName]);
}
}
-
- m.users[user.id].clientCount++;
- m.users[user.id].clients[client.id]=new Date().getTime();
- m.clients[client.id].userId = user.id;
}
- m.removeUserClient = function(user, client) {
- console.log('m.removeUserClient', user.id);
- if(m.users[user.id]) {
- m.users[user.id].clientCount--;
- delete m.users[user.id].clients[client.id];
- m.clients[client.id].userId = null;
+ m.removeUserFromChannel = function(channelName, userId) {
+ if(m.channels[channelName]&&m.users[userId]&&m.channels[channelName].users[userId]) {
+ // destroy associations
+ delete m.channels[channelName].users[userId];
+ delete m.users[userId].channels[channelName];
+ return true;
+ }
+ return false;
+ }
+
+ m.removeClientFromChannel = function(clientId, channelName) {
+ if(m.channels[channelName]) {
+ // destroy associations
+ delete m.clients[clientId].channels[channelName];
+ delete m.channels[channelName].clients[clientId];
// destroy channel if empty
- if(m.users[user.id].clientCount===0) {
- delete m.users[user.id];
+ if(Object.keys(m.channels[channelName].clients).length===0) {
+ delete m.channels[channelName];
}
-
return true;
}
return false;
}
- m.getClientUser = function(client) {
- var userId=m.clients[client.id].userId;
- return userId ? m.users[userId] : false;
+ m.removeUserFromClient = function(userId, clientId) {
+ if(m.clients[clientId]&&m.users[userId]&&m.clients[clientId].userId) {
+ // destroy associations
+ m.clients[clientId].userId = null;
+ delete m.users[userId].clients[clientId];
+
+ // destroy user if empty
+ if(Object.keys(m.users[userId].clients).length===0) {
+ delete m.users[userId];
+ }
+ return true;
+ }
+ return false;
}
-
- m.removeClient = function(client) {
+
+ m.removeClient = function(clientId) {
// console.log('m.removeClient', client.id)
- delete m.clients[client.id];
- m.clientCount--;
+ delete m.clients[clientId];
+ }
+
+ m.getChannelUsers = function(channelName, user) {
+
}
return m;
View
44 lib/routes.js
@@ -56,11 +56,21 @@ app.get('/', function(req, res) {
// TODO: Disable on production
app.post('/example/auth', function(req, res) {
// this should be grabbed from DB
- var exampleUser = {
- id: '12345'
- ,data: {
- name: 'Joseph'
- ,nickname: 'joe'
+ if(Math.random()<0.5) {
+ var exampleUser = {
+ id: '1000'
+ ,data: {
+ name: 'Joseph'
+ ,nickname: 'joe'
+ }
+ }
+ } else {
+ var exampleUser = {
+ id: '1001'
+ ,data: {
+ name: 'Nicholas'
+ ,nickname: 'nick'
+ }
}
}
@@ -80,18 +90,26 @@ app.post('/example/auth', function(req, res) {
});
});
-// TODO: Disable on production?
+// TODO: Disable or protect on production
app.get('/admin', function(req, res) {
- var clients=Object.keys(mustekala.clients)
- , channels=Object.keys(mustekala.channels)
- , users=Object.keys(mustekala.users);
+ res.render('admin.jade', { 'title': 'Mustekala Admin' });
+});
+app.post('/admin/data', function(req, res) {
+ if(req.body.fullView=='true') {
+ var clients=mustekala.clients
+ , channels=mustekala.channels
+ , users=mustekala.users;
+ } else {
+ var clients=Object.keys(mustekala.clients)
+ , channels=Object.keys(mustekala.channels)
+ , users=Object.keys(mustekala.users);
+ }
- res.render('admin.jade', {
- 'title': 'Mustekala Admin'
- ,'mustekalaJSON': JSON.stringify({
+ res.end(
+ JSON.stringify({
'clients': clients
,'channels': channels
,'users': users
})
- });
+ );
});
View
43 lib/socket.js
@@ -1,6 +1,7 @@
var io = mustekala.io = require('socket.io').listen(app);
io.configure('development', function(){
io.set('transports', ['websocket']);
+ io.set('log level', 1);
});
io.configure('production', function(){
io.enable('browser client minification');
@@ -19,43 +20,46 @@ io.configure('production', function(){
// Sockets
var socket = mustekala.socket = io.of('/mustekala');
socket.on('connection', function(client) {
- console.log('*** CLIENT CONNECTED ***');
-
- mustekala.addClient(client);
-
- var currentChannel;
+ mustekala.addClient(client.id);
client.on('subscribe', function(channelName,authKey) {
if(channelName) {
+ // Checks
var auth;
var authMatch=new RegExp(/^(presence|private)@(.+)$/).exec(channelName);
var channelType=authMatch?authMatch[1]:'public';
if(channelType!='public') {
// validate authKey
auth=mustekala.authKeys[authKey];
- console.log('checking auth key:',auth);
+ // console.log('checking auth key:',auth);
if(!auth||auth.channel!=channelName||!auth.user) {
// invalid authentication
// TODO: emit('error', 'Bad authentication')....
return;
}
}
- if(!mustekala.addClientChannel(client, channelName, channelType)) {
- // check if client already subscribed channel
+ // Create channel
+ mustekala.addChannel(channelName, channelType);
+
+ // Check if client already subscribed channel
+ if(!mustekala.addClientToChannel(client.id, channelName)) {
// TODO: emit('error', 'Already joined channel')....
return;
}
+ // Do presence channel jobs
if(channelType=="presence") {
// console.log('presence.memberJoin', channelName, auth.user)
- mustekala.addUserClient(auth.user, client);
+ mustekala.addUser(auth.user.id, auth.user.data);
+ mustekala.addUserToClient(auth.user.id, client.id);
+ mustekala.addUserToChannel(auth.user.id, channelName);
- var user=mustekala.getClientUser(client); // get user, even if we already have it
- socket.to(channelName).emit('presence.memberJoin', channelName, {id: user.id, data: user.data});
+ socket.to(channelName).emit('presence.memberJoin', channelName, mustekala.getUser(auth.user.id));
// Remember: must tell others about memberJoined before actually adding member to channel to avoid possible duplicates on client side.
}
-
+
+ // Subscribe client
client.join(channelName);
client.emit('subscribe', channelName);
}
@@ -65,15 +69,16 @@ socket.on('connection', function(client) {
var result=mustekala.trigger(password, channel, action, data)
});
client.on('disconnect',function() {
- mustekala.eachClientChannel(client, function(channelName, channel) {
- // console.log('********',channel.type)
+ var user=mustekala.getClientUser(client.id);
+ mustekala.eachClientChannel(client.id, function(channelName, channel) {
if(channel.type=="presence") {
- var user=mustekala.getClientUser(client);
- socket.to(channelName).emit('presence.memberLeave', channelName, {id: user.id, data: user.data});
- mustekala.removeUserClient(user, client);
+ socket.to(channelName).emit('presence.memberLeave', channelName, user);
+ mustekala.removeUserFromChannel(user.id, channelName);
}
- mustekala.removeClientChannel(client, channelName);
+ mustekala.removeClientFromChannel(client.id, channelName);
});
- mustekala.removeClient(client);
+
+ mustekala.removeUserFromClient(user.id, client.id);
+ mustekala.removeClient(client.id);
});
});
View
2 package.json
@@ -13,6 +13,8 @@
, "socket.io": "0.9.6"
, "request": "2.9.x"
, "qs": "0.5.0"
+ , "jugglingdb": "0.1.9"
+ , "redis": "0.7.2"
},
"devDependencies": {},
"optionalDependencies": {},
View
2 public/example.html
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
- <script type="application/javascript" src="jquery.js"></script>
+ <script type="application/javascript" src="javascripts/jquery.js"></script>
<script type="application/javascript" src="mustekala.js"></script>
<script>
$(function() {
View
0 public/jquery.js → public/javascripts/jquery.js
File renamed without changes.
View
31 views/admin.jade
@@ -1,7 +1,28 @@
div
+ script(type='text/javascript', src='/javascripts/prettyprint.js')
+ script(type='text/javascript', src='/javascripts/jquery.js')
script
- window.onload=function() {
- var mustekala=JSON.parse('!{mustekalaJSON}');
- document.body.appendChild(prettyPrint(mustekala,{maxArray:999,expanded:true,maxDepth:5}))
- setTimeout(function() { document.location=document.location.href }, 500)
- }
+ $(function() {
+ var fullView=false;
+ var refresh=function() {
+ $.ajax({
+ url: "/admin/data",
+ type: "POST",
+ dataType: 'json',
+ data: {fullView: fullView},
+ success: function(data) {
+ var table = prettyPrint(data,{maxArray:999,expanded:true,maxDepth:5});
+ $('#dump').html(table);
+ }
+ });
+ setTimeout(refresh,1000);
+ };
+ refresh();
+ $('#view-compact').click(function() { fullView=false; $('#dump').html(''); refresh(); return false; });
+ $('#view-full').click(function() { fullView=true; $('#dump').html(''); refresh(); return false; });
+ });
+ div(id="menu") View as:
+ a(id="view-compact",href="#") Compact
+ &nbsp;|&nbsp;
+ a(id="view-full",href="#") Full
+ div(id="dump",style="padding-top: 10px;") Loading...
View
1 views/layout.jade
@@ -2,6 +2,5 @@
html
head
title= title
- script(type='text/javascript', src='/javascripts/prettyprint.js')
link(rel='stylesheet', href='/stylesheets/style.css')
body!= body

0 comments on commit df931c0

Please sign in to comment.