From ea9cd56a2011561436c2d972573c7982ab968be1 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 9 May 2020 17:08:35 +0200 Subject: [PATCH 1/5] Simplify processData Signed-off-by: David Mehren --- lib/utils.ts | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/lib/utils.ts b/lib/utils.ts index 4a5769ac7..a4223666c 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,8 +1,8 @@ -import { logger } from './logger' -import { realtime } from './realtime' -import { config } from './config' import fs from 'fs' +import { config } from './config' +import { logger } from './logger' import { Revision } from './models' +import { realtime } from './realtime' export function getImageMimeType (imagePath: string): string | undefined { const fileExtension = /[^.]+$/.exec(imagePath) @@ -33,21 +33,11 @@ export function stripNullByte (value: string): string { return value ? value.replace(/\u0000/g, '') : value } -export function processData (data, _default, process?) { - if (data === undefined) return data - else if (process) { - if (data === null) { - return _default - } else { - return process(data) - } - } else { - if (data === null) { - return _default - } else { - return data - } - } +export function processData (data: T, _default: T, process?: (T) => T): T | undefined { + if (data === undefined) return undefined + else if (data === null) return _default + else if (process) return process(data) + else return data } export function handleTermSignals (io): void { @@ -65,7 +55,8 @@ export function handleTermSignals (io): void { if (config.path) { // ToDo: add a proper error handler // eslint-disable-next-line @typescript-eslint/no-empty-function - fs.unlink(config.path, (_) => {}) + fs.unlink(config.path, (_) => { + }) } const checkCleanTimer = setInterval(function () { if (realtime.isReady()) { From dcdbd3bf35f9734590c2b51a095c7981a88bbdbe Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 9 May 2020 17:49:44 +0200 Subject: [PATCH 2/5] Replace processData calls with ?? Many times processData was just used to ensure a default value is present. Since TS 3.7 the nullish coalescing operator ?? is supported for that. Signed-off-by: David Mehren --- lib/models/note.ts | 4 ++-- lib/models/revision.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/models/note.ts b/lib/models/note.ts index 129e365dc..3f38c1a5f 100644 --- a/lib/models/note.ts +++ b/lib/models/note.ts @@ -113,7 +113,7 @@ export class Note extends Model { @Column(DataType.TEXT) get title (): string { - return processData(this.getDataValue('title'), '') + return this.getDataValue('title') ?? '' } set title (value: string) { @@ -122,7 +122,7 @@ export class Note extends Model { @Column(DataType.TEXT({ length: 'long' })) get content (): string { - return processData(this.getDataValue('content'), '') + return this.getDataValue('content') ?? '' } set content (value: string) { diff --git a/lib/models/revision.ts b/lib/models/revision.ts index c7e112e60..0861a6383 100644 --- a/lib/models/revision.ts +++ b/lib/models/revision.ts @@ -81,7 +81,7 @@ export class Revision extends Model { @Column(DataType.TEXT({ length: 'long' })) get patch (): string { - return processData(this.getDataValue('patch'), '') + return this.getDataValue('patch') ?? '' } set patch (value: string) { @@ -90,7 +90,7 @@ export class Revision extends Model { @Column(DataType.TEXT({ length: 'long' })) get lastContent (): string { - return processData(this.getDataValue('lastContent'), '') + return this.getDataValue('lastContent') ?? '' } set lastContent (value: string) { @@ -99,7 +99,7 @@ export class Revision extends Model { @Column(DataType.TEXT({ length: 'long' })) get content (): string { - return processData(this.getDataValue('content'), '') + return this.getDataValue('content') ?? '' } set content (value: string) { From 394abe7cf88856081133296d6190346d16d70d50 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 9 May 2020 18:06:01 +0200 Subject: [PATCH 3/5] Fix typings for connect-flash. The `msg` argument is optional. Signed-off-by: David Mehren --- lib/library-ext.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/library-ext.d.ts b/lib/library-ext.d.ts index b08d3b242..0d373e22f 100644 --- a/lib/library-ext.d.ts +++ b/lib/library-ext.d.ts @@ -4,6 +4,6 @@ import { User } from './models' declare module 'express' { export interface Request { user?: User; - flash (type: string, msg: string): any; + flash (type: string, msg?: string): any; } } From 642ae9417c73e41bbd56f34ea71db6f85909a520 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 9 May 2020 18:42:36 +0200 Subject: [PATCH 4/5] Fix interchanged parameters in socket.io disconnect handler. :bug: Signed-off-by: David Mehren --- lib/realtime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/realtime.ts b/lib/realtime.ts index c6daaaaa6..d8e90a1b9 100644 --- a/lib/realtime.ts +++ b/lib/realtime.ts @@ -947,7 +947,7 @@ function connection (socket) { // when a new client disconnect socket.on('disconnect', function () { - if (isDuplicatedInSocketQueue(socket, disconnectSocketQueue)) return + if (isDuplicatedInSocketQueue(disconnectSocketQueue, socket)) return disconnectSocketQueue.push(socket) disconnect(socket) }) From 6c8c5f10623093a26eb7d8d93af77ec06082ed78 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 9 May 2020 19:58:13 +0200 Subject: [PATCH 5/5] Type annotations for realtime.ts Signed-off-by: David Mehren --- lib/ot/editor-socketio-server.ts | 11 ++-- lib/realtime.ts | 107 +++++++++++++++---------------- 2 files changed, 55 insertions(+), 63 deletions(-) diff --git a/lib/ot/editor-socketio-server.ts b/lib/ot/editor-socketio-server.ts index cb27d7f04..5cf7d27b0 100755 --- a/lib/ot/editor-socketio-server.ts +++ b/lib/ot/editor-socketio-server.ts @@ -1,5 +1,6 @@ import { EventEmitter } from 'events' import { logger } from '../logger' +import { SocketWithNoteId } from '../realtime' import Selection from './selection' import Server from './server' import TextOperation from './text-operation' @@ -8,7 +9,7 @@ import WrappedOperation from './wrapped-operation' export class EditorSocketIOServer extends Server { private readonly users: {} private readonly docId: any - private mayWrite: any + private mayWrite: (socket: SocketWithNoteId, originIsOperation: boolean, callback: (mayEdit: boolean) => void) => void constructor (document, operations, docId, mayWrite, operationCallback) { super(document, operations) @@ -16,7 +17,7 @@ export class EditorSocketIOServer extends Server { EventEmitter.call(this) this.users = {} this.docId = docId - this.mayWrite = mayWrite || function (_, cb) { + this.mayWrite = mayWrite || function (_, originIsOperation, cb) { cb(true) } this.operationCallback = operationCallback @@ -32,8 +33,7 @@ export class EditorSocketIOServer extends Server { } socket.emit('doc', docOut) socket.on('operation', function (revision, operation, selection) { - socket.origin = 'operation' - self.mayWrite(socket, function (mayWrite) { + self.mayWrite(socket, true, function (mayWrite) { if (!mayWrite) { logger.info("User doesn't have the right to edit.") return @@ -59,8 +59,7 @@ export class EditorSocketIOServer extends Server { self.onGetOperations(socket, base, head) }) socket.on('selection', function (obj) { - socket.origin = 'selection' - self.mayWrite(socket, function (mayWrite) { + self.mayWrite(socket, false, function (mayWrite) { if (!mayWrite) { logger.info("User doesn't have the right to edit.") return diff --git a/lib/realtime.ts b/lib/realtime.ts index d8e90a1b9..372299ef7 100644 --- a/lib/realtime.ts +++ b/lib/realtime.ts @@ -1,17 +1,19 @@ -import { Author, Note, Revision, User } from './models' - -import ot from './ot' -import { History } from './history' -import { logger } from './logger' -import { config } from './config' +import async from 'async' +import Chance from 'chance' +import cookie from 'cookie' +import cookieParser from 'cookie-parser' import moment from 'moment' import randomcolor from 'randomcolor' -import async from 'async' -import cookieParser from 'cookie-parser' -import cookie from 'cookie' -import Chance from 'chance' +import { Socket } from 'socket.io' +import { config } from './config' + +import { History } from './history' +import { logger } from './logger' +import { Author, Note, Revision, User } from './models' import { EditorSocketIOServer } from './ot/editor-socketio-server' +export type SocketWithNoteId = Socket & { noteId: string } + const chance = new Chance() /* eslint-disable @typescript-eslint/no-use-before-define */ @@ -27,18 +29,18 @@ const realtime: any = { } /* eslint-enable @typescript-eslint/no-use-before-define */ -const disconnectSocketQueue: any = [] +const disconnectSocketQueue: SocketWithNoteId[] = [] -function onAuthorizeSuccess (data, accept) { +function onAuthorizeSuccess (data, accept): void { accept() } -function onAuthorizeFail (data, message, error, accept) { +function onAuthorizeFail (data, message, error, accept): void { accept() // accept whether authorize or not to allow anonymous usage } // secure the origin by the cookie -function secure (socket, next) { +function secure (socket: Socket, next: (err?: any) => void): void { try { const handshakeData = socket.request if (handshakeData.headers.cookie) { @@ -60,7 +62,7 @@ function secure (socket, next) { } } -function emitCheck (note) { +function emitCheck (note): void { const out = { title: note.title, updatetime: note.updatetime, @@ -78,7 +80,7 @@ const notes = {} let saverSleep = false -function finishUpdateNote (note, _note, callback) { +function finishUpdateNote (note: any, _note: Note, callback: any) { if (!note || !note.server) return callback(null, null) const body = note.server.document const title = note.title = Note.parseNoteTitle(body) @@ -98,12 +100,12 @@ function finishUpdateNote (note, _note, callback) { }) } -function updateHistory (userId, note, time?) { +function updateHistory (userId, note, time?): void { const noteId = note.alias ? note.alias : Note.encodeNoteId(note.id) if (note.server) History.updateHistory(userId, noteId, note.server.document, time) } -function updateNote (note, callback) { +function updateNote (note: any, callback: (err, note) => any): any { Note.findOne({ where: { id: note.id @@ -193,11 +195,10 @@ setInterval(function () { }) }, 60000 * 5) -let isConnectionBusy +let isConnectionBusy: boolean +let isDisconnectBusy: boolean -const connectionSocketQueue: any = [] - -let isDisconnectBusy +const connectionSocketQueue: SocketWithNoteId[] = [] function getStatus (callback) { Note.count().then(function (notecount) { @@ -254,14 +255,14 @@ function getStatus (callback) { }) } -function isReady () { +function isReady (): boolean { return realtime.io && Object.keys(notes).length === 0 && Object.keys(users).length === 0 && connectionSocketQueue.length === 0 && !isConnectionBusy && disconnectSocketQueue.length === 0 && !isDisconnectBusy } -function extractNoteIdFromSocket (socket) { +function extractNoteIdFromSocket (socket): string | boolean { if (!socket || !socket.handshake) { return false } @@ -272,7 +273,7 @@ function extractNoteIdFromSocket (socket) { } } -function parseNoteIdFromSocket (socket, callback) { +function parseNoteIdFromSocket (socket, callback: (err, noteId) => void): void { const noteId = extractNoteIdFromSocket(socket) if (!noteId) { return callback(null, null) @@ -298,7 +299,7 @@ function buildUserOutData (user) { return out } -function emitOnlineUsers (socket) { +function emitOnlineUsers (socket: SocketWithNoteId): void { const noteId = socket.noteId if (!noteId || !notes[noteId]) return const users: any[] = [] @@ -314,7 +315,7 @@ function emitOnlineUsers (socket) { realtime.io.to(noteId).emit('online users', out) } -function emitUserStatus (socket) { +function emitUserStatus (socket: SocketWithNoteId): void { const noteId = socket.noteId const user = users[socket.id] if (!noteId || !notes[noteId] || !user) return @@ -322,7 +323,7 @@ function emitUserStatus (socket) { socket.broadcast.to(noteId).emit('user status', out) } -function emitRefresh (socket) { +function emitRefresh (socket: SocketWithNoteId): void { const noteId = socket.noteId if (!noteId || !notes[noteId]) return const note = notes[noteId] @@ -342,7 +343,7 @@ function emitRefresh (socket) { socket.emit('refresh', out) } -function isDuplicatedInSocketQueue (queue, socket) { +function isDuplicatedInSocketQueue (queue: Socket[], socket: Socket): boolean { for (let i = 0; i < queue.length; i++) { if (queue[i] && queue[i].id === socket.id) { return true @@ -351,7 +352,7 @@ function isDuplicatedInSocketQueue (queue, socket) { return false } -function clearSocketQueue (queue, socket) { +function clearSocketQueue (queue: Socket[], socket: Socket): void { for (let i = 0; i < queue.length; i++) { if (!queue[i] || queue[i].id === socket.id) { queue.splice(i, 1) @@ -360,7 +361,7 @@ function clearSocketQueue (queue, socket) { } } -function connectNextSocket () { +function connectNextSocket (): void { setTimeout(function () { isConnectionBusy = false if (connectionSocketQueue.length > 0) { @@ -371,19 +372,19 @@ function connectNextSocket () { }, 1) } -function failConnection (code, err, socket) { - logger.error(err) +function failConnection (errorCode: number, errorMessage: string, socket: Socket): void { + logger.error(errorMessage) // clear error socket in queue clearSocketQueue(connectionSocketQueue, socket) connectNextSocket() // emit error info socket.emit('info', { - code: code + code: errorCode }) - return socket.disconnect(true) + socket.disconnect(true) } -function interruptConnection (socket, noteId, socketId) { +function interruptConnection (socket: Socket, noteId: string, socketId): void { if (notes[noteId]) delete notes[noteId] if (users[socketId]) delete users[socketId] if (socket) { @@ -394,25 +395,17 @@ function interruptConnection (socket, noteId, socketId) { connectNextSocket() } -function checkViewPermission (req, note) { +function checkViewPermission (req, note): boolean { if (note.permission === 'private') { - if (req.user && req.user.logged_in && req.user.id === note.owner) { - return true - } else { - return false - } + return !!(req.user?.logged_in && req.user.id === note.owner) } else if (note.permission === 'limited' || note.permission === 'protected') { - if (req.user && req.user.logged_in) { - return true - } else { - return false - } + return !!(req.user?.logged_in) } else { return true } } -function finishConnection (socket, noteId, socketId) { +function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: string): void { // if no valid info provided will drop the client if (!socket || !notes[noteId] || !users[socketId]) { return interruptConnection(socket, noteId, socketId) @@ -456,7 +449,7 @@ function finishConnection (socket, noteId, socketId) { } } -function ifMayEdit (socket, callback) { +function ifMayEdit (socket: SocketWithNoteId, originIsOperation: boolean, callback: (mayEdit: boolean) => void): void { const noteId = socket.noteId if (!noteId || !notes[noteId]) return const note = notes[noteId] @@ -482,7 +475,7 @@ function ifMayEdit (socket, callback) { break } // if user may edit and this is a text operation - if (socket.origin === 'operation' && mayEdit) { + if (originIsOperation && mayEdit) { // save for the last change user id if (socket.request.user && socket.request.user.logged_in) { note.lastchangeuser = socket.request.user.id @@ -493,7 +486,7 @@ function ifMayEdit (socket, callback) { return callback(mayEdit) } -function operationCallback (socket, operation) { +function operationCallback (socket: SocketWithNoteId, operation): void { const noteId = socket.noteId if (!noteId || !notes[noteId]) return const note = notes[noteId] @@ -524,7 +517,7 @@ function operationCallback (socket, operation) { } } }).catch(function (err) { - return logger.error('operation callback failed: ' + err) + logger.error('operation callback failed: ' + err) }) } note.tempUsers[userId] = Date.now() @@ -535,11 +528,11 @@ function operationCallback (socket, operation) { }) } -function startConnection (socket) { +function startConnection (socket: SocketWithNoteId): void { if (isConnectionBusy) return isConnectionBusy = true - const noteId = socket.noteId + const noteId: string = socket.noteId if (!noteId) { return failConnection(404, 'note id not found', socket) } @@ -625,7 +618,7 @@ function startConnection (socket) { isConnectionBusy = false isDisconnectBusy = false -function disconnect (socket) { +function disconnect (socket: SocketWithNoteId): void { if (isDisconnectBusy) return isDisconnectBusy = true @@ -712,7 +705,7 @@ setInterval(function () { }) }, 60000) -function updateUserData (socket, user) { +function updateUserData (socket: Socket, user): void { // retrieve user data from passport if (socket.request.user && socket.request.user.logged_in) { const profile = User.getProfile(socket.request.user) @@ -727,7 +720,7 @@ function updateUserData (socket, user) { } } -function connection (socket) { +function connection (socket: SocketWithNoteId): void { if (realtime.maintenance) return parseNoteIdFromSocket(socket, function (err, noteId) { if (err) {