# required modules
crypto = require 'crypto'
config = require './config'
db = require './db'
buffer = require './buffer'
shared = require './shared'
# moderator socket connected flag
exports.moderated = false
# spawn socket engine
exports.spawn = (app) ->
# get socket.io
sio = require('socket.io').listen app
# moderator socket referene
socket_moderator = null
# users' sockets buffer
sockets = {}
# expired invites buffer
expired_invites = {}
# bans buffer
bans =
addresses: {}
nicks: {}
# on socket connection
sio.sockets.on 'connection', (socket) ->
### Events/messages handlers ###
# moderator socket sign-in routine #
signModerator = (err, chat) ->
# set nick to chat's main guest name
socket.set 'nick', chat.person, ->
# extracts nick from sockets buffer #
getClients = (moderated = true) ->
clients = for id, client of sockets when client.moderated is moderated
{ id: id, nick: client.nick }
# put socket into chat room
socket.join 'chat'
# mark socket as moderator socket
socket.set 'moderator', true
# send connected sockets list, chat events and chat progress to moderator
socket.emit 'mod connected',
clients:
moderated: getClients true
unmoderated: getClients false
progress: buffer.read 'chat'
events: buffer.read 'events'
# socket sign-in routine #
sign = (id, nick, invited = false) ->
# socket allowed flag
allowed = true
# if socket with invitation hash
if invited
# if invitation expired
if expired_invites.hasOwnProperty id
# emit message
socket.emit 'invitation expired'
# disconnect socket
socket.disconnect()
# disallow socket
allowed = false
else
# put into expired invitations
expired_invites[id] = null
# if socket allowed
if allowed
# store socket reference in sockets buffer
sockets[id] =
socket: socket
nick: nick
moderated: invited
# set socket's moderated flag
socket.set 'moderated', invited
# put socket into chat room
socket.join 'chat'
# emit sign-in operation confirmation message
socket.emit 'user signed', invited: invited
buffer.write shared.messages.sign(nick: nick), 'events'
# if moderator socket connected
if socket_moderator?
# emit socket sign-in event
socket_moderator.emit 'mod user signed',
id: id
nick: nick
allowed
# socket moderation routine #
moderateClient = (client, invited = false) ->
# set socket's moderated flag
sockets[client].moderated = true
# get socket reference
socket = sockets[client].socket
# set socket moderated flag
socket.set 'moderated', true, ->
# emit moderation confirmation message
socket.emit 'user moderated',
# send chat progress to connected socket
progress: buffer.read 'chat'
if invited and socket_moderator?
socket_moderator.emit 'user invited moderated',
id: client
# bulk moderation #
moderateBulk = (payload) ->
moderateClient client for client in payload.clients
clients = for key, id of payload.clients
sockets[id].nick
buffer.write shared.messages.moderateBulk(clients), 'events'
# sends sockets chat message to moderator socket #
clientMessage = (payload) ->
# get sender nick
socket.get 'id', (err, id) ->
buffer.write shared.messages.clientMessage({nick: sockets[id].nick, message: payload.message}), 'events'
# if success and moderator socket connected
if not err and id and socket_moderator?
# emit message and payload
socket_moderator.emit 'mod user message',
id: id
message: payload.message
# socket disconnection routine #
disconnectClient = (err, id) ->
# if success and socket in sockets buffer
if not err and id and sockets[id]?
buffer.write shared.messages.disconnectClient(nick: sockets[id].nick), 'events'
# remove socket from buffer
delete sockets[id]
# if moderator socket connected
if socket_moderator?
# emit disconnect message
socket_moderator.emit 'mod user unsigned',
id: id
# chat closure routine #
close = ->
# broadcast closure message
socket_moderator.broadcast.emit 'mod close'
# forced sockets disconnect
for id, client of sockets
client.socket.disconnect()
# empty buffers buffer
sockets = {}
expired_invites = {}
bans =
addresses: {}
nicks: {}
# save chat data
db.saveChat (chat) ->
id = chat._id
delete chat._id
chat.active = false
chat.started = false
chat.history = buffer.read 'chat'
buffer.wipe()
id
# performs chat database quasi-locking routine #
start = ->
# save flag in database
db.saveChat (chat) ->
id = chat._id
delete chat._id
chat.started = true
id
# calculates message digest based on configured global hashing algorith
digest = (message) ->
hash = crypto.createHash config.app.hashing()
hash.update message
hash.digest 'hex'
timestamp = ->
dt = new Date()
hours = parseInt dt.getHours()
minutes = parseInt dt.getMinutes()
seconds = parseInt dt.getSeconds()
if hours < 10
hours = "0#{hours}"
if minutes < 10
minutes = "0#{minutes}"
if seconds < 10
seconds = "0#{seconds}"
"#{hours}:#{minutes}:#{seconds}"
ban = (payload) ->
for id, key in payload.clients
bans.nicks[id] = true
bans.addresses[sockets[id].socket.handshake.address.address] = true
sockets[id].socket.emit 'user banned'
sockets[id].socket.disconnect()
delete sockets[id]
### Communication section ###
# weak moderator socket discovery
if socket.handshake.headers.referer.indexOf('chat/moderator') > 0
# moderator socket connected flag
exports.moderated = true
# moderator socket reference
socket_moderator = socket
# call moderator sign-in routine
db.getChat signModerator
# moderator socket disconnect routine
socket.on 'disconnect', ->
# wipe moderator socket reference
socket_moderator = null
# moderator socket not connected flag
exports.moderated = false
# if moderator socket not discovered
else
db.getChat (err, chat) ->
# if chat closed (access with invitations)
if chat.closed
# emit message
socket.emit 'chat closed'
# if chat opened
else
if bans.addresses.hasOwnProperty socket.handshake.address.address
socket.emit 'user banned'
else
socket.get 'id', (err, id) ->
# if socket not already signed-in
if not err and not id
# put socket in purgatory (moderator's waiting room)
socket.join 'purgatory'
# emit message
socket.emit 'user unsigned'
# calls socket disconnect routine
socket.on 'disconnect', ->
socket.get 'id', disconnectClient
# handles socket invitation request routine
socket.on 'invitation', (payload) ->
db.getChat (err, chat) ->
# validate invitation
if chat.invites.hasOwnProperty payload.invitation
# get nick assigned to invitation
nick = chat.invites[payload.invitation]
id = digest nick
# set assigned nick
socket.set 'id', id, ->
# call socket sign-in routine
if sign id, nick, true
# call socket moderate routine
moderateClient id, true
else
socket.emit 'invitation invalid'
# handles socket manual sign-in routine
socket.on 'user sign', (payload) ->
id = digest payload.nick
# check against nicks ban list
if bans.nicks.hasOwnProperty id
socket.emit 'user nick banned'
# if nick already binded to different socket
else if sockets.hasOwnProperty id
socket.emit 'user nick invalid'
else
socket.set 'id', id, ->
# call socket sign-in routine
sign id, payload.nick
# handles moderator socket asked question broadcast routine
socket.on 'mod question', (payload) ->
payload.time = timestamp()
# write payload to chat's buffer
buffer.write payload, 'chat'
# broadcast payload
socket_moderator.broadcast.emit 'mod user question', payload
# handles moderator socket message broadcast routine
socket.on 'mod message', (payload) ->
# store moderator socket reference
sock = socket_moderator
sock.get 'nick', (err, nick) ->
# get moderator socket nick
payload.nick = nick
payload.time = timestamp()
# write payload to chat's buffer
buffer.write payload, 'chat'
# broadcast payload
sock.broadcast.emit 'mod answer', payload
# bind handlers to events
socket.on 'mod moderate', moderateBulk
socket.on 'mod ban', ban
socket.on 'user message', clientMessage
socket.on 'mod close', close
socket.on 'mod start', start
console.log 'Chat spawned'