From 4889e9732d2458d60e2a079d3e54e128f6ce1b53 Mon Sep 17 00:00:00 2001 From: BoHong Li Date: Wed, 8 Mar 2017 18:45:51 +0800 Subject: Use JavaScript Standard Style Introduce JavaScript Standard Style as project style rule, and fixed all fail on backend code. --- lib/realtime.js | 1705 +++++++++++++++++++++++++++---------------------------- 1 file changed, 846 insertions(+), 859 deletions(-) (limited to 'lib/realtime.js') diff --git a/lib/realtime.js b/lib/realtime.js index c1db6886..cff795c7 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -1,937 +1,924 @@ -//realtime -//external modules -var cookie = require('cookie'); -var cookieParser = require('cookie-parser'); -var url = require('url'); -var async = require('async'); -var LZString = require('lz-string'); -var randomcolor = require("randomcolor"); -var Chance = require('chance'), - chance = new Chance(); -var moment = require('moment'); - -//core -var config = require("./config.js"); -var logger = require("./logger.js"); -var history = require("./history.js"); -var models = require("./models"); - -//ot -var ot = require("./ot/index.js"); - -//public +// realtime +// external modules +var cookie = require('cookie') +var cookieParser = require('cookie-parser') +var url = require('url') +var async = require('async') +var LZString = require('lz-string') +var randomcolor = require('randomcolor') +var Chance = require('chance') +var chance = new Chance() +var moment = require('moment') + +// core +var config = require('./config.js') +var logger = require('./logger.js') +var history = require('./history.js') +var models = require('./models') + +// ot +var ot = require('./ot/index.js') + +// public var realtime = { - io: null, - onAuthorizeSuccess: onAuthorizeSuccess, - onAuthorizeFail: onAuthorizeFail, - secure: secure, - connection: connection, - getStatus: getStatus, - isReady: isReady -}; - -function onAuthorizeSuccess(data, accept) { - accept(); + io: null, + onAuthorizeSuccess: onAuthorizeSuccess, + onAuthorizeFail: onAuthorizeFail, + secure: secure, + connection: connection, + getStatus: getStatus, + isReady: isReady } -function onAuthorizeFail(data, message, error, accept) { - accept(); //accept whether authorize or not to allow anonymous usage +function onAuthorizeSuccess (data, accept) { + accept() } -//secure the origin by the cookie -function secure(socket, next) { - try { - var handshakeData = socket.request; - if (handshakeData.headers.cookie) { - handshakeData.cookie = cookie.parse(handshakeData.headers.cookie); - handshakeData.sessionID = cookieParser.signedCookie(handshakeData.cookie[config.sessionname], config.sessionsecret); - if (handshakeData.sessionID && +function onAuthorizeFail (data, message, error, accept) { + accept() // accept whether authorize or not to allow anonymous usage +} + +// secure the origin by the cookie +function secure (socket, next) { + try { + var handshakeData = socket.request + if (handshakeData.headers.cookie) { + handshakeData.cookie = cookie.parse(handshakeData.headers.cookie) + handshakeData.sessionID = cookieParser.signedCookie(handshakeData.cookie[config.sessionname], config.sessionsecret) + if (handshakeData.sessionID && handshakeData.cookie[config.sessionname] && - handshakeData.cookie[config.sessionname] != handshakeData.sessionID) { - if (config.debug) - logger.info("AUTH success cookie: " + handshakeData.sessionID); - return next(); - } else { - next(new Error('AUTH failed: Cookie is invalid.')); - } - } else { - next(new Error('AUTH failed: No cookie transmitted.')); - } - } catch (ex) { - next(new Error("AUTH failed:" + JSON.stringify(ex))); + handshakeData.cookie[config.sessionname] !== handshakeData.sessionID) { + if (config.debug) { logger.info('AUTH success cookie: ' + handshakeData.sessionID) } + return next() + } else { + next(new Error('AUTH failed: Cookie is invalid.')) + } + } else { + next(new Error('AUTH failed: No cookie transmitted.')) } + } catch (ex) { + next(new Error('AUTH failed:' + JSON.stringify(ex))) + } } -function emitCheck(note) { - var out = { - title: note.title, - updatetime: note.updatetime, - lastchangeuser: note.lastchangeuser, - lastchangeuserprofile: note.lastchangeuserprofile, - authors: note.authors, - authorship: note.authorship - }; - realtime.io.to(note.id).emit('check', out); +function emitCheck (note) { + var out = { + title: note.title, + updatetime: note.updatetime, + lastchangeuser: note.lastchangeuser, + lastchangeuserprofile: note.lastchangeuserprofile, + authors: note.authors, + authorship: note.authorship + } + realtime.io.to(note.id).emit('check', out) } -//actions -var users = {}; -var notes = {}; -//update when the note is dirty -var updater = setInterval(function () { - async.each(Object.keys(notes), function (key, callback) { - var note = notes[key]; - if (note.server.isDirty) { - if (config.debug) logger.info("updater found dirty note: " + key); - note.server.isDirty = false; - updateNote(note, function(err, _note) { - // handle when note already been clean up - if (!notes[key] || !notes[key].server) return callback(null, null); - if (!_note) { - realtime.io.to(note.id).emit('info', { - code: 404 - }); - logger.error('note not found: ', note.id); - } - if (err || !_note) { - for (var i = 0, l = note.socks.length; i < l; i++) { - var sock = note.socks[i]; - if (typeof sock !== 'undefined' && sock) { - setTimeout(function () { - sock.disconnect(true); - }, 0); - } - } - return callback(err, null); - } - note.updatetime = moment(_note.lastchangeAt).valueOf(); - emitCheck(note); - return callback(null, null); - }); - } else { - return callback(null, null); - } - }, function (err) { - if (err) return logger.error('updater error', err); - }); -}, 1000); -function updateNote(note, callback) { - models.Note.findOne({ - where: { - id: note.id +// actions +var users = {} +var notes = {} +// update when the note is dirty +setInterval(function () { + async.each(Object.keys(notes), function (key, callback) { + var note = notes[key] + if (note.server.isDirty) { + if (config.debug) logger.info('updater found dirty note: ' + key) + note.server.isDirty = false + updateNote(note, function (err, _note) { + // handle when note already been clean up + if (!notes[key] || !notes[key].server) return callback(null, null) + if (!_note) { + realtime.io.to(note.id).emit('info', { + code: 404 + }) + logger.error('note not found: ', note.id) } - }).then(function (_note) { - if (!_note) return callback(null, null); - // update user note history - var tempUsers = Object.assign({}, note.tempUsers); - note.tempUsers = {}; - Object.keys(tempUsers).forEach(function (key) { - updateHistory(key, note, tempUsers[key]); - }); - if (note.lastchangeuser) { - if (_note.lastchangeuserId != note.lastchangeuser) { - models.User.findOne({ - where: { - id: note.lastchangeuser - } - }).then(function (user) { - if (!user) return callback(null, null); - note.lastchangeuserprofile = models.User.getProfile(user); - return finishUpdateNote(note, _note, callback); - }).catch(function (err) { - logger.error(err); - return callback(err, null); - }); - } else { - return finishUpdateNote(note, _note, callback); + if (err || !_note) { + for (var i = 0, l = note.socks.length; i < l; i++) { + var sock = note.socks[i] + if (typeof sock !== 'undefined' && sock) { + setTimeout(function () { + sock.disconnect(true) + }, 0) } - } else { - note.lastchangeuserprofile = null; - return finishUpdateNote(note, _note, callback); + } + return callback(err, null) } - }).catch(function (err) { - logger.error(err); - return callback(err, null); - }); + note.updatetime = moment(_note.lastchangeAt).valueOf() + emitCheck(note) + return callback(null, null) + }) + } else { + return callback(null, null) + } + }, function (err) { + if (err) return logger.error('updater error', err) + }) +}, 1000) + +function updateNote (note, callback) { + models.Note.findOne({ + where: { + id: note.id + } + }).then(function (_note) { + if (!_note) return callback(null, null) + // update user note history + var tempUsers = Object.assign({}, note.tempUsers) + note.tempUsers = {} + Object.keys(tempUsers).forEach(function (key) { + updateHistory(key, note, tempUsers[key]) + }) + if (note.lastchangeuser) { + if (_note.lastchangeuserId !== note.lastchangeuser) { + models.User.findOne({ + where: { + id: note.lastchangeuser + } + }).then(function (user) { + if (!user) return callback(null, null) + note.lastchangeuserprofile = models.User.getProfile(user) + return finishUpdateNote(note, _note, callback) + }).catch(function (err) { + logger.error(err) + return callback(err, null) + }) + } else { + return finishUpdateNote(note, _note, callback) + } + } else { + note.lastchangeuserprofile = null + return finishUpdateNote(note, _note, callback) + } + }).catch(function (err) { + logger.error(err) + return callback(err, null) + }) } -function finishUpdateNote(note, _note, callback) { - if (!note || !note.server) return callback(null, null); - var body = note.server.document; - var title = note.title = models.Note.parseNoteTitle(body); - var values = { - title: title, - content: body, - authorship: note.authorship, - lastchangeuserId: note.lastchangeuser, - lastchangeAt: Date.now() - }; - _note.update(values).then(function (_note) { - saverSleep = false; - return callback(null, _note); - }).catch(function (err) { - logger.error(err); - return callback(err, null); - }); + +function finishUpdateNote (note, _note, callback) { + if (!note || !note.server) return callback(null, null) + var body = note.server.document + var title = note.title = models.Note.parseNoteTitle(body) + var values = { + title: title, + content: body, + authorship: note.authorship, + lastchangeuserId: note.lastchangeuser, + lastchangeAt: Date.now() + } + _note.update(values).then(function (_note) { + saverSleep = false + return callback(null, _note) + }).catch(function (err) { + logger.error(err) + return callback(err, null) + }) } -//clean when user not in any rooms or user not in connected list -var cleaner = setInterval(function () { - async.each(Object.keys(users), function (key, callback) { - var socket = realtime.io.sockets.connected[key]; - if ((!socket && users[key]) || - (socket && (!socket.rooms || socket.rooms.length <= 0))) { - if (config.debug) - logger.info("cleaner found redundant user: " + key); - if (!socket) { - socket = { - id: key - }; - } - disconnectSocketQueue.push(socket); - disconnect(socket); + +// clean when user not in any rooms or user not in connected list +setInterval(function () { + async.each(Object.keys(users), function (key, callback) { + var socket = realtime.io.sockets.connected[key] + if ((!socket && users[key]) || + (socket && (!socket.rooms || socket.rooms.length <= 0))) { + if (config.debug) { logger.info('cleaner found redundant user: ' + key) } + if (!socket) { + socket = { + id: key } - return callback(null, null); - }, function (err) { - if (err) return logger.error('cleaner error', err); - }); -}, 60000); -var saverSleep = false; + } + disconnectSocketQueue.push(socket) + disconnect(socket) + } + return callback(null, null) + }, function (err) { + if (err) return logger.error('cleaner error', err) + }) +}, 60000) + +var saverSleep = false // save note revision in interval -var saver = setInterval(function () { - if (saverSleep) return; - models.Revision.saveAllNotesRevision(function (err, notes) { - if (err) return logger.error('revision saver failed: ' + err); - if (notes && notes.length <= 0) { - saverSleep = true; - return; +setInterval(function () { + if (saverSleep) return + models.Revision.saveAllNotesRevision(function (err, notes) { + if (err) return logger.error('revision saver failed: ' + err) + if (notes && notes.length <= 0) { + saverSleep = true + } + }) +}, 60000 * 5) + +function getStatus (callback) { + models.Note.count().then(function (notecount) { + var distinctaddresses = [] + var regaddresses = [] + var distinctregaddresses = [] + Object.keys(users).forEach(function (key) { + var user = users[key] + if (!user) return + let found = false + for (let i = 0; i < distinctaddresses.length; i++) { + if (user.address === distinctaddresses[i]) { + found = true + break } - }); -}, 60000 * 5); - -function getStatus(callback) { - models.Note.count().then(function (notecount) { - var distinctaddresses = []; - var regaddresses = []; - var distinctregaddresses = []; - Object.keys(users).forEach(function (key) { - var user = users[key]; - if (!user) return; - var found = false; - for (var i = 0; i < distinctaddresses.length; i++) { - if (user.address == distinctaddresses[i]) { - found = true; - break; - } - } - if (!found) { - distinctaddresses.push(user.address); - } - if (user.login) { - regaddresses.push(user.address); - var found = false; - for (var i = 0; i < distinctregaddresses.length; i++) { - if (user.address == distinctregaddresses[i]) { - found = true; - break; - } - } - if (!found) { - distinctregaddresses.push(user.address); - } - } - }); - models.User.count().then(function (regcount) { - return callback ? callback({ - onlineNotes: Object.keys(notes).length, - onlineUsers: Object.keys(users).length, - distinctOnlineUsers: distinctaddresses.length, - notesCount: notecount, - registeredUsers: regcount, - onlineRegisteredUsers: regaddresses.length, - distinctOnlineRegisteredUsers: distinctregaddresses.length, - isConnectionBusy: isConnectionBusy, - connectionSocketQueueLength: connectionSocketQueue.length, - isDisconnectBusy: isDisconnectBusy, - disconnectSocketQueueLength: disconnectSocketQueue.length - }) : null; - }).catch(function (err) { - return logger.error('count user failed: ' + err); - }); + } + if (!found) { + distinctaddresses.push(user.address) + } + if (user.login) { + regaddresses.push(user.address) + let found = false + for (let i = 0; i < distinctregaddresses.length; i++) { + if (user.address === distinctregaddresses[i]) { + found = true + break + } + } + if (!found) { + distinctregaddresses.push(user.address) + } + } + }) + models.User.count().then(function (regcount) { + return callback ? callback({ + onlineNotes: Object.keys(notes).length, + onlineUsers: Object.keys(users).length, + distinctOnlineUsers: distinctaddresses.length, + notesCount: notecount, + registeredUsers: regcount, + onlineRegisteredUsers: regaddresses.length, + distinctOnlineRegisteredUsers: distinctregaddresses.length, + isConnectionBusy: isConnectionBusy, + connectionSocketQueueLength: connectionSocketQueue.length, + isDisconnectBusy: isDisconnectBusy, + disconnectSocketQueueLength: disconnectSocketQueue.length + }) : null }).catch(function (err) { - return logger.error('count note failed: ' + err); - }); + return logger.error('count user failed: ' + err) + }) + }).catch(function (err) { + return logger.error('count note failed: ' + err) + }) } -function isReady() { - return realtime.io - && Object.keys(notes).length == 0 && Object.keys(users).length == 0 - && connectionSocketQueue.length == 0 && !isConnectionBusy - && disconnectSocketQueue.length == 0 && !isDisconnectBusy; +function isReady () { + return realtime.io && + Object.keys(notes).length === 0 && Object.keys(users).length === 0 && + connectionSocketQueue.length === 0 && !isConnectionBusy && + disconnectSocketQueue.length === 0 && !isDisconnectBusy } -function extractNoteIdFromSocket(socket) { - if (!socket || !socket.handshake || !socket.handshake.headers) { - return false; - } - var referer = socket.handshake.headers.referer; - if (!referer) { - return false; - } - var hostUrl = url.parse(referer); - var noteId = config.urlpath ? hostUrl.pathname.slice(config.urlpath.length + 1, hostUrl.pathname.length).split('/')[1] : hostUrl.pathname.split('/')[1]; - return noteId; +function extractNoteIdFromSocket (socket) { + if (!socket || !socket.handshake || !socket.handshake.headers) { + return false + } + var referer = socket.handshake.headers.referer + if (!referer) { + return false + } + var hostUrl = url.parse(referer) + var noteId = config.urlpath ? hostUrl.pathname.slice(config.urlpath.length + 1, hostUrl.pathname.length).split('/')[1] : hostUrl.pathname.split('/')[1] + return noteId } -function parseNoteIdFromSocket(socket, callback) { - var noteId = extractNoteIdFromSocket(socket); - if (!noteId) { - return callback(null, null); - } - models.Note.parseNoteId(noteId, function (err, id) { - if (err || !id) return callback(err, id); - return callback(null, id); - }); +function parseNoteIdFromSocket (socket, callback) { + var noteId = extractNoteIdFromSocket(socket) + if (!noteId) { + return callback(null, null) + } + models.Note.parseNoteId(noteId, function (err, id) { + if (err || !id) return callback(err, id) + return callback(null, id) + }) } -function emitOnlineUsers(socket) { - var noteId = socket.noteId; - if (!noteId || !notes[noteId]) return; - var users = []; - Object.keys(notes[noteId].users).forEach(function (key) { - var user = notes[noteId].users[key]; - if (user) - users.push(buildUserOutData(user)); - }); - var out = { - users: users - }; - realtime.io.to(noteId).emit('online users', out); +function emitOnlineUsers (socket) { + var noteId = socket.noteId + if (!noteId || !notes[noteId]) return + var users = [] + Object.keys(notes[noteId].users).forEach(function (key) { + var user = notes[noteId].users[key] + if (user) { users.push(buildUserOutData(user)) } + }) + var out = { + users: users + } + realtime.io.to(noteId).emit('online users', out) } -function emitUserStatus(socket) { - var noteId = socket.noteId; - var user = users[socket.id]; - if (!noteId || !notes[noteId] || !user) return; - var out = buildUserOutData(user); - socket.broadcast.to(noteId).emit('user status', out); +function emitUserStatus (socket) { + var noteId = socket.noteId + var user = users[socket.id] + if (!noteId || !notes[noteId] || !user) return + var out = buildUserOutData(user) + socket.broadcast.to(noteId).emit('user status', out) } -function emitRefresh(socket) { - var noteId = socket.noteId; - if (!noteId || !notes[noteId]) return; - var note = notes[noteId]; - var out = { - title: note.title, - docmaxlength: config.documentmaxlength, - owner: note.owner, - ownerprofile: note.ownerprofile, - lastchangeuser: note.lastchangeuser, - lastchangeuserprofile: note.lastchangeuserprofile, - authors: note.authors, - authorship: note.authorship, - permission: note.permission, - createtime: note.createtime, - updatetime: note.updatetime - }; - socket.emit('refresh', out); +function emitRefresh (socket) { + var noteId = socket.noteId + if (!noteId || !notes[noteId]) return + var note = notes[noteId] + var out = { + title: note.title, + docmaxlength: config.documentmaxlength, + owner: note.owner, + ownerprofile: note.ownerprofile, + lastchangeuser: note.lastchangeuser, + lastchangeuserprofile: note.lastchangeuserprofile, + authors: note.authors, + authorship: note.authorship, + permission: note.permission, + createtime: note.createtime, + updatetime: note.updatetime + } + socket.emit('refresh', out) } -function isDuplicatedInSocketQueue(queue, socket) { - for (var i = 0; i < queue.length; i++) { - if (queue[i] && queue[i].id == socket.id) { - return true; - } +function isDuplicatedInSocketQueue (queue, socket) { + for (var i = 0; i < queue.length; i++) { + if (queue[i] && queue[i].id === socket.id) { + return true } - return false; + } + return false } -function clearSocketQueue(queue, socket) { - for (var i = 0; i < queue.length; i++) { - if (!queue[i] || queue[i].id == socket.id) { - queue.splice(i, 1); - i--; - } +function clearSocketQueue (queue, socket) { + for (var i = 0; i < queue.length; i++) { + if (!queue[i] || queue[i].id === socket.id) { + queue.splice(i, 1) + i-- } + } } -function connectNextSocket() { - setTimeout(function () { - isConnectionBusy = false; - if (connectionSocketQueue.length > 0) { - startConnection(connectionSocketQueue[0]); - } - }, 1); +function connectNextSocket () { + setTimeout(function () { + isConnectionBusy = false + if (connectionSocketQueue.length > 0) { + startConnection(connectionSocketQueue[0]) + } + }, 1) } -function interruptConnection(socket, note, user) { - if (note) delete note; - if (user) delete user; - if (socket) - clearSocketQueue(connectionSocketQueue, socket); - else - connectionSocketQueue.shift(); - connectNextSocket(); +function interruptConnection (socket, noteId, socketId) { + if (notes[noteId]) delete notes[noteId] + if (users[socketId]) delete users[socketId] + if (socket) { clearSocketQueue(connectionSocketQueue, socket) } else { connectionSocketQueue.shift() } + connectNextSocket() } -function checkViewPermission(req, note) { - if (note.permission == 'private') { - if (req.user && req.user.logged_in && req.user.id == note.owner) - return true; - else - return false; - } else if (note.permission == 'limited' || note.permission == 'protected') { - if(req.user && req.user.logged_in) - return true; - else - return false; - } else { - return true; - } +function checkViewPermission (req, note) { + if (note.permission === 'private') { + if (req.user && req.user.logged_in && req.user.id === note.owner) { return true } else { return false } + } else if (note.permission === 'limited' || note.permission === 'protected') { + if (req.user && req.user.logged_in) { return true } else { return false } + } else { + return true + } } -var isConnectionBusy = false; -var connectionSocketQueue = []; -var isDisconnectBusy = false; -var disconnectSocketQueue = []; - -function finishConnection(socket, note, user) { - // if no valid info provided will drop the client - if (!socket || !note || !user) { - return interruptConnection(socket, note, user); - } - // check view permission - if (!checkViewPermission(socket.request, note)) { - interruptConnection(socket, note, user); - return failConnection(403, 'connection forbidden', socket); - } - // update user color to author color - if (note.authors[user.userid]) { - user.color = users[socket.id].color = note.authors[user.userid].color; - } - note.users[socket.id] = user; - note.socks.push(socket); - note.server.addClient(socket); - note.server.setName(socket, user.name); - note.server.setColor(socket, user.color); - - // update user note history - updateHistory(user.userid, note); - - emitOnlineUsers(socket); - emitRefresh(socket); - - //clear finished socket in queue - clearSocketQueue(connectionSocketQueue, socket); - //seek for next socket - connectNextSocket(); - - if (config.debug) { - var noteId = socket.noteId; - logger.info('SERVER connected a client to [' + noteId + ']:'); - logger.info(JSON.stringify(user)); - //logger.info(notes); - getStatus(function (data) { - logger.info(JSON.stringify(data)); - }); - } +var isConnectionBusy = false +var connectionSocketQueue = [] +var isDisconnectBusy = false +var disconnectSocketQueue = [] + +function finishConnection (socket, noteId, socketId) { + // if no valid info provided will drop the client + if (!socket || !notes[noteId] || !users[socketId]) { + return interruptConnection(socket, noteId, socketId) + } + // check view permission + if (!checkViewPermission(socket.request, notes[noteId])) { + interruptConnection(socket, noteId, socketId) + return failConnection(403, 'connection forbidden', socket) + } + let note = notes[noteId] + let user = users[socketId] + // update user color to author color + if (note.authors[user.userid]) { + user.color = users[socket.id].color = note.authors[user.userid].color + } + note.users[socket.id] = user + note.socks.push(socket) + note.server.addClient(socket) + note.server.setName(socket, user.name) + note.server.setColor(socket, user.color) + + // update user note history + updateHistory(user.userid, note) + + emitOnlineUsers(socket) + emitRefresh(socket) + + // clear finished socket in queue + clearSocketQueue(connectionSocketQueue, socket) + // seek for next socket + connectNextSocket() + + if (config.debug) { + let noteId = socket.noteId + logger.info('SERVER connected a client to [' + noteId + ']:') + logger.info(JSON.stringify(user)) + // logger.info(notes); + getStatus(function (data) { + logger.info(JSON.stringify(data)) + }) + } } -function startConnection(socket) { - if (isConnectionBusy) return; - isConnectionBusy = true; +function startConnection (socket) { + if (isConnectionBusy) return + isConnectionBusy = true + + var noteId = socket.noteId + if (!noteId) { + return failConnection(404, 'note id not found', socket) + } + + if (!notes[noteId]) { + var include = [{ + model: models.User, + as: 'owner' + }, { + model: models.User, + as: 'lastchangeuser' + }, { + model: models.Author, + as: 'authors', + include: [{ + model: models.User, + as: 'user' + }] + }] - var noteId = socket.noteId; - if (!noteId) { - return failConnection(404, 'note id not found', socket); - } + models.Note.findOne({ + where: { + id: noteId + }, + include: include + }).then(function (note) { + if (!note) { + return failConnection(404, 'note not found', socket) + } + var owner = note.ownerId + var ownerprofile = note.owner ? models.User.getProfile(note.owner) : null + + var lastchangeuser = note.lastchangeuserId + var lastchangeuserprofile = note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null + + var body = note.content + var createtime = note.createdAt + var updatetime = note.lastchangeAt + var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback) + + var authors = {} + for (var i = 0; i < note.authors.length; i++) { + var author = note.authors[i] + var profile = models.User.getProfile(author.user) + authors[author.userId] = { + userid: author.userId, + color: author.color, + photo: profile.photo, + name: profile.name + } + } - if (!notes[noteId]) { - var include = [{ - model: models.User, - as: "owner" - }, { - model: models.User, - as: "lastchangeuser" - }, { - model: models.Author, - as: "authors", - include: [{ - model: models.User, - as: "user" - }] - }]; - - models.Note.findOne({ - where: { - id: noteId - }, - include: include - }).then(function (note) { - if (!note) { - return failConnection(404, 'note not found', socket); - } - var owner = note.ownerId; - var ownerprofile = note.owner ? models.User.getProfile(note.owner) : null; - - var lastchangeuser = note.lastchangeuserId; - var lastchangeuserprofile = note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null; - - var body = note.content; - var createtime = note.createdAt; - var updatetime = note.lastchangeAt; - var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback); - - var authors = {}; - for (var i = 0; i < note.authors.length; i++) { - var author = note.authors[i]; - var profile = models.User.getProfile(author.user); - authors[author.userId] = { - userid: author.userId, - color: author.color, - photo: profile.photo, - name: profile.name - }; - } + notes[noteId] = { + id: noteId, + alias: note.alias, + title: note.title, + owner: owner, + ownerprofile: ownerprofile, + permission: note.permission, + lastchangeuser: lastchangeuser, + lastchangeuserprofile: lastchangeuserprofile, + socks: [], + users: {}, + tempUsers: {}, + createtime: moment(createtime).valueOf(), + updatetime: moment(updatetime).valueOf(), + server: server, + authors: authors, + authorship: note.authorship + } - notes[noteId] = { - id: noteId, - alias: note.alias, - title: note.title, - owner: owner, - ownerprofile: ownerprofile, - permission: note.permission, - lastchangeuser: lastchangeuser, - lastchangeuserprofile: lastchangeuserprofile, - socks: [], - users: {}, - tempUsers: {}, - createtime: moment(createtime).valueOf(), - updatetime: moment(updatetime).valueOf(), - server: server, - authors: authors, - authorship: note.authorship - }; - - return finishConnection(socket, notes[noteId], users[socket.id]); - }).catch(function (err) { - return failConnection(500, err, socket); - }); - } else { - return finishConnection(socket, notes[noteId], users[socket.id]); - } + return finishConnection(socket, noteId, socket.id) + }).catch(function (err) { + return failConnection(500, err, socket) + }) + } else { + return finishConnection(socket, noteId, socket.id) + } } -function failConnection(code, err, socket) { - logger.error(err); - // clear error socket in queue - clearSocketQueue(connectionSocketQueue, socket); - connectNextSocket(); - // emit error info - socket.emit('info', { - code: code - }); - return socket.disconnect(true); +function failConnection (code, err, socket) { + logger.error(err) + // clear error socket in queue + clearSocketQueue(connectionSocketQueue, socket) + connectNextSocket() + // emit error info + socket.emit('info', { + code: code + }) + return socket.disconnect(true) } -function disconnect(socket) { - if (isDisconnectBusy) return; - isDisconnectBusy = true; - - if (config.debug) { - logger.info("SERVER disconnected a client"); - logger.info(JSON.stringify(users[socket.id])); +function disconnect (socket) { + if (isDisconnectBusy) return + isDisconnectBusy = true + + if (config.debug) { + logger.info('SERVER disconnected a client') + logger.info(JSON.stringify(users[socket.id])) + } + + if (users[socket.id]) { + delete users[socket.id] + } + var noteId = socket.noteId + var note = notes[noteId] + if (note) { + // delete user in users + if (note.users[socket.id]) { + delete note.users[socket.id] } - - if (users[socket.id]) { - delete users[socket.id]; - } - var noteId = socket.noteId; - var note = notes[noteId]; - if (note) { - // delete user in users - if (note.users[socket.id]) { - delete note.users[socket.id]; - } - // remove sockets in the note socks - do { - var index = note.socks.indexOf(socket); - if (index != -1) { - note.socks.splice(index, 1); - } - } while (index != -1); - // remove note in notes if no user inside - if (Object.keys(note.users).length <= 0) { - if (note.server.isDirty) { - updateNote(note, function (err, _note) { - if (err) return logger.error('disconnect note failed: ' + err); - // clear server before delete to avoid memory leaks - note.server.document = ""; - note.server.operations = []; - delete note.server; - delete notes[noteId]; - if (config.debug) { - //logger.info(notes); - getStatus(function (data) { - logger.info(JSON.stringify(data)); - }); - } - }); - } else { - delete note.server; - delete notes[noteId]; - } - } - } - emitOnlineUsers(socket); - - //clear finished socket in queue - clearSocketQueue(disconnectSocketQueue, socket); - //seek for next socket - isDisconnectBusy = false; - if (disconnectSocketQueue.length > 0) - disconnect(disconnectSocketQueue[0]); - - if (config.debug) { - //logger.info(notes); - getStatus(function (data) { - logger.info(JSON.stringify(data)); - }); + // remove sockets in the note socks + do { + var index = note.socks.indexOf(socket) + if (index !== -1) { + note.socks.splice(index, 1) + } + } while (index !== -1) + // remove note in notes if no user inside + if (Object.keys(note.users).length <= 0) { + if (note.server.isDirty) { + updateNote(note, function (err, _note) { + if (err) return logger.error('disconnect note failed: ' + err) + // clear server before delete to avoid memory leaks + note.server.document = '' + note.server.operations = [] + delete note.server + delete notes[noteId] + if (config.debug) { + // logger.info(notes); + getStatus(function (data) { + logger.info(JSON.stringify(data)) + }) + } + }) + } else { + delete note.server + delete notes[noteId] + } } + } + emitOnlineUsers(socket) + + // clear finished socket in queue + clearSocketQueue(disconnectSocketQueue, socket) + // seek for next socket + isDisconnectBusy = false + if (disconnectSocketQueue.length > 0) { disconnect(disconnectSocketQueue[0]) } + + if (config.debug) { + // logger.info(notes); + getStatus(function (data) { + logger.info(JSON.stringify(data)) + }) + } } -function buildUserOutData(user) { - var out = { - id: user.id, - login: user.login, - userid: user.userid, - photo: user.photo, - color: user.color, - cursor: user.cursor, - name: user.name, - idle: user.idle, - type: user.type - }; - return out; +function buildUserOutData (user) { + var out = { + id: user.id, + login: user.login, + userid: user.userid, + photo: user.photo, + color: user.color, + cursor: user.cursor, + name: user.name, + idle: user.idle, + type: user.type + } + return out } -function updateUserData(socket, user) { - //retrieve user data from passport - if (socket.request.user && socket.request.user.logged_in) { - var profile = models.User.getProfile(socket.request.user); - user.photo = profile.photo; - user.name = profile.name; - user.userid = socket.request.user.id; - user.login = true; - } else { - user.userid = null; - user.name = 'Guest ' + chance.last(); - user.login = false; - } +function updateUserData (socket, user) { + // retrieve user data from passport + if (socket.request.user && socket.request.user.logged_in) { + var profile = models.User.getProfile(socket.request.user) + user.photo = profile.photo + user.name = profile.name + user.userid = socket.request.user.id + user.login = true + } else { + user.userid = null + user.name = 'Guest ' + chance.last() + user.login = false + } } -function ifMayEdit(socket, callback) { - var noteId = socket.noteId; - if (!noteId || !notes[noteId]) return; - var note = notes[noteId]; - var mayEdit = true; - switch (note.permission) { - case "freely": - //not blocking anyone - break; - case "editable": case "limited": - //only login user can change - if (!socket.request.user || !socket.request.user.logged_in) - mayEdit = false; - break; - case "locked": case "private": case "protected": - //only owner can change - if (!note.owner || note.owner != socket.request.user.id) - mayEdit = false; - break; - } - //if user may edit and this is a text operation - if (socket.origin == 'operation' && mayEdit) { - //save for the last change user id - if (socket.request.user && socket.request.user.logged_in) { - note.lastchangeuser = socket.request.user.id; - } else { - note.lastchangeuser = null; - } +function ifMayEdit (socket, callback) { + var noteId = socket.noteId + if (!noteId || !notes[noteId]) return + var note = notes[noteId] + var mayEdit = true + switch (note.permission) { + case 'freely': + // not blocking anyone + break + case 'editable': case 'limited': + // only login user can change + if (!socket.request.user || !socket.request.user.logged_in) { mayEdit = false } + break + case 'locked': case 'private': case 'protected': + // only owner can change + if (!note.owner || note.owner !== socket.request.user.id) { mayEdit = false } + break + } + // if user may edit and this is a text operation + if (socket.origin === 'operation' && mayEdit) { + // save for the last change user id + if (socket.request.user && socket.request.user.logged_in) { + note.lastchangeuser = socket.request.user.id + } else { + note.lastchangeuser = null } - return callback(mayEdit); + } + return callback(mayEdit) } -function operationCallback(socket, operation) { - var noteId = socket.noteId; - if (!noteId || !notes[noteId]) return; - var note = notes[noteId]; - var userId = null; - // save authors - if (socket.request.user && socket.request.user.logged_in) { - var user = users[socket.id]; - if (!user) return; - userId = socket.request.user.id; - if (!note.authors[userId]) { - models.Author.findOrCreate({ - where: { - noteId: noteId, - userId: userId - }, - defaults: { - noteId: noteId, - userId: userId, - color: user.color - } - }).spread(function (author, created) { - if (author) { - note.authors[author.userId] = { - userid: author.userId, - color: author.color, - photo: user.photo, - name: user.name - }; - } - }).catch(function (err) { - return logger.error('operation callback failed: ' + err); - }); +function operationCallback (socket, operation) { + var noteId = socket.noteId + if (!noteId || !notes[noteId]) return + var note = notes[noteId] + var userId = null + // save authors + if (socket.request.user && socket.request.user.logged_in) { + var user = users[socket.id] + if (!user) return + userId = socket.request.user.id + if (!note.authors[userId]) { + models.Author.findOrCreate({ + where: { + noteId: noteId, + userId: userId + }, + defaults: { + noteId: noteId, + userId: userId, + color: user.color + } + }).spread(function (author, created) { + if (author) { + note.authors[author.userId] = { + userid: author.userId, + color: author.color, + photo: user.photo, + name: user.name + } } - note.tempUsers[userId] = Date.now(); + }).catch(function (err) { + return logger.error('operation callback failed: ' + err) + }) } - // save authorship - use timer here because it's an O(n) complexity algorithm - setImmediate(function () { - note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship); - }); + note.tempUsers[userId] = Date.now() + } + // save authorship - use timer here because it's an O(n) complexity algorithm + setImmediate(function () { + note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship) + }) } -function updateHistory(userId, note, time) { - var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id); - if (note.server) history.updateHistory(userId, noteId, note.server.document, time); +function updateHistory (userId, note, time) { + var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id) + if (note.server) history.updateHistory(userId, noteId, note.server.document, time) } -function connection(socket) { - if (config.maintenance) return; - parseNoteIdFromSocket(socket, function (err, noteId) { - if (err) { - return failConnection(500, err, socket); - } - if (!noteId) { - return failConnection(404, 'note id not found', socket); - } +function connection (socket) { + if (config.maintenance) return + parseNoteIdFromSocket(socket, function (err, noteId) { + if (err) { + return failConnection(500, err, socket) + } + if (!noteId) { + return failConnection(404, 'note id not found', socket) + } - if (isDuplicatedInSocketQueue(socket, connectionSocketQueue)) return; - - // store noteId in this socket session - socket.noteId = noteId; - - //initialize user data - //random color - var color = randomcolor(); - //make sure color not duplicated or reach max random count - if (notes[noteId]) { - var randomcount = 0; - var maxrandomcount = 10; - var found = false; - do { - Object.keys(notes[noteId].users).forEach(function (user) { - if (user.color == color) { - found = true; - return; - } - }); - if (found) { - color = randomcolor(); - randomcount++; - } - } while (found && randomcount < maxrandomcount); + if (isDuplicatedInSocketQueue(socket, connectionSocketQueue)) return + + // store noteId in this socket session + socket.noteId = noteId + + // initialize user data + // random color + var color = randomcolor() + // make sure color not duplicated or reach max random count + if (notes[noteId]) { + var randomcount = 0 + var maxrandomcount = 10 + var found = false + do { + Object.keys(notes[noteId].users).forEach(function (user) { + if (user.color === color) { + found = true + } + }) + if (found) { + color = randomcolor() + randomcount++ } - //create user data - users[socket.id] = { - id: socket.id, - address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address, - 'user-agent': socket.handshake.headers['user-agent'], - color: color, - cursor: null, - login: false, - userid: null, - name: null, - idle: false, - type: null - }; - updateUserData(socket, users[socket.id]); - - //start connection - connectionSocketQueue.push(socket); - startConnection(socket); - }); - - //received client refresh request - socket.on('refresh', function () { - emitRefresh(socket); - }); - - //received user status - socket.on('user status', function (data) { - var noteId = socket.noteId; - var user = users[socket.id]; - if (!noteId || !notes[noteId] || !user) return; - if (config.debug) - logger.info('SERVER received [' + noteId + '] user status from [' + socket.id + ']: ' + JSON.stringify(data)); - if (data) { - user.idle = data.idle; - user.type = data.type; - } - emitUserStatus(socket); - }); - - //received note permission change request - socket.on('permission', function (permission) { - //need login to do more actions - if (socket.request.user && socket.request.user.logged_in) { - var noteId = socket.noteId; - if (!noteId || !notes[noteId]) return; - var note = notes[noteId]; - //Only owner can change permission - if (note.owner && note.owner == socket.request.user.id) { - if (permission == 'freely' && !config.allowanonymous) return; - note.permission = permission; - models.Note.update({ - permission: permission - }, { - where: { - id: noteId - } - }).then(function (count) { - if (!count) { - return; - } - var out = { - permission: permission - }; - realtime.io.to(note.id).emit('permission', out); - for (var i = 0, l = note.socks.length; i < l; i++) { - var sock = note.socks[i]; - if (typeof sock !== 'undefined' && sock) { - // check view permission - if (!checkViewPermission(sock.request, note)) { - sock.emit('info', { - code: 403 - }); - setTimeout(function () { - sock.disconnect(true); - }, 0); - } - } - } - }).catch(function (err) { - return logger.error('update note permission failed: ' + err); - }); + } while (found && randomcount < maxrandomcount) + } + // create user data + users[socket.id] = { + id: socket.id, + address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address, + 'user-agent': socket.handshake.headers['user-agent'], + color: color, + cursor: null, + login: false, + userid: null, + name: null, + idle: false, + type: null + } + updateUserData(socket, users[socket.id]) + + // start connection + connectionSocketQueue.push(socket) + startConnection(socket) + }) + + // received client refresh request + socket.on('refresh', function () { + emitRefresh(socket) + }) + + // received user status + socket.on('user status', function (data) { + var noteId = socket.noteId + var user = users[socket.id] + if (!noteId || !notes[noteId] || !user) return + if (config.debug) { logger.info('SERVER received [' + noteId + '] user status from [' + socket.id + ']: ' + JSON.stringify(data)) } + if (data) { + user.idle = data.idle + user.type = data.type + } + emitUserStatus(socket) + }) + + // received note permission change request + socket.on('permission', function (permission) { + // need login to do more actions + if (socket.request.user && socket.request.user.logged_in) { + var noteId = socket.noteId + if (!noteId || !notes[noteId]) return + var note = notes[noteId] + // Only owner can change permission + if (note.owner && note.owner === socket.request.user.id) { + if (permission === 'freely' && !config.allowanonymous) return + note.permission = permission + models.Note.update({ + permission: permission + }, { + where: { + id: noteId + } + }).then(function (count) { + if (!count) { + return + } + var out = { + permission: permission + } + realtime.io.to(note.id).emit('permission', out) + for (var i = 0, l = note.socks.length; i < l; i++) { + var sock = note.socks[i] + if (typeof sock !== 'undefined' && sock) { + // check view permission + if (!checkViewPermission(sock.request, note)) { + sock.emit('info', { + code: 403 + }) + setTimeout(function () { + sock.disconnect(true) + }, 0) + } } - } - }); - - // delete a note - socket.on('delete', function () { - //need login to do more actions - if (socket.request.user && socket.request.user.logged_in) { - var noteId = socket.noteId; - if (!noteId || !notes[noteId]) return; - var note = notes[noteId]; - //Only owner can delete note - if (note.owner && note.owner == socket.request.user.id) { - models.Note.destroy({ - where: { - id: noteId - } - }).then(function (count) { - if (!count) return; - for (var i = 0, l = note.socks.length; i < l; i++) { - var sock = note.socks[i]; - if (typeof sock !== 'undefined' && sock) { - sock.emit('delete'); - setTimeout(function () { - sock.disconnect(true); - }, 0); - } - } - }).catch(function (err) { - return logger.error('delete note failed: ' + err); - }); + } + }).catch(function (err) { + return logger.error('update note permission failed: ' + err) + }) + } + } + }) + + // delete a note + socket.on('delete', function () { + // need login to do more actions + if (socket.request.user && socket.request.user.logged_in) { + var noteId = socket.noteId + if (!noteId || !notes[noteId]) return + var note = notes[noteId] + // Only owner can delete note + if (note.owner && note.owner === socket.request.user.id) { + models.Note.destroy({ + where: { + id: noteId + } + }).then(function (count) { + if (!count) return + for (var i = 0, l = note.socks.length; i < l; i++) { + var sock = note.socks[i] + if (typeof sock !== 'undefined' && sock) { + sock.emit('delete') + setTimeout(function () { + sock.disconnect(true) + }, 0) } - } - }); - - //reveiced when user logout or changed - socket.on('user changed', function () { - logger.info('user changed'); - var noteId = socket.noteId; - if (!noteId || !notes[noteId]) return; - var user = notes[noteId].users[socket.id]; - if (!user) return; - updateUserData(socket, user); - emitOnlineUsers(socket); - }); - - //received sync of online users request - socket.on('online users', function () { - var noteId = socket.noteId; - if (!noteId || !notes[noteId]) return; - var users = []; - Object.keys(notes[noteId].users).forEach(function (key) { - var user = notes[noteId].users[key]; - if (user) - users.push(buildUserOutData(user)); - }); - var out = { - users: users - }; - socket.emit('online users', out); - }); - - //check version - socket.on('version', function () { - socket.emit('version', { - version: config.version, - minimumCompatibleVersion: config.minimumCompatibleVersion - }); - }); - - //received cursor focus - socket.on('cursor focus', function (data) { - var noteId = socket.noteId; - var user = users[socket.id]; - if (!noteId || !notes[noteId] || !user) return; - user.cursor = data; - var out = buildUserOutData(user); - socket.broadcast.to(noteId).emit('cursor focus', out); - }); - - //received cursor activity - socket.on('cursor activity', function (data) { - var noteId = socket.noteId; - var user = users[socket.id]; - if (!noteId || !notes[noteId] || !user) return; - user.cursor = data; - var out = buildUserOutData(user); - socket.broadcast.to(noteId).emit('cursor activity', out); - }); - - //received cursor blur - socket.on('cursor blur', function () { - var noteId = socket.noteId; - var user = users[socket.id]; - if (!noteId || !notes[noteId] || !user) return; - user.cursor = null; - var out = { - id: socket.id - }; - socket.broadcast.to(noteId).emit('cursor blur', out); - }); - - //when a new client disconnect - socket.on('disconnect', function () { - if (isDuplicatedInSocketQueue(socket, disconnectSocketQueue)) return; - disconnectSocketQueue.push(socket); - disconnect(socket); - }); + } + }).catch(function (err) { + return logger.error('delete note failed: ' + err) + }) + } + } + }) + + // reveiced when user logout or changed + socket.on('user changed', function () { + logger.info('user changed') + var noteId = socket.noteId + if (!noteId || !notes[noteId]) return + var user = notes[noteId].users[socket.id] + if (!user) return + updateUserData(socket, user) + emitOnlineUsers(socket) + }) + + // received sync of online users request + socket.on('online users', function () { + var noteId = socket.noteId + if (!noteId || !notes[noteId]) return + var users = [] + Object.keys(notes[noteId].users).forEach(function (key) { + var user = notes[noteId].users[key] + if (user) { users.push(buildUserOutData(user)) } + }) + var out = { + users: users + } + socket.emit('online users', out) + }) + + // check version + socket.on('version', function () { + socket.emit('version', { + version: config.version, + minimumCompatibleVersion: config.minimumCompatibleVersion + }) + }) + + // received cursor focus + socket.on('cursor focus', function (data) { + var noteId = socket.noteId + var user = users[socket.id] + if (!noteId || !notes[noteId] || !user) return + user.cursor = data + var out = buildUserOutData(user) + socket.broadcast.to(noteId).emit('cursor focus', out) + }) + + // received cursor activity + socket.on('cursor activity', function (data) { + var noteId = socket.noteId + var user = users[socket.id] + if (!noteId || !notes[noteId] || !user) return + user.cursor = data + var out = buildUserOutData(user) + socket.broadcast.to(noteId).emit('cursor activity', out) + }) + + // received cursor blur + socket.on('cursor blur', function () { + var noteId = socket.noteId + var user = users[socket.id] + if (!noteId || !notes[noteId] || !user) return + user.cursor = null + var out = { + id: socket.id + } + socket.broadcast.to(noteId).emit('cursor blur', out) + }) + + // when a new client disconnect + socket.on('disconnect', function () { + if (isDuplicatedInSocketQueue(socket, disconnectSocketQueue)) return + disconnectSocketQueue.push(socket) + disconnect(socket) + }) } -module.exports = realtime; \ No newline at end of file +module.exports = realtime -- cgit v1.2.3