From e8a34e7cedc7a1e5cc9f3ecbbf2cc868f864bfc7 Mon Sep 17 00:00:00 2001 From: Yannick Bungers Date: Sun, 24 May 2020 22:18:44 +0200 Subject: [PATCH] More Types for realtime.ts Signed-off-by: Yannick Bungers --- src/lib/realtime.ts | 265 ++++++++++++++++++++++++-------------------- 1 file changed, 146 insertions(+), 119 deletions(-) diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts index c99435278..1890a46c3 100644 --- a/src/lib/realtime.ts +++ b/src/lib/realtime.ts @@ -11,6 +11,7 @@ import { History } from './history' import { logger } from './logger' import { Author, Note, Revision, User } from './models' import { EditorSocketIOServer } from './ot/editor-socketio-server' +import { NoteAuthorship } from './models/note' export type SocketWithNoteId = Socket & { noteId: string } @@ -62,7 +63,7 @@ function secure (socket: Socket, next: (err?: Error) => void): void { } } -function emitCheck (note): void { +function emitCheck (note: NoteSession): void { const out = { title: note.title, updatetime: note.updatetime, @@ -74,14 +75,18 @@ function emitCheck (note): void { realtime.io.to(note.id).emit('check', out) } + + class UserSession { - id: string; + id?: string + address: string login: any - userid: string + userid: string | null + 'user-agent': any photo: any - color: any + color: string cursor: any - name: string + name: string | null idle: any type: any } @@ -90,26 +95,24 @@ class NoteSession { id: string alias: string title: string - owner: UserSession + owner: string ownerprofile: any permission: any - lastchangeuser: string + lastchangeuser: string | null lastchangeuserprofile: any socks: SocketWithNoteId[] - users: UserSession[] - tempUsers: UserSession[] + users: Map + tempUsers: Map // time value createtime: number updatetime: number server: any - authors: UserSession[] - authorship: UserSession + authors: Map + authorship: NoteAuthorship[] } - - // actions -const users: UserSession[] = [] -const notes: NoteSession[] = [] +const users: Map = new Map() +const notes: Map = new Map() let saverSleep = false @@ -147,10 +150,10 @@ function updateNote (note: NoteSession, callback: (err, note) => void): void { if (!_note) return callback(null, null) // update user note history const tempUsers = Object.assign({}, note.tempUsers) - note.tempUsers = [] - Object.keys(tempUsers).forEach(function (key) { - updateHistory(key, note, tempUsers[key]) - }) + note.tempUsers = new Map() + for (const [key, time] of tempUsers) { + updateHistory(key, note, time) + } if (note.lastchangeuser) { if (_note.lastchangeuserId !== note.lastchangeuser) { User.findOne({ @@ -180,14 +183,13 @@ function updateNote (note: NoteSession, callback: (err, note) => void): void { // update when the note is dirty setInterval(function () { - async.each(Object.keys(notes), function (key, callback) { - const note = notes[key] + for (const [key, note] of notes) { if (note.server.isDirty) { logger.debug(`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 || !note.server) return if (!_note) { realtime.io.to(note.id).emit('info', { code: 404 @@ -195,26 +197,22 @@ setInterval(function () { logger.error('note not found: ', note.id) } if (err || !_note) { - for (let i = 0, l = note.socks.length; i < l; i++) { - const sock = note.socks[i] - if (typeof sock !== 'undefined' && sock) { + for (const sock of note.socks) { + if (sock) { setTimeout(function () { sock.disconnect() }, 0) } } - return callback(err, null) + return logger.error('updater error', err) } note.updatetime = moment(_note.lastchangeAt).valueOf() emitCheck(note) - return callback(null, null) }) } else { - return callback(null, null) + return } - }, function (err) { - if (err) return logger.error('updater error', err) - }) + } }, 1000) // save note revision in interval @@ -238,12 +236,12 @@ function getStatus (callback): void { const distinctaddresses: string[] = [] const regaddresses: string[] = [] const distinctregaddresses: string[] = [] - Object.keys(users).forEach(function (key) { - const user = users[key] + // Object.keys(users).forEach(function (key) { + for (const user of users.values()) { if (!user) return let found = false - for (let i = 0; i < distinctaddresses.length; i++) { - if (user.address === distinctaddresses[i]) { + for (const distinctaddress of distinctaddresses) { + if (user.address === distinctaddress) { found = true break } @@ -264,7 +262,7 @@ function getStatus (callback): void { distinctregaddresses.push(user.address) } } - }) + } User.count().then(function (regcount) { // eslint-disable-next-line standard/no-callback-literal return callback ? callback({ @@ -321,6 +319,8 @@ function buildUserOutData (user): UserSession { return { id: user.id, login: user.login, + address: '', + 'user-agent': '', userid: user.userid, photo: user.photo, color: user.color, @@ -333,14 +333,16 @@ function buildUserOutData (user): UserSession { function emitOnlineUsers (socket: SocketWithNoteId): void { const noteId = socket.noteId - if (!noteId || !notes[noteId]) return + if (!noteId) return + const note = notes.get(noteId) + if (!note) return const users: UserSession[] = [] - Object.keys(notes[noteId].users).forEach(function (key) { - const user = notes[noteId].users[key] + // Object.keys(note.users).forEach(function (key) { + for (const user of note.users.keys()) { if (user) { users.push(buildUserOutData(user)) } - }) + } const out = { users: users } @@ -349,16 +351,20 @@ function emitOnlineUsers (socket: SocketWithNoteId): void { function emitUserStatus (socket: SocketWithNoteId): void { const noteId = socket.noteId - const user = users[socket.id] - if (!noteId || !notes[noteId] || !user) return + if (!noteId) return + const note = notes.get(noteId) + if (!note) return + const user = users.get(socket.id) + if (!user) return const out = buildUserOutData(user) socket.broadcast.to(noteId).emit('user status', out) } function emitRefresh (socket: SocketWithNoteId): void { const noteId = socket.noteId - if (!noteId || !notes[noteId]) return - const note = notes[noteId] + if (!noteId) return + const note = notes.get(noteId) + if (!note) return const out = { title: note.title, docmaxlength: config.documentMaxLength, @@ -376,8 +382,8 @@ function emitRefresh (socket: SocketWithNoteId): void { } function isDuplicatedInSocketQueue (queue: Socket[], socket: Socket): boolean { - for (let i = 0; i < queue.length; i++) { - if (queue[i] && queue[i].id === socket.id) { + for (const sock of queue) { + if (sock && sock.id === socket.id) { return true } } @@ -417,8 +423,8 @@ function failConnection (errorCode: number, errorMessage: string, socket: Socket } function interruptConnection (socket: Socket, noteId: string, socketId): void { - if (notes[noteId]) delete notes[noteId] - if (users[socketId]) delete users[socketId] + notes.delete(noteId) + users.delete(socketId) if (socket) { clearSocketQueue(connectionSocketQueue, socket) } else { @@ -427,7 +433,7 @@ function interruptConnection (socket: Socket, noteId: string, socketId): void { connectNextSocket() } -function checkViewPermission (req, note): boolean { +function checkViewPermission (req, note: NoteSession): boolean { if (note.permission === 'private') { return !!(req.user?.logged_in && req.user.id === note.owner) } else if (note.permission === 'limited' || note.permission === 'protected') { @@ -439,21 +445,28 @@ function checkViewPermission (req, note): boolean { function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: string): void { // if no valid info provided will drop the client - if (!socket || !notes[noteId] || !users[socketId]) { + if (!socket || !notes.get(noteId) || !users.get(socketId)) { return interruptConnection(socket, noteId, socketId) } // check view permission - if (!checkViewPermission(socket.request, notes[noteId])) { + const note = notes.get(noteId) + if (!note) return + if (!checkViewPermission(socket.request, note)) { interruptConnection(socket, noteId, socketId) return failConnection(403, 'connection forbidden', socket) } - const note = notes[noteId] - const user = users[socketId] + const user = users.get(socketId) + if (!user || !user.userid) return // update user color to author color - if (note.authors[user.userid]) { - user.color = users[socket.id].color = note.authors[user.userid].color + const author = note.authors.get(user.userid) + if (author) { + const socketIdUser = users.get(socket.id) + if (!socketIdUser) return + user.color = socketIdUser.color + users.set(socket.id, user) } - note.users[socket.id] = user + + note.users.set(socket.id, user) note.socks.push(socket) note.server.addClient(socket) note.server.setName(socket, user.name) @@ -483,8 +496,9 @@ function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: s function ifMayEdit (socket: SocketWithNoteId, originIsOperation: boolean, callback: (mayEdit: boolean) => void): void { const noteId = socket.noteId - if (!noteId || !notes[noteId]) return - const note = notes[noteId] + if (!noteId) return + const note = notes.get(noteId) + if (!note) return let mayEdit = true switch (note.permission) { case 'freely': @@ -520,15 +534,18 @@ function ifMayEdit (socket: SocketWithNoteId, originIsOperation: boolean, callba function operationCallback (socket: SocketWithNoteId, operation): void { const noteId = socket.noteId - if (!noteId || !notes[noteId]) return - const note = notes[noteId] - let userId = null + if (!noteId) return + const note = notes.get(noteId) + if (!note) return + let userId: string | null = null // save authors if (socket.request.user && socket.request.user.logged_in) { - const user = users[socket.id] + const user = users.get(socket.id) if (!user) return userId = socket.request.user.id - if (!note.authors[userId]) { + if (!userId) return + const author = note.authors.get(userId) + if (!author) { Author.findOrCreate({ where: { noteId: noteId, @@ -541,18 +558,18 @@ function operationCallback (socket: SocketWithNoteId, operation): void { } }).then(function ([author, _created]) { if (author) { - note.authors[author.userId] = { + note.authors.set(author.userId, { userid: author.userId, color: author.color, photo: user.photo, name: user.name - } + }) } }).catch(function (err) { logger.error('operation callback failed: ' + err) }) } - note.tempUsers[userId] = Date.now() + if (userId) note.tempUsers.set(userId, Date.now()) } // save authorship - use timer here because it's an O(n) complexity algorithm setImmediate(function () { @@ -569,7 +586,7 @@ function startConnection (socket: SocketWithNoteId): void { return failConnection(404, 'note id not found', socket) } - if (!notes[noteId]) { + if (!notes.get(noteId)) { const include = [{ model: User, as: 'owner' @@ -605,21 +622,27 @@ function startConnection (socket: SocketWithNoteId): void { const updatetime = note.lastchangeAt const server = new EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback) - const authors = {} - for (let i = 0; i < note.authors.length; i++) { - const author = note.authors[i] + const authors = new Map() + for (const author of note.authors) { const profile = User.getProfile(author.user) if (profile) { - authors[author.userId] = { + authors.set(author.userId, { + id: '', + address: '', + 'user-agent': '', + login: {}, + cursor: {}, + idle: {}, userid: author.userId, color: author.color, photo: profile.photo, - name: profile.name - } + name: profile.name, + type: {} + }) } } - notes[noteId] = { + notes.set(noteId, { id: noteId, alias: note.alias, title: note.title, @@ -629,14 +652,14 @@ function startConnection (socket: SocketWithNoteId): void { lastchangeuser: lastchangeuser, lastchangeuserprofile: lastchangeuserprofile, socks: [], - users: {}, - tempUsers: {}, + users: new Map(), + tempUsers: new Map(), createtime: moment(createtime).valueOf(), updatetime: moment(updatetime).valueOf(), server: server, authors: authors, authorship: note.authorship - } + }) return finishConnection(socket, noteId, socket.id) }).catch(function (err) { @@ -655,17 +678,17 @@ function disconnect (socket: SocketWithNoteId): void { isDisconnectBusy = true logger.debug('SERVER disconnected a client') - logger.debug(JSON.stringify(users[socket.id])) + logger.debug(JSON.stringify(users.get(socket.id))) - if (users[socket.id]) { - delete users[socket.id] + if (users.get(socket.id)) { + users.delete(socket.id) } const noteId = socket.noteId - const note = notes[noteId] + const note = notes.get(noteId) if (note) { // delete user in users - if (note.users[socket.id]) { - delete note.users[socket.id] + if (note.users.get(socket.id)) { + note.users.delete(socket.id) } // remove sockets in the note socks let index @@ -676,15 +699,15 @@ function disconnect (socket: SocketWithNoteId): void { } } while (index !== -1) // remove note in notes if no user inside - if (Object.keys(note.users).length <= 0) { + if (note.users.size <= 0) { if (note.server.isDirty) { - updateNote(note, function (err, _note: Note) { + updateNote(note, function (err, _) { 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] + notes.delete(noteId) if (config.debug) { logger.debug(notes) getStatus(function (data) { @@ -694,7 +717,7 @@ function disconnect (socket: SocketWithNoteId): void { }) } else { delete note.server - delete notes[noteId] + notes.delete(noteId) } } } @@ -718,9 +741,9 @@ function disconnect (socket: SocketWithNoteId): void { // clean when user not in any rooms or user not in connected list setInterval(function () { - async.each(Object.keys(users), function (key, callback) { + for (const [key, user] of users) { let socket = realtime.io.sockets.connected[key] - if ((!socket && users[key]) || + if ((!socket && user) || (socket && (!socket.rooms || socket.rooms.length <= 0))) { logger.debug(`cleaner found redundant user: ${key}`) if (!socket) { @@ -731,10 +754,7 @@ setInterval(function () { disconnectSocketQueue.push(socket) disconnect(socket) } - return callback(null, null) - }, function (err) { - if (err) return logger.error('cleaner error', err) - }) + } }, 60000) function updateUserData (socket: Socket, user): void { @@ -771,16 +791,17 @@ function connection (socket: SocketWithNoteId): void { // random color let color = randomcolor() // make sure color not duplicated or reach max random count - if (notes[noteId]) { + const note = notes.get(noteId) + if (note) { let randomcount = 0 const maxrandomcount = 10 let found = false do { - Object.keys(notes[noteId].users).forEach(function (userId) { - if (notes[noteId].users[userId].color === color) { + for (const user of note.users.values()) { + if (user.color === color) { found = true } - }) + } if (found) { color = randomcolor() randomcount++ @@ -788,7 +809,7 @@ function connection (socket: SocketWithNoteId): void { } while (found && randomcount < maxrandomcount) } // create user data - users[socket.id] = { + users.set(socket.id, { id: socket.id, address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address, 'user-agent': socket.handshake.headers['user-agent'], @@ -798,9 +819,10 @@ function connection (socket: SocketWithNoteId): void { userid: null, name: null, idle: false, - type: null - } - updateUserData(socket, users[socket.id]) + type: null, + photo: {} + }) + updateUserData(socket, users.get(socket.id)) // start connection connectionSocketQueue.push(socket) @@ -815,8 +837,8 @@ function connection (socket: SocketWithNoteId): void { // received user status socket.on('user status', function (data) { const noteId = socket.noteId - const user = users[socket.id] - if (!noteId || !notes[noteId] || !user) return + const user = users.get(socket.id) + if (!noteId || !notes.get(noteId) || !user) return logger.debug(`SERVER received [${noteId}] user status from [${socket.id}]: ${JSON.stringify(data)}`) if (data) { user.idle = data.idle @@ -830,8 +852,9 @@ function connection (socket: SocketWithNoteId): void { // need login to do more actions if (socket.request.user && socket.request.user.logged_in) { const noteId = socket.noteId - if (!noteId || !notes[noteId]) return - const note = notes[noteId] + if (!noteId) return + const note = notes.get(noteId) + if (!note) return // Only owner can change permission if (note.owner && note.owner === socket.request.user.id) { if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousEdits) return @@ -876,8 +899,9 @@ function connection (socket: SocketWithNoteId): void { // need login to do more actions if (socket.request.user && socket.request.user.logged_in) { const noteId = socket.noteId - if (!noteId || !notes[noteId]) return - const note = notes[noteId] + if (!noteId) return + const note = notes.get(noteId) + if (!note) return // Only owner can delete note if (note.owner && note.owner === socket.request.user.id) { Note.destroy({ @@ -906,8 +930,10 @@ function connection (socket: SocketWithNoteId): void { socket.on('user changed', function () { logger.info('user changed') const noteId = socket.noteId - if (!noteId || !notes[noteId]) return - const user = notes[noteId].users[socket.id] + if (!noteId) return + const note = notes.get(noteId) + if (!note) return + const user = note.users.get(socket.id) if (!user) return updateUserData(socket, user) emitOnlineUsers(socket) @@ -916,14 +942,15 @@ function connection (socket: SocketWithNoteId): void { // received sync of online users request socket.on('online users', function () { const noteId = socket.noteId - if (!noteId || !notes[noteId]) return + if (!noteId) return + const note = notes.get(noteId) + if (!note) return const users: UserSession[] = [] - Object.keys(notes[noteId].users).forEach(function (key) { - const user = notes[noteId].users[key] + for (const user of note.users.values()) { if (user) { users.push(buildUserOutData(user)) } - }) + } const out = { users: users } @@ -941,8 +968,8 @@ function connection (socket: SocketWithNoteId): void { // received cursor focus socket.on('cursor focus', function (data) { const noteId = socket.noteId - const user = users[socket.id] - if (!noteId || !notes[noteId] || !user) return + const user = users.get(socket.id) + if (!noteId || !notes.get(noteId) || !user) return user.cursor = data const out = buildUserOutData(user) socket.broadcast.to(noteId).emit('cursor focus', out) @@ -951,8 +978,8 @@ function connection (socket: SocketWithNoteId): void { // received cursor activity socket.on('cursor activity', function (data) { const noteId = socket.noteId - const user = users[socket.id] - if (!noteId || !notes[noteId] || !user) return + const user = users.get(socket.id) + if (!noteId || !notes.get(noteId) || !user) return user.cursor = data const out = buildUserOutData(user) socket.broadcast.to(noteId).emit('cursor activity', out) @@ -961,8 +988,8 @@ function connection (socket: SocketWithNoteId): void { // received cursor blur socket.on('cursor blur', function () { const noteId = socket.noteId - const user = users[socket.id] - if (!noteId || !notes[noteId] || !user) return + const user = users.get(socket.id) + if (!noteId || !notes.get(noteId) || !user) return user.cursor = null const out = { id: socket.id