From fa301ab450fc25df82babd69e3c9000924521e43 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 23 May 2020 23:24:01 +0200 Subject: [PATCH 01/19] Re-exclude OT code from eslint Signed-off-by: David Mehren --- .eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 286d781b1..954d39180 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,4 @@ -lib/ot +src/lib/ot public/vendor public/build node_modules From 1f517bfb994750dc85752c5227a69a4ae224c03d Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 17:07:13 +0200 Subject: [PATCH 02/19] More types for history, config/interfaces and Request.flash Signed-off-by: David Mehren --- src/lib/config/interfaces.ts | 3 ++- src/lib/history.ts | 21 +++++++++++---------- src/lib/library-ext.d.ts | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/lib/config/interfaces.ts b/src/lib/config/interfaces.ts index 5a9308676..8fa6023f9 100644 --- a/src/lib/config/interfaces.ts +++ b/src/lib/config/interfaces.ts @@ -36,7 +36,7 @@ export interface Config { forbiddenNoteIDs: string[]; defaultPermission: string; dbURL: string; - db: any; + db; sslKeyPath: string; sslCertPath: string; sslCAPath: string[]; @@ -153,5 +153,6 @@ export interface Config { linkifyHeaderStyle: string; // TODO: Remove escape hatch for dynamically added properties + // eslint-disable-next-line @typescript-eslint/no-explicit-any [propName: string]: any; } diff --git a/src/lib/history.ts b/src/lib/history.ts index 898b96a0f..694900a78 100644 --- a/src/lib/history.ts +++ b/src/lib/history.ts @@ -6,6 +6,7 @@ import LZString from 'lz-string' import { logger } from './logger' import { Note, User } from './models' import { errors } from './errors' +import { LogEntry } from 'winston' // public @@ -34,7 +35,7 @@ function parseHistoryArrayToMap (historyArray: HistoryObject[]): Map void): void { +function getHistory (userId, callback: (err: unknown, history: Map | null) => void): void { User.findOne({ where: { id: userId @@ -44,7 +45,7 @@ function getHistory (userId, callback: (err: any, history: any) => void): void { return callback(null, null) } if (user.history) { - const history = JSON.parse(user.history) + const history: HistoryObject[] = JSON.parse(user.history) // migrate LZString encoded note id to base64url encoded note id for (let i = 0, l = history.length; i < l; i++) { // Calculate minimal string length for an UUID that is encoded @@ -74,14 +75,14 @@ function getHistory (userId, callback: (err: any, history: any) => void): void { return callback(null, parseHistoryArrayToMap(history)) } logger.debug(`read empty history: ${user.id}`) - return callback(null, []) + return callback(null, new Map()) }).catch(function (err) { logger.error('read history failed: ' + err) return callback(err, null) }) } -function setHistory (userId: string, history: any[], callback: (err: any | null, count: [number, User[]] | null) => void): void { +function setHistory (userId: string, history: HistoryObject[], callback: (err: LogEntry | null, count: [number, User[]] | null) => void): void { User.update({ history: JSON.stringify(history) }, { @@ -109,7 +110,7 @@ function updateHistory (userId: string, noteId: string, document, time): void { noteHistory.text = noteInfo.title noteHistory.time = time || Date.now() noteHistory.tags = noteInfo.tags - setHistory(userId, history, function (err, _) { + setHistory(userId, parseHistoryMapToArray(history), function (err, _) { if (err) { logger.log(err) } @@ -118,7 +119,7 @@ function updateHistory (userId: string, noteId: string, document, time): void { } } -function historyGet (req, res): any { +function historyGet (req, res): void { if (req.isAuthenticated()) { getHistory(req.user.id, function (err, history) { if (err) return errors.errorInternalError(res) @@ -132,7 +133,7 @@ function historyGet (req, res): any { } } -function historyPost (req, res): any { +function historyPost (req, res): void { if (req.isAuthenticated()) { const noteId = req.params.noteId if (!noteId) { @@ -160,7 +161,7 @@ function historyPost (req, res): any { if (!history[noteId]) return errors.errorNotFound(res) if (req.body.pinned === 'true' || req.body.pinned === 'false') { history[noteId].pinned = (req.body.pinned === 'true') - setHistory(req.user.id, history, function (err, _) { + setHistory(req.user.id, parseHistoryMapToArray(history), function (err, _) { if (err) return errors.errorInternalError(res) res.end() }) @@ -174,7 +175,7 @@ function historyPost (req, res): any { } } -function historyDelete (req, res): any { +function historyDelete (req, res): void { if (req.isAuthenticated()) { const noteId = req.params.noteId if (!noteId) { @@ -187,7 +188,7 @@ function historyDelete (req, res): any { if (err) return errors.errorInternalError(res) if (!history) return errors.errorNotFound(res) delete history[noteId] - setHistory(req.user.id, history, function (err, _) { + setHistory(req.user.id, parseHistoryMapToArray(history), function (err, _) { if (err) return errors.errorInternalError(res) res.end() }) diff --git a/src/lib/library-ext.d.ts b/src/lib/library-ext.d.ts index fd2ec2d7a..e06789e84 100644 --- a/src/lib/library-ext.d.ts +++ b/src/lib/library-ext.d.ts @@ -3,6 +3,6 @@ import { User } from './models' declare module 'express' { export interface Request { user?: User; - flash (type: string, msg?: string): any; + flash (type: string, msg?: string): [] | object | number; } } From b01bb93813d0a11852a88b13e0fa948c2dafb45a Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 17:49:32 +0200 Subject: [PATCH 03/19] More types for Note Signed-off-by: David Mehren --- src/lib/models/note.ts | 57 ++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/lib/models/note.ts b/src/lib/models/note.ts index 3f38c1a5f..2cc56711f 100644 --- a/src/lib/models/note.ts +++ b/src/lib/models/note.ts @@ -1,3 +1,15 @@ +import async from 'async' +import base64url from 'base64url' +import cheerio from 'cheerio' +// eslint-disable-next-line @typescript-eslint/camelcase +import { diff_match_patch, patch_obj } from 'diff-match-patch' +import fs from 'fs' +import LZString from 'lz-string' +import markdownIt from 'markdown-it' +import metaMarked from 'meta-marked' +import moment from 'moment' +import path from 'path' +import Sequelize from 'sequelize' import { AfterCreate, AllowNull, @@ -15,24 +27,12 @@ import { } from 'sequelize-typescript' import { generate as shortIdGenerate, isValid as shortIdIsValid } from 'shortid' -import { Author, Revision, User } from './index' -import { processData, stripNullByte } from '../utils' -import Sequelize from 'sequelize' -import fs from 'fs' -import path from 'path' -import LZString from 'lz-string' -import base64url from 'base64url' -import markdownIt from 'markdown-it' -import metaMarked from 'meta-marked' -import cheerio from 'cheerio' -import async from 'async' -import moment from 'moment' -// eslint-disable-next-line @typescript-eslint/camelcase -import { diff_match_patch, patch_obj } from 'diff-match-patch' import S from 'string' import { config } from '../config' import { logger } from '../logger' import ot from '../ot' +import { processData, stripNullByte } from '../utils' +import { Author, Revision, User } from './index' const md = markdownIt() // eslint-disable-next-line new-cap @@ -48,16 +48,24 @@ enum PermissionEnum { private = 'private' } +export class OpengraphMetadata { + title: string | number + description: string | number + type: string +} + export class NoteMetadata { title: string description: string robots: string GA: string disqus: string - slideOptions: any - opengraph: any + slideOptions + opengraph: OpengraphMetadata } +export type NoteAuthorship = [string, number, number, number, number] + @Table({ paranoid: false }) export class Note extends Model { @PrimaryKey @@ -130,17 +138,18 @@ export class Note extends Model { } @Column(DataType.TEXT({ length: 'long' })) - get authorship (): string { + get authorship (): NoteAuthorship[] { return processData(this.getDataValue('authorship'), [], JSON.parse) } - set authorship (value: string) { - this.setDataValue('authorship', JSON.stringify(value)) + set authorship (value: NoteAuthorship[]) { + // Evil hack for TypeScript to accept saving a string in a NoteAuthorship DB-field + this.setDataValue('authorship', JSON.stringify(value) as unknown as NoteAuthorship[]) } @BeforeCreate static async defaultContentAndPermissions (note: Note): Promise { - return await new Promise(function (resolve, reject) { + return await new Promise(function (resolve) { // if no content specified then use default note if (!note.content) { let filePath: string @@ -430,7 +439,7 @@ export class Note extends Model { return tags } - static extractMeta (content): any { + static extractMeta (content): { markdown: string; meta: {} } { try { const obj = metaMarked(content) if (!obj.markdown) obj.markdown = '' @@ -472,8 +481,8 @@ export class Note extends Model { return _meta } - static parseOpengraph (meta, title): any { - let _ogdata: any = {} + static parseOpengraph (meta, title: string): OpengraphMetadata { + let _ogdata = new OpengraphMetadata() if (meta.opengraph) { _ogdata = meta.opengraph } @@ -489,7 +498,7 @@ export class Note extends Model { return _ogdata } - static updateAuthorshipByOperation (operation, userId: string|null, authorships): any { + static updateAuthorshipByOperation (operation, userId: string | null, authorships): NoteAuthorship[] { let index = 0 const timestamp = Date.now() for (let i = 0; i < operation.length; i++) { From 8b6d5a64f042fba0f4de9fa64a23dff1c1761471 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 18:10:37 +0200 Subject: [PATCH 04/19] Types and lint fixes in lib/web/note Signed-off-by: David Mehren --- src/lib/web/note/actions.ts | 8 ++++---- src/lib/web/note/controller.ts | 16 ++++++++-------- src/lib/web/note/slide.ts | 6 +++--- src/lib/web/note/util.ts | 12 ++++++------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/lib/web/note/actions.ts b/src/lib/web/note/actions.ts index 7d90fe196..a1f3bfa11 100644 --- a/src/lib/web/note/actions.ts +++ b/src/lib/web/note/actions.ts @@ -1,4 +1,4 @@ -import { Response } from 'express' +import { Request, Response } from 'express' import { Note, Revision } from '../../models' import { logger } from '../../logger' @@ -8,7 +8,7 @@ import shortId from 'shortid' import moment from 'moment' import querystring from 'querystring' -export function getInfo (req: any, res: Response, note: Note): void { +export function getInfo (req: Request, res: Response, note: Note): void { const body = note.content const extracted = Note.extractMeta(body) const markdown = extracted.markdown @@ -31,7 +31,7 @@ export function getInfo (req: any, res: Response, note: Note): void { res.send(data) } -export function createGist (req: any, res: Response, note: Note): void { +export function createGist (req: Request, res: Response, note: Note): void { const data = { // eslint-disable-next-line @typescript-eslint/camelcase client_id: config.github.clientID, @@ -44,7 +44,7 @@ export function createGist (req: any, res: Response, note: Note): void { res.redirect('https://github.com/login/oauth/authorize?' + query) } -export function getRevision (req: any, res: Response, note: Note): void { +export function getRevision (req: Request, res: Response, note: Note): void { const actionId = req.params.actionId if (actionId) { const time = moment(parseInt(actionId)) diff --git a/src/lib/web/note/controller.ts b/src/lib/web/note/controller.ts index 79711af85..55de8943a 100644 --- a/src/lib/web/note/controller.ts +++ b/src/lib/web/note/controller.ts @@ -1,4 +1,4 @@ -import { NextFunction, Request, Response } from 'express' +import { Request, Response } from 'express' import { config } from '../../config' import { errors } from '../../errors' import { logger } from '../../logger' @@ -6,7 +6,7 @@ import { Note, User } from '../../models' import * as ActionController from './actions' import * as NoteUtils from './util' -export function publishNoteActions (req: any, res: Response, next: NextFunction) { +export function publishNoteActions (req: Request, res: Response): void { NoteUtils.findNoteOrCreate(req, res, function (note) { const action = req.params.action switch (action) { @@ -23,7 +23,7 @@ export function publishNoteActions (req: any, res: Response, next: NextFunction) }) } -export function showPublishNote (req: any, res: Response, next: NextFunction) { +export function showPublishNote (req: Request, res: Response): void { const include = [{ model: User, as: 'owner' @@ -51,10 +51,10 @@ export function showPublishNote (req: any, res: Response, next: NextFunction) { logger.error(err) return errors.errorInternalError(res) }) - }, include) + }) } -export function showNote (req: any, res: Response, next: NextFunction) { +export function showNote (req: Request, res: Response): void { NoteUtils.findNoteOrCreate(req, res, function (note) { // force to use note id const noteId = req.params.noteId @@ -79,7 +79,7 @@ export function showNote (req: any, res: Response, next: NextFunction) { }) } -export function createFromPOST (req: any, res: Response, next: NextFunction) { +export function createFromPOST (req: Request, res: Response): void { let body = '' if (req.body && req.body.length > config.documentMaxLength) { return errors.errorTooLong(res) @@ -90,7 +90,7 @@ export function createFromPOST (req: any, res: Response, next: NextFunction) { return NoteUtils.newNote(req, res, body) } -export function doAction (req: any, res: Response, next: NextFunction) { +export function doAction (req: Request, res: Response): void { const noteId = req.params.noteId NoteUtils.findNoteOrCreate(req, res, (note) => { const action = req.params.action @@ -121,7 +121,7 @@ export function doAction (req: any, res: Response, next: NextFunction) { }) } -export function downloadMarkdown (req: Request, res: Response, note: any) { +export function downloadMarkdown (req: Request, res: Response, note): void { const body = note.content let filename = Note.decodeTitle(note.title) filename = encodeURIComponent(filename) diff --git a/src/lib/web/note/slide.ts b/src/lib/web/note/slide.ts index 5008295f1..850c00f08 100644 --- a/src/lib/web/note/slide.ts +++ b/src/lib/web/note/slide.ts @@ -5,7 +5,7 @@ import { logger } from '../../logger' import { Note, User } from '../../models' import * as NoteUtils from './util' -export function publishSlideActions (req: any, res: Response, next: NextFunction) { +export function publishSlideActions (req: any, res: Response) { NoteUtils.findNoteOrCreate(req, res, function (note) { const action = req.params.action if (action === 'edit') { @@ -16,7 +16,7 @@ export function publishSlideActions (req: any, res: Response, next: NextFunction }) } -export function showPublishSlide (req: any, res: Response, next: NextFunction) { +export function showPublishSlide (req: any, res: Response) { const include = [{ model: User, as: 'owner' @@ -44,5 +44,5 @@ export function showPublishSlide (req: any, res: Response, next: NextFunction) { logger.error(err) return errors.errorInternalError(res) }) - }, include) + }) } diff --git a/src/lib/web/note/util.ts b/src/lib/web/note/util.ts index e26d3235e..7c367fd74 100644 --- a/src/lib/web/note/util.ts +++ b/src/lib/web/note/util.ts @@ -1,4 +1,4 @@ -import { Response } from 'express' +import { Request, Response } from 'express' import fs from 'fs' import path from 'path' @@ -8,7 +8,7 @@ import { errors } from '../../errors' import { logger } from '../../logger' import { Note, User } from '../../models' -export function newNote (req: any, res: Response, body: string | null) { +export function newNote (req, res: Response, body: string | null): void { let owner = null const noteId = req.params.noteId ? req.params.noteId : null if (req.isAuthenticated()) { @@ -33,7 +33,7 @@ export function newNote (req: any, res: Response, body: string | null) { }) } -export function checkViewPermission (req: any, note: any) { +export function checkViewPermission (req, note: any): boolean { if (note.permission === 'private') { return req.isAuthenticated() && note.ownerId === req.user.id } else if (note.permission === 'limited' || note.permission === 'protected') { @@ -43,7 +43,7 @@ export function checkViewPermission (req: any, note: any) { } } -export function findNoteOrCreate (req, res, callback: (note: any) => void, include?: Includeable[]) { +export function findNoteOrCreate (req: Request, res: Response, callback: (note: Note) => void): void { const id = req.params.noteId || req.params.shortid Note.parseNoteId(id, function (err, _id) { if (err) { @@ -70,14 +70,14 @@ export function findNoteOrCreate (req, res, callback: (note: any) => void, inclu }) } -function isRevealTheme (theme: string) { +function isRevealTheme (theme: string): string | undefined { if (fs.existsSync(path.join(__dirname, '..', '..', '..', '..', 'public', 'build', 'reveal.js', 'css', 'theme', theme + '.css'))) { return theme } return undefined } -export function getPublishData (req: any, res: Response, note: any, callback: (data: any) => void) { +export function getPublishData (req: Request, res: Response, note, callback: (data) => void): void { const body = note.content const extracted = Note.extractMeta(body) const markdown = extracted.markdown From 762e58b0d4bd2afa4bf914fe7a66f9fc0065b011 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 18:11:47 +0200 Subject: [PATCH 05/19] Do not lint migration code Signed-off-by: David Mehren --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index 954d39180..062f955d0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ src/lib/ot +src/lib/migrations public/vendor public/build node_modules From d925b0cc5f9abf65619f5bce42dea9ae40092b51 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 18:38:24 +0200 Subject: [PATCH 06/19] Types and lint fixes in lib/web/auth Signed-off-by: David Mehren --- src/lib/web/auth/dropbox/index.ts | 2 +- src/lib/web/auth/google/index.ts | 2 +- src/lib/web/auth/oauth2/oauth2-custom-strategy.ts | 2 +- src/lib/web/auth/utils.ts | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/web/auth/dropbox/index.ts b/src/lib/web/auth/dropbox/index.ts index bf8db04a6..a6f4d750e 100644 --- a/src/lib/web/auth/dropbox/index.ts +++ b/src/lib/web/auth/dropbox/index.ts @@ -18,7 +18,7 @@ export const DropboxMiddleware: AuthMiddleware = { }, ( accessToken: string, refreshToken: string, - profile: any, + profile, done: (err?: Error | null, user?: User) => void ): void => { // the Dropbox plugin wraps the email addresses in an object diff --git a/src/lib/web/auth/google/index.ts b/src/lib/web/auth/google/index.ts index 54c2753ed..165d1f7e5 100644 --- a/src/lib/web/auth/google/index.ts +++ b/src/lib/web/auth/google/index.ts @@ -17,7 +17,7 @@ export const GoogleMiddleware: AuthMiddleware = { }, ( accessToken: string, refreshToken: string, - profile: any, + profile, done) => { /* This ugly hack is neccessary, because the Google Strategy wants a done-callback with an err as Error | null | undefined diff --git a/src/lib/web/auth/oauth2/oauth2-custom-strategy.ts b/src/lib/web/auth/oauth2/oauth2-custom-strategy.ts index 4fec1cbc8..a6b80fcc3 100644 --- a/src/lib/web/auth/oauth2/oauth2-custom-strategy.ts +++ b/src/lib/web/auth/oauth2/oauth2-custom-strategy.ts @@ -2,7 +2,7 @@ import { InternalOAuthError, Strategy as OAuth2Strategy } from 'passport-oauth2' import { config } from '../../../config' import { Profile, ProviderEnum } from '../../../models/user' -function extractProfileAttribute (data, path: string): any { +function extractProfileAttribute (data, path: string): string { // can handle stuff like `attrs[0].name` const pathArray = path.split('.') for (const segment of pathArray) { diff --git a/src/lib/web/auth/utils.ts b/src/lib/web/auth/utils.ts index bf0de1fc6..025b21478 100644 --- a/src/lib/web/auth/utils.ts +++ b/src/lib/web/auth/utils.ts @@ -1,10 +1,11 @@ +import { Profile } from 'passport' import { User } from '../../models' import { logger } from '../../logger' export function passportGeneralCallback ( accessToken: string, refreshToken: string, - profile: any, + profile: Profile, done: (err?: Error | null, user?: User) => void ): void { const stringifiedProfile = JSON.stringify(profile) From f208f3eeef7152b34bf4b2ab44d9f3327bd6f10c Mon Sep 17 00:00:00 2001 From: Yannick Bungers Date: Sun, 24 May 2020 18:41:15 +0200 Subject: [PATCH 07/19] Types for realtime.ts Signed-off-by: Yannick Bungers --- src/lib/config/utils.ts | 2 +- src/lib/realtime.ts | 66 ++++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/lib/config/utils.ts b/src/lib/config/utils.ts index 7ca96dd16..56cb5137e 100644 --- a/src/lib/config/utils.ts +++ b/src/lib/config/utils.ts @@ -8,7 +8,7 @@ export function toBooleanConfig (configValue: string | boolean | undefined): boo return configValue } -export function toArrayConfig (configValue: string | undefined, separator = ',', fallback = []): any[] { +export function toArrayConfig (configValue: string | undefined, separator = ',', fallback = []): string[] { if (configValue) { return (configValue.split(separator).map(arrayItem => arrayItem.trim())) } diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts index 372299ef7..c99435278 100644 --- a/src/lib/realtime.ts +++ b/src/lib/realtime.ts @@ -40,7 +40,7 @@ function onAuthorizeFail (data, message, error, accept): void { } // secure the origin by the cookie -function secure (socket: Socket, next: (err?: any) => void): void { +function secure (socket: Socket, next: (err?: Error) => void): void { try { const handshakeData = socket.request if (handshakeData.headers.cookie) { @@ -74,13 +74,46 @@ function emitCheck (note): void { realtime.io.to(note.id).emit('check', out) } +class UserSession { + id: string; + login: any + userid: string + photo: any + color: any + cursor: any + name: string + idle: any + type: any +} + +class NoteSession { + id: string + alias: string + title: string + owner: UserSession + ownerprofile: any + permission: any + lastchangeuser: string + lastchangeuserprofile: any + socks: SocketWithNoteId[] + users: UserSession[] + tempUsers: UserSession[] + createtime: number + updatetime: number + server: any + authors: UserSession[] + authorship: UserSession +} + + + // actions -const users = {} -const notes = {} +const users: UserSession[] = [] +const notes: NoteSession[] = [] let saverSleep = false -function finishUpdateNote (note: any, _note: Note, callback: any) { +function finishUpdateNote (note: NoteSession, _note: Note, callback: (err: Error | null, note: Note | null) => void): void { if (!note || !note.server) return callback(null, null) const body = note.server.document const title = note.title = Note.parseNoteTitle(body) @@ -100,12 +133,12 @@ function finishUpdateNote (note: any, _note: Note, callback: any) { }) } -function updateHistory (userId, note, time?): void { +function updateHistory (userId, note: NoteSession, 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: any, callback: (err, note) => any): any { +function updateNote (note: NoteSession, callback: (err, note) => void): void { Note.findOne({ where: { id: note.id @@ -114,7 +147,7 @@ function updateNote (note: any, callback: (err, note) => any): any { if (!_note) return callback(null, null) // update user note history const tempUsers = Object.assign({}, note.tempUsers) - note.tempUsers = {} + note.tempUsers = [] Object.keys(tempUsers).forEach(function (key) { updateHistory(key, note, tempUsers[key]) }) @@ -200,7 +233,7 @@ let isDisconnectBusy: boolean const connectionSocketQueue: SocketWithNoteId[] = [] -function getStatus (callback) { +function getStatus (callback): void { Note.count().then(function (notecount) { const distinctaddresses: string[] = [] const regaddresses: string[] = [] @@ -262,7 +295,7 @@ function isReady (): boolean { disconnectSocketQueue.length === 0 && !isDisconnectBusy } -function extractNoteIdFromSocket (socket): string | boolean { +function extractNoteIdFromSocket (socket: Socket): string | boolean { if (!socket || !socket.handshake) { return false } @@ -273,7 +306,7 @@ function extractNoteIdFromSocket (socket): string | boolean { } } -function parseNoteIdFromSocket (socket, callback: (err, noteId) => void): void { +function parseNoteIdFromSocket (socket: Socket, callback: (err: string | null, noteId: string | null) => void): void { const noteId = extractNoteIdFromSocket(socket) if (!noteId) { return callback(null, null) @@ -284,8 +317,8 @@ function parseNoteIdFromSocket (socket, callback: (err, noteId) => void): void { }) } -function buildUserOutData (user) { - const out = { +function buildUserOutData (user): UserSession { + return { id: user.id, login: user.login, userid: user.userid, @@ -296,13 +329,12 @@ function buildUserOutData (user) { idle: user.idle, type: user.type } - return out } function emitOnlineUsers (socket: SocketWithNoteId): void { const noteId = socket.noteId if (!noteId || !notes[noteId]) return - const users: any[] = [] + const users: UserSession[] = [] Object.keys(notes[noteId].users).forEach(function (key) { const user = notes[noteId].users[key] if (user) { @@ -507,7 +539,7 @@ function operationCallback (socket: SocketWithNoteId, operation): void { userId: userId, color: user.color } - }).then(function ([author, created]) { + }).then(function ([author, _created]) { if (author) { note.authors[author.userId] = { userid: author.userId, @@ -646,7 +678,7 @@ function disconnect (socket: SocketWithNoteId): void { // remove note in notes if no user inside if (Object.keys(note.users).length <= 0) { if (note.server.isDirty) { - updateNote(note, function (err, _note) { + updateNote(note, function (err, _note: Note) { if (err) return logger.error('disconnect note failed: ' + err) // clear server before delete to avoid memory leaks note.server.document = '' @@ -885,7 +917,7 @@ function connection (socket: SocketWithNoteId): void { socket.on('online users', function () { const noteId = socket.noteId if (!noteId || !notes[noteId]) return - const users: any = [] + const users: UserSession[] = [] Object.keys(notes[noteId].users).forEach(function (key) { const user = notes[noteId].users[key] if (user) { From 280fda1d6c23da1aa2a7baadca8f686a582e733d Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 20:54:56 +0200 Subject: [PATCH 08/19] Fix note history updating :bug: a7aaded6 started to use a Map for a users note history in various places, but didn't update the code to actually use the Map operations. This broke updating the note history. Signed-off-by: David Mehren --- src/lib/history.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib/history.ts b/src/lib/history.ts index 694900a78..6bc8ad4fb 100644 --- a/src/lib/history.ts +++ b/src/lib/history.ts @@ -10,12 +10,12 @@ import { LogEntry } from 'winston' // public -type HistoryObject = { - id: string; - text: string; - time: number; - tags: string[]; - pinned?: boolean; +class HistoryObject { + id: string + text: string + time: number + tags: string[] + pinned?: boolean } function parseHistoryMapToArray (historyMap: Map): HistoryObject[] { @@ -101,15 +101,13 @@ function updateHistory (userId: string, noteId: string, document, time): void { if (userId && noteId && typeof document !== 'undefined') { getHistory(userId, function (err, history) { if (err || !history) return - if (!history[noteId]) { - history[noteId] = {} - } - const noteHistory = history[noteId] + const noteHistory = history.get(noteId) || new HistoryObject() const noteInfo = Note.parseNoteInfo(document) noteHistory.id = noteId noteHistory.text = noteInfo.title noteHistory.time = time || Date.now() noteHistory.tags = noteInfo.tags + history.set(noteId, noteHistory) setHistory(userId, parseHistoryMapToArray(history), function (err, _) { if (err) { logger.log(err) @@ -158,9 +156,10 @@ function historyPost (req, res): void { getHistory(req.user.id, function (err, history) { if (err) return errors.errorInternalError(res) if (!history) return errors.errorNotFound(res) - if (!history[noteId]) return errors.errorNotFound(res) + const noteHistory = history.get(noteId) + if (!noteHistory) return errors.errorNotFound(res) if (req.body.pinned === 'true' || req.body.pinned === 'false') { - history[noteId].pinned = (req.body.pinned === 'true') + noteHistory.pinned = (req.body.pinned === 'true') setHistory(req.user.id, parseHistoryMapToArray(history), function (err, _) { if (err) return errors.errorInternalError(res) res.end() @@ -187,7 +186,7 @@ function historyDelete (req, res): void { getHistory(req.user.id, function (err, history) { if (err) return errors.errorInternalError(res) if (!history) return errors.errorNotFound(res) - delete history[noteId] + history.delete(noteId) setHistory(req.user.id, parseHistoryMapToArray(history), function (err, _) { if (err) return errors.errorInternalError(res) res.end() From 5a6ec56a7e9a493a7571c30b7e761ac21d3dfa00 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 21:09:03 +0200 Subject: [PATCH 09/19] Cleanups lib/web/note Signed-off-by: David Mehren --- src/lib/web/note/controller.ts | 9 +-------- src/lib/web/note/slide.ts | 15 ++++----------- src/lib/web/note/util.ts | 3 +-- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/lib/web/note/controller.ts b/src/lib/web/note/controller.ts index 55de8943a..ec024123d 100644 --- a/src/lib/web/note/controller.ts +++ b/src/lib/web/note/controller.ts @@ -2,7 +2,7 @@ import { Request, Response } from 'express' import { config } from '../../config' import { errors } from '../../errors' import { logger } from '../../logger' -import { Note, User } from '../../models' +import { Note } from '../../models' import * as ActionController from './actions' import * as NoteUtils from './util' @@ -24,13 +24,6 @@ export function publishNoteActions (req: Request, res: Response): void { } export function showPublishNote (req: Request, res: Response): void { - const include = [{ - model: User, - as: 'owner' - }, { - model: User, - as: 'lastchangeuser' - }] NoteUtils.findNoteOrCreate(req, res, function (note) { // force to use short id const shortid = req.params.shortid diff --git a/src/lib/web/note/slide.ts b/src/lib/web/note/slide.ts index 850c00f08..2172f59a3 100644 --- a/src/lib/web/note/slide.ts +++ b/src/lib/web/note/slide.ts @@ -1,11 +1,11 @@ -import { NextFunction, Response } from 'express' +import { Request, Response } from 'express' import { config } from '../../config' import { errors } from '../../errors' import { logger } from '../../logger' -import { Note, User } from '../../models' +import { Note } from '../../models' import * as NoteUtils from './util' -export function publishSlideActions (req: any, res: Response) { +export function publishSlideActions (req: Request, res: Response): void { NoteUtils.findNoteOrCreate(req, res, function (note) { const action = req.params.action if (action === 'edit') { @@ -16,14 +16,7 @@ export function publishSlideActions (req: any, res: Response) { }) } -export function showPublishSlide (req: any, res: Response) { - const include = [{ - model: User, - as: 'owner' - }, { - model: User, - as: 'lastchangeuser' - }] +export function showPublishSlide (req: Request, res: Response): void { NoteUtils.findNoteOrCreate(req, res, function (note) { // force to use short id const shortid = req.params.shortid diff --git a/src/lib/web/note/util.ts b/src/lib/web/note/util.ts index 7c367fd74..b06a3c242 100644 --- a/src/lib/web/note/util.ts +++ b/src/lib/web/note/util.ts @@ -2,7 +2,6 @@ import { Request, Response } from 'express' import fs from 'fs' import path from 'path' -import { Includeable } from 'sequelize' import { config } from '../../config' import { errors } from '../../errors' import { logger } from '../../logger' @@ -33,7 +32,7 @@ export function newNote (req, res: Response, body: string | null): void { }) } -export function checkViewPermission (req, note: any): boolean { +export function checkViewPermission (req, note: Note): boolean { if (note.permission === 'private') { return req.isAuthenticated() && note.ownerId === req.user.id } else if (note.permission === 'limited' || note.permission === 'protected') { From 2fc2219bb4929485807baa0cf8aa784e79f31d87 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 21:09:25 +0200 Subject: [PATCH 10/19] Type fix in dmpWorker Signed-off-by: David Mehren --- src/lib/workers/dmpWorker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/workers/dmpWorker.ts b/src/lib/workers/dmpWorker.ts index 604bb7253..cfbdf3b84 100644 --- a/src/lib/workers/dmpWorker.ts +++ b/src/lib/workers/dmpWorker.ts @@ -98,7 +98,7 @@ function createPatch (lastDoc: string, currDoc: string): string { class Data { msg: string - cacheKey: any + cacheKey: string lastDoc?: string currDoc?: string revisions?: Revision[] From ed9a89efb549104da60b9e5fac7df01128853f5d Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 24 May 2020 21:09:42 +0200 Subject: [PATCH 11/19] Type fix in User model Signed-off-by: David Mehren --- src/lib/models/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/models/user.ts b/src/lib/models/user.ts index 453379cf7..2beb5a1c3 100644 --- a/src/lib/models/user.ts +++ b/src/lib/models/user.ts @@ -40,7 +40,7 @@ export type Profile = { avatarUrl: string; profileUrl: string; provider: ProviderEnum; - photos: any[]; + photos: { value: string }[]; } export type PhotoProfile = { From e8a34e7cedc7a1e5cc9f3ecbbf2cc868f864bfc7 Mon Sep 17 00:00:00 2001 From: Yannick Bungers Date: Sun, 24 May 2020 22:18:44 +0200 Subject: [PATCH 12/19] 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 From 591096ce8b8cc397e1bd2798885dd3d4987bd19b Mon Sep 17 00:00:00 2001 From: David Mehren Date: Mon, 25 May 2020 23:19:59 +0200 Subject: [PATCH 13/19] Add @types/codemirror Signed-off-by: David Mehren --- package.json | 1 + tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index feccc807c..c65d91459 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ "devDependencies": { "@types/archiver": "^3.1.0", "@types/bluebird": "^3.5.30", + "@types/codemirror": "^0.0.95", "@types/diff-match-patch": "^1.0.32", "@types/express": "^4.17.6", "@types/helmet": "^0.0.45", diff --git a/tsconfig.json b/tsconfig.json index 5bff2a5d0..90a04b54c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "sourceMap": true, - "lib": ["ES2019"], + "lib": ["ES2019", "DOM"], "alwaysStrict": true }, "include": ["src"] From 9c894633a8b99c28a1a1e45cdca4f217c265da66 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Mon, 25 May 2020 23:22:27 +0200 Subject: [PATCH 14/19] Many types (and corresponding changes to keek tsc happy) in realtime.ts Signed-off-by: David Mehren --- src/lib/realtime.ts | 107 +++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts index 1890a46c3..5b733d934 100644 --- a/src/lib/realtime.ts +++ b/src/lib/realtime.ts @@ -1,25 +1,33 @@ -import async from 'async' import Chance from 'chance' +import CodeMirror from 'codemirror' import cookie from 'cookie' import cookieParser from 'cookie-parser' import moment from 'moment' import randomcolor from 'randomcolor' -import { Socket } from 'socket.io' +import SocketIO, { 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' import { NoteAuthorship } from './models/note' +import { PhotoProfile } from './models/user' +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 */ -const realtime: any = { - io: null, +const realtime: { + onAuthorizeSuccess: (data, accept) => void; + onAuthorizeFail: (data, message, error, accept) => void; + io: SocketIO.Server; isReady: () => boolean; + connection: (socket: SocketWithNoteId) => void; + secure: (socket: SocketIO.Socket, next: (err?: Error) => void) => void; + getStatus: (callback) => void; maintenance: boolean; +} = { + io: SocketIO(), onAuthorizeSuccess: onAuthorizeSuccess, onAuthorizeFail: onAuthorizeFail, secure: secure, @@ -75,39 +83,37 @@ function emitCheck (note: NoteSession): void { realtime.io.to(note.id).emit('check', out) } - - class UserSession { id?: string - address: string - login: any + address?: string + login?: boolean userid: string | null - 'user-agent': any - photo: any + 'user-agent'? + photo: string color: string - cursor: any + cursor?: CodeMirror.Position name: string | null - idle: any - type: any + idle?: boolean + type?: string } class NoteSession { - id: string - alias: string - title: string - owner: string - ownerprofile: any - permission: any - lastchangeuser: string | null - lastchangeuserprofile: any - socks: SocketWithNoteId[] - users: Map - tempUsers: Map // time value - createtime: number - updatetime: number - server: any - authors: Map - authorship: NoteAuthorship[] + id: string + alias: string + title: string + owner: string + ownerprofile: PhotoProfile | null + permission: string + lastchangeuser: string | null + lastchangeuserprofile: PhotoProfile | null + socks: SocketWithNoteId[] + users: Map + tempUsers: Map // time value + createtime: number + updatetime: number + server: EditorSocketIOServer + authors: Map + authorship: NoteAuthorship[] } // actions @@ -149,7 +155,7 @@ function updateNote (note: NoteSession, callback: (err, note) => void): void { }).then(function (_note) { if (!_note) return callback(null, null) // update user note history - const tempUsers = Object.assign({}, note.tempUsers) + const tempUsers = new Map(note.tempUsers) note.tempUsers = new Map() for (const [key, time] of tempUsers) { updateHistory(key, note, time) @@ -247,10 +253,14 @@ function getStatus (callback): void { } } if (!found) { - distinctaddresses.push(user.address) + if (user.address != null) { + distinctaddresses.push(user.address) + } } if (user.login) { - regaddresses.push(user.address) + if (user.address != null) { + regaddresses.push(user.address) + } let found = false for (let i = 0; i < distinctregaddresses.length; i++) { if (user.address === distinctregaddresses[i]) { @@ -259,7 +269,9 @@ function getStatus (callback): void { } } if (!found) { - distinctregaddresses.push(user.address) + if (user.address != null) { + distinctregaddresses.push(user.address) + } } } } @@ -556,7 +568,7 @@ function operationCallback (socket: SocketWithNoteId, operation): void { userId: userId, color: user.color } - }).then(function ([author, _created]) { + }).then(function ([author, _]) { if (author) { note.authors.set(author.userId, { userid: author.userId, @@ -627,17 +639,10 @@ function startConnection (socket: SocketWithNoteId): void { const profile = User.getProfile(author.user) if (profile) { authors.set(author.userId, { - id: '', - address: '', - 'user-agent': '', - login: {}, - cursor: {}, - idle: {}, userid: author.userId, color: author.color, photo: profile.photo, - name: profile.name, - type: {} + name: profile.name }) } } @@ -742,14 +747,14 @@ function disconnect (socket: SocketWithNoteId): void { // clean when user not in any rooms or user not in connected list setInterval(function () { for (const [key, user] of users) { - let socket = realtime.io.sockets.connected[key] + let socket = realtime.io.sockets.connected[key] as SocketWithNoteId if ((!socket && user) || - (socket && (!socket.rooms || socket.rooms.length <= 0))) { + (socket && (!socket.rooms || Object.keys(socket.rooms).length <= 0))) { logger.debug(`cleaner found redundant user: ${key}`) if (!socket) { socket = { id: key - } + } as SocketWithNoteId } disconnectSocketQueue.push(socket) disconnect(socket) @@ -814,13 +819,13 @@ function connection (socket: SocketWithNoteId): void { address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address, 'user-agent': socket.handshake.headers['user-agent'], color: color, - cursor: null, + cursor: undefined, login: false, userid: null, name: null, idle: false, - type: null, - photo: {} + type: '', + photo: '' }) updateUserData(socket, users.get(socket.id)) @@ -976,7 +981,7 @@ function connection (socket: SocketWithNoteId): void { }) // received cursor activity - socket.on('cursor activity', function (data) { + socket.on('cursor activity', function (data: CodeMirror.Position) { const noteId = socket.noteId const user = users.get(socket.id) if (!noteId || !notes.get(noteId) || !user) return @@ -990,7 +995,7 @@ function connection (socket: SocketWithNoteId): void { const noteId = socket.noteId const user = users.get(socket.id) if (!noteId || !notes.get(noteId) || !user) return - user.cursor = null + user.cursor = undefined const out = { id: socket.id } From cb0f5c1bed1d0bd445e6eb1954c931196c626a09 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Mon, 25 May 2020 23:26:45 +0200 Subject: [PATCH 15/19] Update yarn.lock Signed-off-by: David Mehren --- yarn.lock | 73 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/yarn.lock b/yarn.lock index f09fae9f6..8cb6b8da8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -130,6 +130,13 @@ "@types/connect" "*" "@types/node" "*" +"@types/codemirror@^0.0.95": + version "0.0.95" + resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.95.tgz#8fe424989cd5a6d7848f124774d42ef34d91adb8" + integrity sha512-E7w4HS8/rR7Rxkga6j68n3/Mi4BJ870/OJJKRqytyWiM659KnbviSng/NPfM/FOjg7YL+5ruFF69FqoLChnPBw== + dependencies: + "@types/tern" "*" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -152,6 +159,11 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/estree@*": + version "0.0.44" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.44.tgz#980cc5a29a3ef3bea6ff1f7d021047d7ea575e21" + integrity sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g== + "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" @@ -248,6 +260,11 @@ dependencies: "@types/node" "*" +"@types/mocha@^7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" + integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== + "@types/node@*", "@types/node@>=8.0.0", "@types/node@^13.11.1": version "13.11.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7" @@ -392,6 +409,18 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/sinon@^9.0.0": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1" + integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e" + integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== + "@types/socket.io@*": version "2.1.4" resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-2.1.4.tgz#674e7bc193c5ccdadd4433f79f3660d31759e9ac" @@ -409,6 +438,13 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== +"@types/tern@*": + version "0.23.3" + resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.3.tgz#4b54538f04a88c9ff79de1f6f94f575a7f339460" + integrity sha512-imDtS4TAoTcXk0g7u4kkWqedB3E4qpjXzCpD2LU5M5NAXHzCDsypyvXSaG7mM8DKYkCRa7tFp4tS/lp/Wo7Q3w== + dependencies: + "@types/estree" "*" + "@types/tunnel@0.0.0": version "0.0.0" resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.0.tgz#c2a42943ee63c90652a5557b8c4e56cda77f944e" @@ -4781,11 +4817,6 @@ generate-function@^2.3.1: dependencies: is-property "^1.0.2" -get-caller-file@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -6177,10 +6208,10 @@ ldap-filter@0.2.2: dependencies: assert-plus "0.1.5" -ldapauth-fork@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ldapauth-fork/-/ldapauth-fork-4.3.2.tgz#bc408f0d4fb8d07e5e9a7287c7310fdceea226f9" - integrity sha512-XaO/kLaY9XGH/O58qTgtGtS0u7Qfq9IMDYWBvjRDuNfh7PbqOA5JXwF7DeKW6kIWQ842fGoIpB/cZmJ0SaJFbQ== +ldapauth-fork@^4.3.0, ldapauth-fork@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/ldapauth-fork/-/ldapauth-fork-4.3.3.tgz#d62c8f18a5035fd47a572f2ac7aa8c8227b3f4c2" + integrity sha512-x76VpQ5ZqkwAJmqwcD6KIwDiNEbgIGIPGwC/eA17e1dxWhlTx36w0DlLOFwjTuZ2iuaLTsZsUprlVqvSlwc/1Q== dependencies: "@types/ldapjs" "^1.0.0" "@types/node" "*" @@ -7009,14 +7040,6 @@ mocha@^5.2.0: mkdirp "0.5.1" supports-color "5.4.0" -mock-require@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/mock-require/-/mock-require-3.0.3.tgz#ccd544d9eae81dd576b3f219f69ec867318a1946" - integrity sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg== - dependencies: - get-caller-file "^1.0.2" - normalize-path "^2.1.1" - moment-mini@^2.22.1: version "2.24.0" resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18" @@ -9520,7 +9543,7 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -sinon@^9.0.1: +sinon@^9.0.1, sinon@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d" integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A== @@ -9755,14 +9778,13 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sqlite3@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.1.1.tgz#539a42e476640796578e22d589b3283c28055242" - integrity sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg== +sqlite3@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.2.0.tgz#49026d665e9fc4f922e56fb9711ba5b4c85c4901" + integrity sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg== dependencies: nan "^2.12.1" node-pre-gyp "^0.11.0" - request "^2.87.0" sqlstring@^2.3.1: version "2.3.1" @@ -10343,6 +10365,11 @@ try-to-catch@^1.0.2: resolved "https://registry.yarnpkg.com/try-to-catch/-/try-to-catch-1.1.1.tgz#770162dd13b9a0e55da04db5b7f888956072038a" integrity sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA== +ts-mock-imports@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-mock-imports/-/ts-mock-imports-1.3.0.tgz#ed9b743349f3c27346afe5b7454ffd2bcaa2302d" + integrity sha512-cCrVcRYsp84eDvPict0ZZD/D7ppQ0/JSx4ve6aEU8DjlsaWRJWV6ADMovp2sCuh6pZcduLFoIYhKTDU2LARo7Q== + ts-node@^8.8.2: version "8.8.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f" From 5c4820483c4c249a74977b626b79a511269e58f8 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 31 May 2020 21:38:42 +0200 Subject: [PATCH 16/19] realtime.ts: Fix bug in emitOnlineUsers() We incorrectly iterated over Map.keys() instead of Map.values() Signed-off-by: David Mehren --- src/lib/realtime.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts index 5b733d934..98ab8c895 100644 --- a/src/lib/realtime.ts +++ b/src/lib/realtime.ts @@ -349,8 +349,7 @@ function emitOnlineUsers (socket: SocketWithNoteId): void { const note = notes.get(noteId) if (!note) return const users: UserSession[] = [] - // Object.keys(note.users).forEach(function (key) { - for (const user of note.users.keys()) { + for (const user of note.users.values()) { if (user) { users.push(buildUserOutData(user)) } From d2963eedc638ffbe3e32c679a9780154bb686c95 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 31 May 2020 21:40:05 +0200 Subject: [PATCH 17/19] realtime.ts: Fix bug in user-color setup The code was incorrectly migrated from JavaScript set colors in the wrong way. Signed-off-by: David Mehren --- src/lib/realtime.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts index 98ab8c895..2b3ca9210 100644 --- a/src/lib/realtime.ts +++ b/src/lib/realtime.ts @@ -467,16 +467,21 @@ function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: s return failConnection(403, 'connection forbidden', socket) } const user = users.get(socketId) - if (!user || !user.userid) return - // update user color to author 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) + if (!user) { + logger.warn('Could not find user for socketId ' + socketId) + return + } + if (user.userid) { + // update user color to author color + const author = note.authors.get(user.userid) + if (author) { + const socketIdUser = users.get(socket.id) + if (!socketIdUser) return + user.color = author.color + socketIdUser.color = author.color + users.set(socket.id, user) + } } - note.users.set(socket.id, user) note.socks.push(socket) note.server.addClient(socket) From 17f3dc18770350d23a01205f97409787aaf3bcdd Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 31 May 2020 21:40:35 +0200 Subject: [PATCH 18/19] realtime.ts: Minor cleanups Signed-off-by: David Mehren --- src/lib/realtime.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts index 2b3ca9210..edacde520 100644 --- a/src/lib/realtime.ts +++ b/src/lib/realtime.ts @@ -242,7 +242,6 @@ function getStatus (callback): void { const distinctaddresses: string[] = [] const regaddresses: string[] = [] const distinctregaddresses: string[] = [] - // Object.keys(users).forEach(function (key) { for (const user of users.values()) { if (!user) return let found = false @@ -331,8 +330,6 @@ function buildUserOutData (user): UserSession { return { id: user.id, login: user.login, - address: '', - 'user-agent': '', userid: user.userid, photo: user.photo, color: user.color, From 908bf36fa0f8fba21d383191b82fabf21802a86f Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 31 May 2020 21:43:14 +0200 Subject: [PATCH 19/19] Make authorships show up again It turns out our shiny new typed ES2015 `Map`s are not serializable to JSON. :( Luckily, we only use strings as keys and can write a function that converts them to serializable objects! Signed-off-by: David Mehren --- src/lib/realtime.ts | 5 +++-- src/lib/utils.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts index edacde520..f5eff89b1 100644 --- a/src/lib/realtime.ts +++ b/src/lib/realtime.ts @@ -13,6 +13,7 @@ import { Author, Note, Revision, User } from './models' import { NoteAuthorship } from './models/note' import { PhotoProfile } from './models/user' import { EditorSocketIOServer } from './ot/editor-socketio-server' +import { mapToObject } from './utils' export type SocketWithNoteId = Socket & { noteId: string } @@ -77,7 +78,7 @@ function emitCheck (note: NoteSession): void { updatetime: note.updatetime, lastchangeuser: note.lastchangeuser, lastchangeuserprofile: note.lastchangeuserprofile, - authors: note.authors, + authors: mapToObject(note.authors), authorship: note.authorship } realtime.io.to(note.id).emit('check', out) @@ -380,7 +381,7 @@ function emitRefresh (socket: SocketWithNoteId): void { ownerprofile: note.ownerprofile, lastchangeuser: note.lastchangeuser, lastchangeuserprofile: note.lastchangeuserprofile, - authors: note.authors, + authors: mapToObject(note.authors), authorship: note.authorship, permission: note.permission, createtime: note.createtime, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a4223666c..8509a1cef 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,6 +4,16 @@ import { logger } from './logger' import { Revision } from './models' import { realtime } from './realtime' +/* +Converts a map from string to something into a plain JS object for transmitting via a websocket + */ +export function mapToObject (map: Map): object { + return Array.from(map).reduce((obj, [key, value]) => { + obj[key] = value + return obj + }, {}) +} + export function getImageMimeType (imagePath: string): string | undefined { const fileExtension = /[^.]+$/.exec(imagePath) switch (fileExtension?.[0]) {