Merge pull request #369 from davidmehren/make-eslint-happy-again

This commit is contained in:
David Mehren 2020-06-01 20:19:37 +02:00 committed by GitHub
commit d8265e4085
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 357 additions and 256 deletions

View file

@ -1,4 +1,5 @@
lib/ot
src/lib/ot
src/lib/migrations
public/vendor
public/build
node_modules

View file

@ -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",

View file

@ -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;
}

View file

@ -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()))
}

View file

@ -6,15 +6,16 @@ import LZString from 'lz-string'
import { logger } from './logger'
import { Note, User } from './models'
import { errors } from './errors'
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<string, HistoryObject>): HistoryObject[] {
@ -34,7 +35,7 @@ function parseHistoryArrayToMap (historyArray: HistoryObject[]): Map<string, His
return historyMap
}
function getHistory (userId, callback: (err: any, history: any) => void): void {
function getHistory (userId, callback: (err: unknown, history: Map<string, HistoryObject> | 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<string, HistoryObject>())
}).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)
}, {
@ -100,16 +101,14 @@ 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
setHistory(userId, history, function (err, _) {
history.set(noteId, noteHistory)
setHistory(userId, parseHistoryMapToArray(history), function (err, _) {
if (err) {
logger.log(err)
}
@ -118,7 +117,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 +131,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) {
@ -157,10 +156,11 @@ function historyPost (req, res): any {
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')
setHistory(req.user.id, history, function (err, _) {
noteHistory.pinned = (req.body.pinned === 'true')
setHistory(req.user.id, parseHistoryMapToArray(history), function (err, _) {
if (err) return errors.errorInternalError(res)
res.end()
})
@ -174,7 +174,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) {
@ -186,8 +186,8 @@ function historyDelete (req, res): any {
getHistory(req.user.id, function (err, history) {
if (err) return errors.errorInternalError(res)
if (!history) return errors.errorNotFound(res)
delete history[noteId]
setHistory(req.user.id, history, function (err, _) {
history.delete(noteId)
setHistory(req.user.id, parseHistoryMapToArray(history), function (err, _) {
if (err) return errors.errorInternalError(res)
res.end()
})

View file

@ -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;
}
}

View file

@ -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<Note> {
@PrimaryKey
@ -130,17 +138,18 @@ export class Note extends Model<Note> {
}
@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<Note> {
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<Note> {
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<Note> {
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<Note> {
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++) {

View file

@ -40,7 +40,7 @@ export type Profile = {
avatarUrl: string;
profileUrl: string;
provider: ProviderEnum;
photos: any[];
photos: { value: string }[];
}
export type PhotoProfile = {

View file

@ -1,24 +1,34 @@
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 { 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 }
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,
@ -40,7 +50,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) {
@ -62,25 +72,58 @@ function secure (socket: Socket, next: (err?: any) => void): void {
}
}
function emitCheck (note): void {
function emitCheck (note: NoteSession): void {
const out = {
title: note.title,
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)
}
class UserSession {
id?: string
address?: string
login?: boolean
userid: string | null
'user-agent'?
photo: string
color: string
cursor?: CodeMirror.Position
name: string | null
idle?: boolean
type?: string
}
class NoteSession {
id: string
alias: string
title: string
owner: string
ownerprofile: PhotoProfile | null
permission: string
lastchangeuser: string | null
lastchangeuserprofile: PhotoProfile | null
socks: SocketWithNoteId[]
users: Map<string, UserSession>
tempUsers: Map<string, number> // time value
createtime: number
updatetime: number
server: EditorSocketIOServer
authors: Map<string, UserSession>
authorship: NoteAuthorship[]
}
// actions
const users = {}
const notes = {}
const users: Map<string, UserSession> = new Map<string, UserSession>()
const notes: Map<string, NoteSession> = new Map<string, 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 +143,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
@ -113,11 +156,11 @@ function updateNote (note: any, callback: (err, note) => any): any {
}).then(function (_note) {
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])
})
const tempUsers = new Map(note.tempUsers)
note.tempUsers = new Map<string, number>()
for (const [key, time] of tempUsers) {
updateHistory(key, note, time)
}
if (note.lastchangeuser) {
if (_note.lastchangeuserId !== note.lastchangeuser) {
User.findOne({
@ -147,14 +190,13 @@ function updateNote (note: any, callback: (err, note) => any): any {
// 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
@ -162,26 +204,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
@ -200,26 +238,29 @@ let isDisconnectBusy: boolean
const connectionSocketQueue: SocketWithNoteId[] = []
function getStatus (callback) {
function getStatus (callback): void {
Note.count().then(function (notecount) {
const distinctaddresses: string[] = []
const regaddresses: string[] = []
const distinctregaddresses: string[] = []
Object.keys(users).forEach(function (key) {
const user = users[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
}
}
if (!found) {
if (user.address != null) {
distinctaddresses.push(user.address)
}
}
if (user.login) {
if (user.address != null) {
regaddresses.push(user.address)
}
let found = false
for (let i = 0; i < distinctregaddresses.length; i++) {
if (user.address === distinctregaddresses[i]) {
@ -228,10 +269,12 @@ function getStatus (callback) {
}
}
if (!found) {
if (user.address != null) {
distinctregaddresses.push(user.address)
}
}
})
}
}
User.count().then(function (regcount) {
// eslint-disable-next-line standard/no-callback-literal
return callback ? callback({
@ -262,7 +305,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 +316,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 +327,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,19 +339,19 @@ 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[] = []
Object.keys(notes[noteId].users).forEach(function (key) {
const user = notes[noteId].users[key]
if (!noteId) return
const note = notes.get(noteId)
if (!note) return
const users: UserSession[] = []
for (const user of note.users.values()) {
if (user) {
users.push(buildUserOutData(user))
}
})
}
const out = {
users: users
}
@ -317,16 +360,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,
@ -334,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,
@ -344,8 +391,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
}
}
@ -385,8 +432,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 {
@ -395,7 +442,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') {
@ -407,21 +454,33 @@ 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]
// update user color to author color
if (note.authors[user.userid]) {
user.color = users[socket.id].color = note.authors[user.userid].color
const user = users.get(socketId)
if (!user) {
logger.warn('Could not find user for socketId ' + socketId)
return
}
note.users[socket.id] = user
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)
note.server.setName(socket, user.name)
@ -451,8 +510,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':
@ -488,15 +548,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,
@ -507,20 +570,20 @@ function operationCallback (socket: SocketWithNoteId, operation): void {
userId: userId,
color: user.color
}
}).then(function ([author, created]) {
}).then(function ([author, _]) {
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 () {
@ -537,7 +600,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'
@ -573,21 +636,20 @@ 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<string, UserSession>()
for (const author of note.authors) {
const profile = User.getProfile(author.user)
if (profile) {
authors[author.userId] = {
authors.set(author.userId, {
userid: author.userId,
color: author.color,
photo: profile.photo,
name: profile.name
}
})
}
}
notes[noteId] = {
notes.set(noteId, {
id: noteId,
alias: note.alias,
title: note.title,
@ -597,14 +659,14 @@ function startConnection (socket: SocketWithNoteId): void {
lastchangeuser: lastchangeuser,
lastchangeuserprofile: lastchangeuserprofile,
socks: [],
users: {},
tempUsers: {},
users: new Map<string, UserSession>(),
tempUsers: new Map<string, number>(),
createtime: moment(createtime).valueOf(),
updatetime: moment(updatetime).valueOf(),
server: server,
authors: authors,
authorship: note.authorship
}
})
return finishConnection(socket, noteId, socket.id)
}).catch(function (err) {
@ -623,17 +685,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
@ -644,15 +706,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) {
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) {
@ -662,7 +724,7 @@ function disconnect (socket: SocketWithNoteId): void {
})
} else {
delete note.server
delete notes[noteId]
notes.delete(noteId)
}
}
}
@ -686,23 +748,20 @@ 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) {
let socket = realtime.io.sockets.connected[key]
if ((!socket && users[key]) ||
(socket && (!socket.rooms || socket.rooms.length <= 0))) {
for (const [key, user] of users) {
let socket = realtime.io.sockets.connected[key] as SocketWithNoteId
if ((!socket && user) ||
(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)
}
return callback(null, null)
}, function (err) {
if (err) return logger.error('cleaner error', err)
})
}
}, 60000)
function updateUserData (socket: Socket, user): void {
@ -739,16 +798,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++
@ -756,19 +816,20 @@ 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'],
color: color,
cursor: null,
cursor: undefined,
login: false,
userid: null,
name: null,
idle: false,
type: null
}
updateUserData(socket, users[socket.id])
type: '',
photo: ''
})
updateUserData(socket, users.get(socket.id))
// start connection
connectionSocketQueue.push(socket)
@ -783,8 +844,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
@ -798,8 +859,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
@ -844,8 +906,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({
@ -874,8 +937,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)
@ -884,14 +949,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
const users: any = []
Object.keys(notes[noteId].users).forEach(function (key) {
const user = notes[noteId].users[key]
if (!noteId) return
const note = notes.get(noteId)
if (!note) return
const users: UserSession[] = []
for (const user of note.users.values()) {
if (user) {
users.push(buildUserOutData(user))
}
})
}
const out = {
users: users
}
@ -909,18 +975,18 @@ 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)
})
// received cursor activity
socket.on('cursor activity', function (data) {
socket.on('cursor activity', function (data: CodeMirror.Position) {
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)
@ -929,9 +995,9 @@ 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
user.cursor = null
const user = users.get(socket.id)
if (!noteId || !notes.get(noteId) || !user) return
user.cursor = undefined
const out = {
id: socket.id
}

View file

@ -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<T> (map: Map<string, T>): 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]) {

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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)

View file

@ -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))

View file

@ -1,12 +1,12 @@
import { NextFunction, Request, 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 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,14 +23,7 @@ export function publishNoteActions (req: any, res: Response, next: NextFunction)
})
}
export function showPublishNote (req: any, res: Response, next: NextFunction) {
const include = [{
model: User,
as: 'owner'
}, {
model: User,
as: 'lastchangeuser'
}]
export function showPublishNote (req: Request, res: Response): void {
NoteUtils.findNoteOrCreate(req, res, function (note) {
// force to use short id
const shortid = req.params.shortid
@ -51,10 +44,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 +72,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 +83,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 +114,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)

View file

@ -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, next: NextFunction) {
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, next: NextFunction
})
}
export function showPublishSlide (req: any, res: Response, next: NextFunction) {
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
@ -44,5 +37,5 @@ export function showPublishSlide (req: any, res: Response, next: NextFunction) {
logger.error(err)
return errors.errorInternalError(res)
})
}, include)
})
}

View file

@ -1,14 +1,13 @@
import { Response } from 'express'
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'
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 +32,7 @@ export function newNote (req: any, res: Response, body: string | null) {
})
}
export function checkViewPermission (req: any, note: any) {
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') {
@ -43,7 +42,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 +69,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

View file

@ -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[]

View file

@ -9,7 +9,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true,
"lib": ["ES2019"],
"lib": ["ES2019", "DOM"],
"alwaysStrict": true
},
"include": ["src"]

View file

@ -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"