mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-10-19 13:50:15 -04:00
Merge pull request #369 from davidmehren/make-eslint-happy-again
This commit is contained in:
commit
d8265e4085
21 changed files with 357 additions and 256 deletions
|
@ -1,4 +1,5 @@
|
||||||
lib/ot
|
src/lib/ot
|
||||||
|
src/lib/migrations
|
||||||
public/vendor
|
public/vendor
|
||||||
public/build
|
public/build
|
||||||
node_modules
|
node_modules
|
||||||
|
|
|
@ -170,6 +170,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/archiver": "^3.1.0",
|
"@types/archiver": "^3.1.0",
|
||||||
"@types/bluebird": "^3.5.30",
|
"@types/bluebird": "^3.5.30",
|
||||||
|
"@types/codemirror": "^0.0.95",
|
||||||
"@types/diff-match-patch": "^1.0.32",
|
"@types/diff-match-patch": "^1.0.32",
|
||||||
"@types/express": "^4.17.6",
|
"@types/express": "^4.17.6",
|
||||||
"@types/helmet": "^0.0.45",
|
"@types/helmet": "^0.0.45",
|
||||||
|
|
|
@ -36,7 +36,7 @@ export interface Config {
|
||||||
forbiddenNoteIDs: string[];
|
forbiddenNoteIDs: string[];
|
||||||
defaultPermission: string;
|
defaultPermission: string;
|
||||||
dbURL: string;
|
dbURL: string;
|
||||||
db: any;
|
db;
|
||||||
sslKeyPath: string;
|
sslKeyPath: string;
|
||||||
sslCertPath: string;
|
sslCertPath: string;
|
||||||
sslCAPath: string[];
|
sslCAPath: string[];
|
||||||
|
@ -153,5 +153,6 @@ export interface Config {
|
||||||
linkifyHeaderStyle: string;
|
linkifyHeaderStyle: string;
|
||||||
|
|
||||||
// TODO: Remove escape hatch for dynamically added properties
|
// TODO: Remove escape hatch for dynamically added properties
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ export function toBooleanConfig (configValue: string | boolean | undefined): boo
|
||||||
return configValue
|
return configValue
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toArrayConfig (configValue: string | undefined, separator = ',', fallback = []): any[] {
|
export function toArrayConfig (configValue: string | undefined, separator = ',', fallback = []): string[] {
|
||||||
if (configValue) {
|
if (configValue) {
|
||||||
return (configValue.split(separator).map(arrayItem => arrayItem.trim()))
|
return (configValue.split(separator).map(arrayItem => arrayItem.trim()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,16 @@ import LZString from 'lz-string'
|
||||||
import { logger } from './logger'
|
import { logger } from './logger'
|
||||||
import { Note, User } from './models'
|
import { Note, User } from './models'
|
||||||
import { errors } from './errors'
|
import { errors } from './errors'
|
||||||
|
import { LogEntry } from 'winston'
|
||||||
|
|
||||||
// public
|
// public
|
||||||
|
|
||||||
type HistoryObject = {
|
class HistoryObject {
|
||||||
id: string;
|
id: string
|
||||||
text: string;
|
text: string
|
||||||
time: number;
|
time: number
|
||||||
tags: string[];
|
tags: string[]
|
||||||
pinned?: boolean;
|
pinned?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseHistoryMapToArray (historyMap: Map<string, HistoryObject>): HistoryObject[] {
|
function parseHistoryMapToArray (historyMap: Map<string, HistoryObject>): HistoryObject[] {
|
||||||
|
@ -34,7 +35,7 @@ function parseHistoryArrayToMap (historyArray: HistoryObject[]): Map<string, His
|
||||||
return historyMap
|
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({
|
User.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: userId
|
id: userId
|
||||||
|
@ -44,7 +45,7 @@ function getHistory (userId, callback: (err: any, history: any) => void): void {
|
||||||
return callback(null, null)
|
return callback(null, null)
|
||||||
}
|
}
|
||||||
if (user.history) {
|
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
|
// migrate LZString encoded note id to base64url encoded note id
|
||||||
for (let i = 0, l = history.length; i < l; i++) {
|
for (let i = 0, l = history.length; i < l; i++) {
|
||||||
// Calculate minimal string length for an UUID that is encoded
|
// 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))
|
return callback(null, parseHistoryArrayToMap(history))
|
||||||
}
|
}
|
||||||
logger.debug(`read empty history: ${user.id}`)
|
logger.debug(`read empty history: ${user.id}`)
|
||||||
return callback(null, [])
|
return callback(null, new Map<string, HistoryObject>())
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
logger.error('read history failed: ' + err)
|
logger.error('read history failed: ' + err)
|
||||||
return callback(err, null)
|
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({
|
User.update({
|
||||||
history: JSON.stringify(history)
|
history: JSON.stringify(history)
|
||||||
}, {
|
}, {
|
||||||
|
@ -100,16 +101,14 @@ function updateHistory (userId: string, noteId: string, document, time): void {
|
||||||
if (userId && noteId && typeof document !== 'undefined') {
|
if (userId && noteId && typeof document !== 'undefined') {
|
||||||
getHistory(userId, function (err, history) {
|
getHistory(userId, function (err, history) {
|
||||||
if (err || !history) return
|
if (err || !history) return
|
||||||
if (!history[noteId]) {
|
const noteHistory = history.get(noteId) || new HistoryObject()
|
||||||
history[noteId] = {}
|
|
||||||
}
|
|
||||||
const noteHistory = history[noteId]
|
|
||||||
const noteInfo = Note.parseNoteInfo(document)
|
const noteInfo = Note.parseNoteInfo(document)
|
||||||
noteHistory.id = noteId
|
noteHistory.id = noteId
|
||||||
noteHistory.text = noteInfo.title
|
noteHistory.text = noteInfo.title
|
||||||
noteHistory.time = time || Date.now()
|
noteHistory.time = time || Date.now()
|
||||||
noteHistory.tags = noteInfo.tags
|
noteHistory.tags = noteInfo.tags
|
||||||
setHistory(userId, history, function (err, _) {
|
history.set(noteId, noteHistory)
|
||||||
|
setHistory(userId, parseHistoryMapToArray(history), function (err, _) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.log(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()) {
|
if (req.isAuthenticated()) {
|
||||||
getHistory(req.user.id, function (err, history) {
|
getHistory(req.user.id, function (err, history) {
|
||||||
if (err) return errors.errorInternalError(res)
|
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()) {
|
if (req.isAuthenticated()) {
|
||||||
const noteId = req.params.noteId
|
const noteId = req.params.noteId
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
|
@ -157,10 +156,11 @@ function historyPost (req, res): any {
|
||||||
getHistory(req.user.id, function (err, history) {
|
getHistory(req.user.id, function (err, history) {
|
||||||
if (err) return errors.errorInternalError(res)
|
if (err) return errors.errorInternalError(res)
|
||||||
if (!history) return errors.errorNotFound(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') {
|
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, history, function (err, _) {
|
setHistory(req.user.id, parseHistoryMapToArray(history), function (err, _) {
|
||||||
if (err) return errors.errorInternalError(res)
|
if (err) return errors.errorInternalError(res)
|
||||||
res.end()
|
res.end()
|
||||||
})
|
})
|
||||||
|
@ -174,7 +174,7 @@ function historyPost (req, res): any {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function historyDelete (req, res): any {
|
function historyDelete (req, res): void {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
const noteId = req.params.noteId
|
const noteId = req.params.noteId
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
|
@ -186,8 +186,8 @@ function historyDelete (req, res): any {
|
||||||
getHistory(req.user.id, function (err, history) {
|
getHistory(req.user.id, function (err, history) {
|
||||||
if (err) return errors.errorInternalError(res)
|
if (err) return errors.errorInternalError(res)
|
||||||
if (!history) return errors.errorNotFound(res)
|
if (!history) return errors.errorNotFound(res)
|
||||||
delete history[noteId]
|
history.delete(noteId)
|
||||||
setHistory(req.user.id, history, function (err, _) {
|
setHistory(req.user.id, parseHistoryMapToArray(history), function (err, _) {
|
||||||
if (err) return errors.errorInternalError(res)
|
if (err) return errors.errorInternalError(res)
|
||||||
res.end()
|
res.end()
|
||||||
})
|
})
|
||||||
|
|
2
src/lib/library-ext.d.ts
vendored
2
src/lib/library-ext.d.ts
vendored
|
@ -3,6 +3,6 @@ import { User } from './models'
|
||||||
declare module 'express' {
|
declare module 'express' {
|
||||||
export interface Request {
|
export interface Request {
|
||||||
user?: User;
|
user?: User;
|
||||||
flash (type: string, msg?: string): any;
|
flash (type: string, msg?: string): [] | object | number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
import {
|
||||||
AfterCreate,
|
AfterCreate,
|
||||||
AllowNull,
|
AllowNull,
|
||||||
|
@ -15,24 +27,12 @@ import {
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
|
|
||||||
import { generate as shortIdGenerate, isValid as shortIdIsValid } from 'shortid'
|
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 S from 'string'
|
||||||
import { config } from '../config'
|
import { config } from '../config'
|
||||||
import { logger } from '../logger'
|
import { logger } from '../logger'
|
||||||
import ot from '../ot'
|
import ot from '../ot'
|
||||||
|
import { processData, stripNullByte } from '../utils'
|
||||||
|
import { Author, Revision, User } from './index'
|
||||||
|
|
||||||
const md = markdownIt()
|
const md = markdownIt()
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
|
@ -48,16 +48,24 @@ enum PermissionEnum {
|
||||||
private = 'private'
|
private = 'private'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class OpengraphMetadata {
|
||||||
|
title: string | number
|
||||||
|
description: string | number
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
export class NoteMetadata {
|
export class NoteMetadata {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
robots: string
|
robots: string
|
||||||
GA: string
|
GA: string
|
||||||
disqus: string
|
disqus: string
|
||||||
slideOptions: any
|
slideOptions
|
||||||
opengraph: any
|
opengraph: OpengraphMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NoteAuthorship = [string, number, number, number, number]
|
||||||
|
|
||||||
@Table({ paranoid: false })
|
@Table({ paranoid: false })
|
||||||
export class Note extends Model<Note> {
|
export class Note extends Model<Note> {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
|
@ -130,17 +138,18 @@ export class Note extends Model<Note> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Column(DataType.TEXT({ length: 'long' }))
|
@Column(DataType.TEXT({ length: 'long' }))
|
||||||
get authorship (): string {
|
get authorship (): NoteAuthorship[] {
|
||||||
return processData(this.getDataValue('authorship'), [], JSON.parse)
|
return processData(this.getDataValue('authorship'), [], JSON.parse)
|
||||||
}
|
}
|
||||||
|
|
||||||
set authorship (value: string) {
|
set authorship (value: NoteAuthorship[]) {
|
||||||
this.setDataValue('authorship', JSON.stringify(value))
|
// 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
|
@BeforeCreate
|
||||||
static async defaultContentAndPermissions (note: Note): Promise<Note> {
|
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 no content specified then use default note
|
||||||
if (!note.content) {
|
if (!note.content) {
|
||||||
let filePath: string
|
let filePath: string
|
||||||
|
@ -430,7 +439,7 @@ export class Note extends Model<Note> {
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
static extractMeta (content): any {
|
static extractMeta (content): { markdown: string; meta: {} } {
|
||||||
try {
|
try {
|
||||||
const obj = metaMarked(content)
|
const obj = metaMarked(content)
|
||||||
if (!obj.markdown) obj.markdown = ''
|
if (!obj.markdown) obj.markdown = ''
|
||||||
|
@ -472,8 +481,8 @@ export class Note extends Model<Note> {
|
||||||
return _meta
|
return _meta
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseOpengraph (meta, title): any {
|
static parseOpengraph (meta, title: string): OpengraphMetadata {
|
||||||
let _ogdata: any = {}
|
let _ogdata = new OpengraphMetadata()
|
||||||
if (meta.opengraph) {
|
if (meta.opengraph) {
|
||||||
_ogdata = meta.opengraph
|
_ogdata = meta.opengraph
|
||||||
}
|
}
|
||||||
|
@ -489,7 +498,7 @@ export class Note extends Model<Note> {
|
||||||
return _ogdata
|
return _ogdata
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateAuthorshipByOperation (operation, userId: string|null, authorships): any {
|
static updateAuthorshipByOperation (operation, userId: string | null, authorships): NoteAuthorship[] {
|
||||||
let index = 0
|
let index = 0
|
||||||
const timestamp = Date.now()
|
const timestamp = Date.now()
|
||||||
for (let i = 0; i < operation.length; i++) {
|
for (let i = 0; i < operation.length; i++) {
|
||||||
|
|
|
@ -40,7 +40,7 @@ export type Profile = {
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
profileUrl: string;
|
profileUrl: string;
|
||||||
provider: ProviderEnum;
|
provider: ProviderEnum;
|
||||||
photos: any[];
|
photos: { value: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PhotoProfile = {
|
export type PhotoProfile = {
|
||||||
|
|
|
@ -1,24 +1,34 @@
|
||||||
import async from 'async'
|
|
||||||
import Chance from 'chance'
|
import Chance from 'chance'
|
||||||
|
import CodeMirror from 'codemirror'
|
||||||
import cookie from 'cookie'
|
import cookie from 'cookie'
|
||||||
import cookieParser from 'cookie-parser'
|
import cookieParser from 'cookie-parser'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import randomcolor from 'randomcolor'
|
import randomcolor from 'randomcolor'
|
||||||
import { Socket } from 'socket.io'
|
import SocketIO, { Socket } from 'socket.io'
|
||||||
import { config } from './config'
|
import { config } from './config'
|
||||||
|
|
||||||
import { History } from './history'
|
import { History } from './history'
|
||||||
import { logger } from './logger'
|
import { logger } from './logger'
|
||||||
import { Author, Note, Revision, User } from './models'
|
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 { EditorSocketIOServer } from './ot/editor-socketio-server'
|
||||||
|
import { mapToObject } from './utils'
|
||||||
|
|
||||||
export type SocketWithNoteId = Socket & { noteId: string }
|
export type SocketWithNoteId = Socket & { noteId: string }
|
||||||
|
|
||||||
const chance = new Chance()
|
const chance = new Chance()
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
const realtime: any = {
|
const realtime: {
|
||||||
io: null,
|
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,
|
onAuthorizeSuccess: onAuthorizeSuccess,
|
||||||
onAuthorizeFail: onAuthorizeFail,
|
onAuthorizeFail: onAuthorizeFail,
|
||||||
secure: secure,
|
secure: secure,
|
||||||
|
@ -40,7 +50,7 @@ function onAuthorizeFail (data, message, error, accept): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// secure the origin by the cookie
|
// 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 {
|
try {
|
||||||
const handshakeData = socket.request
|
const handshakeData = socket.request
|
||||||
if (handshakeData.headers.cookie) {
|
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 = {
|
const out = {
|
||||||
title: note.title,
|
title: note.title,
|
||||||
updatetime: note.updatetime,
|
updatetime: note.updatetime,
|
||||||
lastchangeuser: note.lastchangeuser,
|
lastchangeuser: note.lastchangeuser,
|
||||||
lastchangeuserprofile: note.lastchangeuserprofile,
|
lastchangeuserprofile: note.lastchangeuserprofile,
|
||||||
authors: note.authors,
|
authors: mapToObject(note.authors),
|
||||||
authorship: note.authorship
|
authorship: note.authorship
|
||||||
}
|
}
|
||||||
realtime.io.to(note.id).emit('check', out)
|
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
|
// actions
|
||||||
const users = {}
|
const users: Map<string, UserSession> = new Map<string, UserSession>()
|
||||||
const notes = {}
|
const notes: Map<string, NoteSession> = new Map<string, NoteSession>()
|
||||||
|
|
||||||
let saverSleep = false
|
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)
|
if (!note || !note.server) return callback(null, null)
|
||||||
const body = note.server.document
|
const body = note.server.document
|
||||||
const title = note.title = Note.parseNoteTitle(body)
|
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)
|
const noteId = note.alias ? note.alias : Note.encodeNoteId(note.id)
|
||||||
if (note.server) History.updateHistory(userId, noteId, note.server.document, time)
|
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({
|
Note.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: note.id
|
id: note.id
|
||||||
|
@ -113,11 +156,11 @@ function updateNote (note: any, callback: (err, note) => any): any {
|
||||||
}).then(function (_note) {
|
}).then(function (_note) {
|
||||||
if (!_note) return callback(null, null)
|
if (!_note) return callback(null, null)
|
||||||
// update user note history
|
// update user note history
|
||||||
const tempUsers = Object.assign({}, note.tempUsers)
|
const tempUsers = new Map(note.tempUsers)
|
||||||
note.tempUsers = {}
|
note.tempUsers = new Map<string, number>()
|
||||||
Object.keys(tempUsers).forEach(function (key) {
|
for (const [key, time] of tempUsers) {
|
||||||
updateHistory(key, note, tempUsers[key])
|
updateHistory(key, note, time)
|
||||||
})
|
}
|
||||||
if (note.lastchangeuser) {
|
if (note.lastchangeuser) {
|
||||||
if (_note.lastchangeuserId !== note.lastchangeuser) {
|
if (_note.lastchangeuserId !== note.lastchangeuser) {
|
||||||
User.findOne({
|
User.findOne({
|
||||||
|
@ -147,14 +190,13 @@ function updateNote (note: any, callback: (err, note) => any): any {
|
||||||
|
|
||||||
// update when the note is dirty
|
// update when the note is dirty
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
async.each(Object.keys(notes), function (key, callback) {
|
for (const [key, note] of notes) {
|
||||||
const note = notes[key]
|
|
||||||
if (note.server.isDirty) {
|
if (note.server.isDirty) {
|
||||||
logger.debug(`updater found dirty note: ${key}`)
|
logger.debug(`updater found dirty note: ${key}`)
|
||||||
note.server.isDirty = false
|
note.server.isDirty = false
|
||||||
updateNote(note, function (err, _note) {
|
updateNote(note, function (err, _note) {
|
||||||
// handle when note already been clean up
|
// handle when note already been clean up
|
||||||
if (!notes[key] || !notes[key].server) return callback(null, null)
|
if (!note || !note.server) return
|
||||||
if (!_note) {
|
if (!_note) {
|
||||||
realtime.io.to(note.id).emit('info', {
|
realtime.io.to(note.id).emit('info', {
|
||||||
code: 404
|
code: 404
|
||||||
|
@ -162,26 +204,22 @@ setInterval(function () {
|
||||||
logger.error('note not found: ', note.id)
|
logger.error('note not found: ', note.id)
|
||||||
}
|
}
|
||||||
if (err || !_note) {
|
if (err || !_note) {
|
||||||
for (let i = 0, l = note.socks.length; i < l; i++) {
|
for (const sock of note.socks) {
|
||||||
const sock = note.socks[i]
|
if (sock) {
|
||||||
if (typeof sock !== 'undefined' && sock) {
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
sock.disconnect()
|
sock.disconnect()
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return callback(err, null)
|
return logger.error('updater error', err)
|
||||||
}
|
}
|
||||||
note.updatetime = moment(_note.lastchangeAt).valueOf()
|
note.updatetime = moment(_note.lastchangeAt).valueOf()
|
||||||
emitCheck(note)
|
emitCheck(note)
|
||||||
return callback(null, null)
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return callback(null, null)
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, function (err) {
|
|
||||||
if (err) return logger.error('updater error', err)
|
|
||||||
})
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
// save note revision in interval
|
// save note revision in interval
|
||||||
|
@ -200,26 +238,29 @@ let isDisconnectBusy: boolean
|
||||||
|
|
||||||
const connectionSocketQueue: SocketWithNoteId[] = []
|
const connectionSocketQueue: SocketWithNoteId[] = []
|
||||||
|
|
||||||
function getStatus (callback) {
|
function getStatus (callback): void {
|
||||||
Note.count().then(function (notecount) {
|
Note.count().then(function (notecount) {
|
||||||
const distinctaddresses: string[] = []
|
const distinctaddresses: string[] = []
|
||||||
const regaddresses: string[] = []
|
const regaddresses: string[] = []
|
||||||
const distinctregaddresses: string[] = []
|
const distinctregaddresses: string[] = []
|
||||||
Object.keys(users).forEach(function (key) {
|
for (const user of users.values()) {
|
||||||
const user = users[key]
|
|
||||||
if (!user) return
|
if (!user) return
|
||||||
let found = false
|
let found = false
|
||||||
for (let i = 0; i < distinctaddresses.length; i++) {
|
for (const distinctaddress of distinctaddresses) {
|
||||||
if (user.address === distinctaddresses[i]) {
|
if (user.address === distinctaddress) {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
if (user.address != null) {
|
||||||
distinctaddresses.push(user.address)
|
distinctaddresses.push(user.address)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (user.login) {
|
if (user.login) {
|
||||||
|
if (user.address != null) {
|
||||||
regaddresses.push(user.address)
|
regaddresses.push(user.address)
|
||||||
|
}
|
||||||
let found = false
|
let found = false
|
||||||
for (let i = 0; i < distinctregaddresses.length; i++) {
|
for (let i = 0; i < distinctregaddresses.length; i++) {
|
||||||
if (user.address === distinctregaddresses[i]) {
|
if (user.address === distinctregaddresses[i]) {
|
||||||
|
@ -228,10 +269,12 @@ function getStatus (callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
if (user.address != null) {
|
||||||
distinctregaddresses.push(user.address)
|
distinctregaddresses.push(user.address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
User.count().then(function (regcount) {
|
User.count().then(function (regcount) {
|
||||||
// eslint-disable-next-line standard/no-callback-literal
|
// eslint-disable-next-line standard/no-callback-literal
|
||||||
return callback ? callback({
|
return callback ? callback({
|
||||||
|
@ -262,7 +305,7 @@ function isReady (): boolean {
|
||||||
disconnectSocketQueue.length === 0 && !isDisconnectBusy
|
disconnectSocketQueue.length === 0 && !isDisconnectBusy
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractNoteIdFromSocket (socket): string | boolean {
|
function extractNoteIdFromSocket (socket: Socket): string | boolean {
|
||||||
if (!socket || !socket.handshake) {
|
if (!socket || !socket.handshake) {
|
||||||
return false
|
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)
|
const noteId = extractNoteIdFromSocket(socket)
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
return callback(null, null)
|
return callback(null, null)
|
||||||
|
@ -284,8 +327,8 @@ function parseNoteIdFromSocket (socket, callback: (err, noteId) => void): void {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildUserOutData (user) {
|
function buildUserOutData (user): UserSession {
|
||||||
const out = {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
login: user.login,
|
login: user.login,
|
||||||
userid: user.userid,
|
userid: user.userid,
|
||||||
|
@ -296,19 +339,19 @@ function buildUserOutData (user) {
|
||||||
idle: user.idle,
|
idle: user.idle,
|
||||||
type: user.type
|
type: user.type
|
||||||
}
|
}
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitOnlineUsers (socket: SocketWithNoteId): void {
|
function emitOnlineUsers (socket: SocketWithNoteId): void {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
if (!noteId || !notes[noteId]) return
|
if (!noteId) return
|
||||||
const users: any[] = []
|
const note = notes.get(noteId)
|
||||||
Object.keys(notes[noteId].users).forEach(function (key) {
|
if (!note) return
|
||||||
const user = notes[noteId].users[key]
|
const users: UserSession[] = []
|
||||||
|
for (const user of note.users.values()) {
|
||||||
if (user) {
|
if (user) {
|
||||||
users.push(buildUserOutData(user))
|
users.push(buildUserOutData(user))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
const out = {
|
const out = {
|
||||||
users: users
|
users: users
|
||||||
}
|
}
|
||||||
|
@ -317,16 +360,20 @@ function emitOnlineUsers (socket: SocketWithNoteId): void {
|
||||||
|
|
||||||
function emitUserStatus (socket: SocketWithNoteId): void {
|
function emitUserStatus (socket: SocketWithNoteId): void {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
const user = users[socket.id]
|
if (!noteId) return
|
||||||
if (!noteId || !notes[noteId] || !user) return
|
const note = notes.get(noteId)
|
||||||
|
if (!note) return
|
||||||
|
const user = users.get(socket.id)
|
||||||
|
if (!user) return
|
||||||
const out = buildUserOutData(user)
|
const out = buildUserOutData(user)
|
||||||
socket.broadcast.to(noteId).emit('user status', out)
|
socket.broadcast.to(noteId).emit('user status', out)
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitRefresh (socket: SocketWithNoteId): void {
|
function emitRefresh (socket: SocketWithNoteId): void {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
if (!noteId || !notes[noteId]) return
|
if (!noteId) return
|
||||||
const note = notes[noteId]
|
const note = notes.get(noteId)
|
||||||
|
if (!note) return
|
||||||
const out = {
|
const out = {
|
||||||
title: note.title,
|
title: note.title,
|
||||||
docmaxlength: config.documentMaxLength,
|
docmaxlength: config.documentMaxLength,
|
||||||
|
@ -334,7 +381,7 @@ function emitRefresh (socket: SocketWithNoteId): void {
|
||||||
ownerprofile: note.ownerprofile,
|
ownerprofile: note.ownerprofile,
|
||||||
lastchangeuser: note.lastchangeuser,
|
lastchangeuser: note.lastchangeuser,
|
||||||
lastchangeuserprofile: note.lastchangeuserprofile,
|
lastchangeuserprofile: note.lastchangeuserprofile,
|
||||||
authors: note.authors,
|
authors: mapToObject(note.authors),
|
||||||
authorship: note.authorship,
|
authorship: note.authorship,
|
||||||
permission: note.permission,
|
permission: note.permission,
|
||||||
createtime: note.createtime,
|
createtime: note.createtime,
|
||||||
|
@ -344,8 +391,8 @@ function emitRefresh (socket: SocketWithNoteId): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDuplicatedInSocketQueue (queue: Socket[], socket: Socket): boolean {
|
function isDuplicatedInSocketQueue (queue: Socket[], socket: Socket): boolean {
|
||||||
for (let i = 0; i < queue.length; i++) {
|
for (const sock of queue) {
|
||||||
if (queue[i] && queue[i].id === socket.id) {
|
if (sock && sock.id === socket.id) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -385,8 +432,8 @@ function failConnection (errorCode: number, errorMessage: string, socket: Socket
|
||||||
}
|
}
|
||||||
|
|
||||||
function interruptConnection (socket: Socket, noteId: string, socketId): void {
|
function interruptConnection (socket: Socket, noteId: string, socketId): void {
|
||||||
if (notes[noteId]) delete notes[noteId]
|
notes.delete(noteId)
|
||||||
if (users[socketId]) delete users[socketId]
|
users.delete(socketId)
|
||||||
if (socket) {
|
if (socket) {
|
||||||
clearSocketQueue(connectionSocketQueue, socket)
|
clearSocketQueue(connectionSocketQueue, socket)
|
||||||
} else {
|
} else {
|
||||||
|
@ -395,7 +442,7 @@ function interruptConnection (socket: Socket, noteId: string, socketId): void {
|
||||||
connectNextSocket()
|
connectNextSocket()
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkViewPermission (req, note): boolean {
|
function checkViewPermission (req, note: NoteSession): boolean {
|
||||||
if (note.permission === 'private') {
|
if (note.permission === 'private') {
|
||||||
return !!(req.user?.logged_in && req.user.id === note.owner)
|
return !!(req.user?.logged_in && req.user.id === note.owner)
|
||||||
} else if (note.permission === 'limited' || note.permission === 'protected') {
|
} 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 {
|
function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: string): void {
|
||||||
// if no valid info provided will drop the client
|
// 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)
|
return interruptConnection(socket, noteId, socketId)
|
||||||
}
|
}
|
||||||
// check view permission
|
// 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)
|
interruptConnection(socket, noteId, socketId)
|
||||||
return failConnection(403, 'connection forbidden', socket)
|
return failConnection(403, 'connection forbidden', socket)
|
||||||
}
|
}
|
||||||
const note = notes[noteId]
|
const user = users.get(socketId)
|
||||||
const user = users[socketId]
|
if (!user) {
|
||||||
// update user color to author color
|
logger.warn('Could not find user for socketId ' + socketId)
|
||||||
if (note.authors[user.userid]) {
|
return
|
||||||
user.color = users[socket.id].color = note.authors[user.userid].color
|
|
||||||
}
|
}
|
||||||
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.socks.push(socket)
|
||||||
note.server.addClient(socket)
|
note.server.addClient(socket)
|
||||||
note.server.setName(socket, user.name)
|
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 {
|
function ifMayEdit (socket: SocketWithNoteId, originIsOperation: boolean, callback: (mayEdit: boolean) => void): void {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
if (!noteId || !notes[noteId]) return
|
if (!noteId) return
|
||||||
const note = notes[noteId]
|
const note = notes.get(noteId)
|
||||||
|
if (!note) return
|
||||||
let mayEdit = true
|
let mayEdit = true
|
||||||
switch (note.permission) {
|
switch (note.permission) {
|
||||||
case 'freely':
|
case 'freely':
|
||||||
|
@ -488,15 +548,18 @@ function ifMayEdit (socket: SocketWithNoteId, originIsOperation: boolean, callba
|
||||||
|
|
||||||
function operationCallback (socket: SocketWithNoteId, operation): void {
|
function operationCallback (socket: SocketWithNoteId, operation): void {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
if (!noteId || !notes[noteId]) return
|
if (!noteId) return
|
||||||
const note = notes[noteId]
|
const note = notes.get(noteId)
|
||||||
let userId = null
|
if (!note) return
|
||||||
|
let userId: string | null = null
|
||||||
// save authors
|
// save authors
|
||||||
if (socket.request.user && socket.request.user.logged_in) {
|
if (socket.request.user && socket.request.user.logged_in) {
|
||||||
const user = users[socket.id]
|
const user = users.get(socket.id)
|
||||||
if (!user) return
|
if (!user) return
|
||||||
userId = socket.request.user.id
|
userId = socket.request.user.id
|
||||||
if (!note.authors[userId]) {
|
if (!userId) return
|
||||||
|
const author = note.authors.get(userId)
|
||||||
|
if (!author) {
|
||||||
Author.findOrCreate({
|
Author.findOrCreate({
|
||||||
where: {
|
where: {
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
|
@ -507,20 +570,20 @@ function operationCallback (socket: SocketWithNoteId, operation): void {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
color: user.color
|
color: user.color
|
||||||
}
|
}
|
||||||
}).then(function ([author, created]) {
|
}).then(function ([author, _]) {
|
||||||
if (author) {
|
if (author) {
|
||||||
note.authors[author.userId] = {
|
note.authors.set(author.userId, {
|
||||||
userid: author.userId,
|
userid: author.userId,
|
||||||
color: author.color,
|
color: author.color,
|
||||||
photo: user.photo,
|
photo: user.photo,
|
||||||
name: user.name
|
name: user.name
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
logger.error('operation callback failed: ' + 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
|
// save authorship - use timer here because it's an O(n) complexity algorithm
|
||||||
setImmediate(function () {
|
setImmediate(function () {
|
||||||
|
@ -537,7 +600,7 @@ function startConnection (socket: SocketWithNoteId): void {
|
||||||
return failConnection(404, 'note id not found', socket)
|
return failConnection(404, 'note id not found', socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notes[noteId]) {
|
if (!notes.get(noteId)) {
|
||||||
const include = [{
|
const include = [{
|
||||||
model: User,
|
model: User,
|
||||||
as: 'owner'
|
as: 'owner'
|
||||||
|
@ -573,21 +636,20 @@ function startConnection (socket: SocketWithNoteId): void {
|
||||||
const updatetime = note.lastchangeAt
|
const updatetime = note.lastchangeAt
|
||||||
const server = new EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback)
|
const server = new EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback)
|
||||||
|
|
||||||
const authors = {}
|
const authors = new Map<string, UserSession>()
|
||||||
for (let i = 0; i < note.authors.length; i++) {
|
for (const author of note.authors) {
|
||||||
const author = note.authors[i]
|
|
||||||
const profile = User.getProfile(author.user)
|
const profile = User.getProfile(author.user)
|
||||||
if (profile) {
|
if (profile) {
|
||||||
authors[author.userId] = {
|
authors.set(author.userId, {
|
||||||
userid: author.userId,
|
userid: author.userId,
|
||||||
color: author.color,
|
color: author.color,
|
||||||
photo: profile.photo,
|
photo: profile.photo,
|
||||||
name: profile.name
|
name: profile.name
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notes[noteId] = {
|
notes.set(noteId, {
|
||||||
id: noteId,
|
id: noteId,
|
||||||
alias: note.alias,
|
alias: note.alias,
|
||||||
title: note.title,
|
title: note.title,
|
||||||
|
@ -597,14 +659,14 @@ function startConnection (socket: SocketWithNoteId): void {
|
||||||
lastchangeuser: lastchangeuser,
|
lastchangeuser: lastchangeuser,
|
||||||
lastchangeuserprofile: lastchangeuserprofile,
|
lastchangeuserprofile: lastchangeuserprofile,
|
||||||
socks: [],
|
socks: [],
|
||||||
users: {},
|
users: new Map<string, UserSession>(),
|
||||||
tempUsers: {},
|
tempUsers: new Map<string, number>(),
|
||||||
createtime: moment(createtime).valueOf(),
|
createtime: moment(createtime).valueOf(),
|
||||||
updatetime: moment(updatetime).valueOf(),
|
updatetime: moment(updatetime).valueOf(),
|
||||||
server: server,
|
server: server,
|
||||||
authors: authors,
|
authors: authors,
|
||||||
authorship: note.authorship
|
authorship: note.authorship
|
||||||
}
|
})
|
||||||
|
|
||||||
return finishConnection(socket, noteId, socket.id)
|
return finishConnection(socket, noteId, socket.id)
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
@ -623,17 +685,17 @@ function disconnect (socket: SocketWithNoteId): void {
|
||||||
isDisconnectBusy = true
|
isDisconnectBusy = true
|
||||||
|
|
||||||
logger.debug('SERVER disconnected a client')
|
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]) {
|
if (users.get(socket.id)) {
|
||||||
delete users[socket.id]
|
users.delete(socket.id)
|
||||||
}
|
}
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
const note = notes[noteId]
|
const note = notes.get(noteId)
|
||||||
if (note) {
|
if (note) {
|
||||||
// delete user in users
|
// delete user in users
|
||||||
if (note.users[socket.id]) {
|
if (note.users.get(socket.id)) {
|
||||||
delete note.users[socket.id]
|
note.users.delete(socket.id)
|
||||||
}
|
}
|
||||||
// remove sockets in the note socks
|
// remove sockets in the note socks
|
||||||
let index
|
let index
|
||||||
|
@ -644,15 +706,15 @@ function disconnect (socket: SocketWithNoteId): void {
|
||||||
}
|
}
|
||||||
} while (index !== -1)
|
} while (index !== -1)
|
||||||
// remove note in notes if no user inside
|
// remove note in notes if no user inside
|
||||||
if (Object.keys(note.users).length <= 0) {
|
if (note.users.size <= 0) {
|
||||||
if (note.server.isDirty) {
|
if (note.server.isDirty) {
|
||||||
updateNote(note, function (err, _note) {
|
updateNote(note, function (err, _) {
|
||||||
if (err) return logger.error('disconnect note failed: ' + err)
|
if (err) return logger.error('disconnect note failed: ' + err)
|
||||||
// clear server before delete to avoid memory leaks
|
// clear server before delete to avoid memory leaks
|
||||||
note.server.document = ''
|
note.server.document = ''
|
||||||
note.server.operations = []
|
note.server.operations = []
|
||||||
delete note.server
|
delete note.server
|
||||||
delete notes[noteId]
|
notes.delete(noteId)
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
logger.debug(notes)
|
logger.debug(notes)
|
||||||
getStatus(function (data) {
|
getStatus(function (data) {
|
||||||
|
@ -662,7 +724,7 @@ function disconnect (socket: SocketWithNoteId): void {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
delete note.server
|
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
|
// clean when user not in any rooms or user not in connected list
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
async.each(Object.keys(users), function (key, callback) {
|
for (const [key, user] of users) {
|
||||||
let socket = realtime.io.sockets.connected[key]
|
let socket = realtime.io.sockets.connected[key] as SocketWithNoteId
|
||||||
if ((!socket && users[key]) ||
|
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}`)
|
logger.debug(`cleaner found redundant user: ${key}`)
|
||||||
if (!socket) {
|
if (!socket) {
|
||||||
socket = {
|
socket = {
|
||||||
id: key
|
id: key
|
||||||
}
|
} as SocketWithNoteId
|
||||||
}
|
}
|
||||||
disconnectSocketQueue.push(socket)
|
disconnectSocketQueue.push(socket)
|
||||||
disconnect(socket)
|
disconnect(socket)
|
||||||
}
|
}
|
||||||
return callback(null, null)
|
}
|
||||||
}, function (err) {
|
|
||||||
if (err) return logger.error('cleaner error', err)
|
|
||||||
})
|
|
||||||
}, 60000)
|
}, 60000)
|
||||||
|
|
||||||
function updateUserData (socket: Socket, user): void {
|
function updateUserData (socket: Socket, user): void {
|
||||||
|
@ -739,16 +798,17 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
// random color
|
// random color
|
||||||
let color = randomcolor()
|
let color = randomcolor()
|
||||||
// make sure color not duplicated or reach max random count
|
// make sure color not duplicated or reach max random count
|
||||||
if (notes[noteId]) {
|
const note = notes.get(noteId)
|
||||||
|
if (note) {
|
||||||
let randomcount = 0
|
let randomcount = 0
|
||||||
const maxrandomcount = 10
|
const maxrandomcount = 10
|
||||||
let found = false
|
let found = false
|
||||||
do {
|
do {
|
||||||
Object.keys(notes[noteId].users).forEach(function (userId) {
|
for (const user of note.users.values()) {
|
||||||
if (notes[noteId].users[userId].color === color) {
|
if (user.color === color) {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
if (found) {
|
if (found) {
|
||||||
color = randomcolor()
|
color = randomcolor()
|
||||||
randomcount++
|
randomcount++
|
||||||
|
@ -756,19 +816,20 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
} while (found && randomcount < maxrandomcount)
|
} while (found && randomcount < maxrandomcount)
|
||||||
}
|
}
|
||||||
// create user data
|
// create user data
|
||||||
users[socket.id] = {
|
users.set(socket.id, {
|
||||||
id: socket.id,
|
id: socket.id,
|
||||||
address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address,
|
address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address,
|
||||||
'user-agent': socket.handshake.headers['user-agent'],
|
'user-agent': socket.handshake.headers['user-agent'],
|
||||||
color: color,
|
color: color,
|
||||||
cursor: null,
|
cursor: undefined,
|
||||||
login: false,
|
login: false,
|
||||||
userid: null,
|
userid: null,
|
||||||
name: null,
|
name: null,
|
||||||
idle: false,
|
idle: false,
|
||||||
type: null
|
type: '',
|
||||||
}
|
photo: ''
|
||||||
updateUserData(socket, users[socket.id])
|
})
|
||||||
|
updateUserData(socket, users.get(socket.id))
|
||||||
|
|
||||||
// start connection
|
// start connection
|
||||||
connectionSocketQueue.push(socket)
|
connectionSocketQueue.push(socket)
|
||||||
|
@ -783,8 +844,8 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
// received user status
|
// received user status
|
||||||
socket.on('user status', function (data) {
|
socket.on('user status', function (data) {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
const user = users[socket.id]
|
const user = users.get(socket.id)
|
||||||
if (!noteId || !notes[noteId] || !user) return
|
if (!noteId || !notes.get(noteId) || !user) return
|
||||||
logger.debug(`SERVER received [${noteId}] user status from [${socket.id}]: ${JSON.stringify(data)}`)
|
logger.debug(`SERVER received [${noteId}] user status from [${socket.id}]: ${JSON.stringify(data)}`)
|
||||||
if (data) {
|
if (data) {
|
||||||
user.idle = data.idle
|
user.idle = data.idle
|
||||||
|
@ -798,8 +859,9 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
// need login to do more actions
|
// need login to do more actions
|
||||||
if (socket.request.user && socket.request.user.logged_in) {
|
if (socket.request.user && socket.request.user.logged_in) {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
if (!noteId || !notes[noteId]) return
|
if (!noteId) return
|
||||||
const note = notes[noteId]
|
const note = notes.get(noteId)
|
||||||
|
if (!note) return
|
||||||
// Only owner can change permission
|
// Only owner can change permission
|
||||||
if (note.owner && note.owner === socket.request.user.id) {
|
if (note.owner && note.owner === socket.request.user.id) {
|
||||||
if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousEdits) return
|
if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousEdits) return
|
||||||
|
@ -844,8 +906,9 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
// need login to do more actions
|
// need login to do more actions
|
||||||
if (socket.request.user && socket.request.user.logged_in) {
|
if (socket.request.user && socket.request.user.logged_in) {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
if (!noteId || !notes[noteId]) return
|
if (!noteId) return
|
||||||
const note = notes[noteId]
|
const note = notes.get(noteId)
|
||||||
|
if (!note) return
|
||||||
// Only owner can delete note
|
// Only owner can delete note
|
||||||
if (note.owner && note.owner === socket.request.user.id) {
|
if (note.owner && note.owner === socket.request.user.id) {
|
||||||
Note.destroy({
|
Note.destroy({
|
||||||
|
@ -874,8 +937,10 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
socket.on('user changed', function () {
|
socket.on('user changed', function () {
|
||||||
logger.info('user changed')
|
logger.info('user changed')
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
if (!noteId || !notes[noteId]) return
|
if (!noteId) return
|
||||||
const user = notes[noteId].users[socket.id]
|
const note = notes.get(noteId)
|
||||||
|
if (!note) return
|
||||||
|
const user = note.users.get(socket.id)
|
||||||
if (!user) return
|
if (!user) return
|
||||||
updateUserData(socket, user)
|
updateUserData(socket, user)
|
||||||
emitOnlineUsers(socket)
|
emitOnlineUsers(socket)
|
||||||
|
@ -884,14 +949,15 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
// received sync of online users request
|
// received sync of online users request
|
||||||
socket.on('online users', function () {
|
socket.on('online users', function () {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
if (!noteId || !notes[noteId]) return
|
if (!noteId) return
|
||||||
const users: any = []
|
const note = notes.get(noteId)
|
||||||
Object.keys(notes[noteId].users).forEach(function (key) {
|
if (!note) return
|
||||||
const user = notes[noteId].users[key]
|
const users: UserSession[] = []
|
||||||
|
for (const user of note.users.values()) {
|
||||||
if (user) {
|
if (user) {
|
||||||
users.push(buildUserOutData(user))
|
users.push(buildUserOutData(user))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
const out = {
|
const out = {
|
||||||
users: users
|
users: users
|
||||||
}
|
}
|
||||||
|
@ -909,18 +975,18 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
// received cursor focus
|
// received cursor focus
|
||||||
socket.on('cursor focus', function (data) {
|
socket.on('cursor focus', function (data) {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
const user = users[socket.id]
|
const user = users.get(socket.id)
|
||||||
if (!noteId || !notes[noteId] || !user) return
|
if (!noteId || !notes.get(noteId) || !user) return
|
||||||
user.cursor = data
|
user.cursor = data
|
||||||
const out = buildUserOutData(user)
|
const out = buildUserOutData(user)
|
||||||
socket.broadcast.to(noteId).emit('cursor focus', out)
|
socket.broadcast.to(noteId).emit('cursor focus', out)
|
||||||
})
|
})
|
||||||
|
|
||||||
// received cursor activity
|
// received cursor activity
|
||||||
socket.on('cursor activity', function (data) {
|
socket.on('cursor activity', function (data: CodeMirror.Position) {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
const user = users[socket.id]
|
const user = users.get(socket.id)
|
||||||
if (!noteId || !notes[noteId] || !user) return
|
if (!noteId || !notes.get(noteId) || !user) return
|
||||||
user.cursor = data
|
user.cursor = data
|
||||||
const out = buildUserOutData(user)
|
const out = buildUserOutData(user)
|
||||||
socket.broadcast.to(noteId).emit('cursor activity', out)
|
socket.broadcast.to(noteId).emit('cursor activity', out)
|
||||||
|
@ -929,9 +995,9 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
// received cursor blur
|
// received cursor blur
|
||||||
socket.on('cursor blur', function () {
|
socket.on('cursor blur', function () {
|
||||||
const noteId = socket.noteId
|
const noteId = socket.noteId
|
||||||
const user = users[socket.id]
|
const user = users.get(socket.id)
|
||||||
if (!noteId || !notes[noteId] || !user) return
|
if (!noteId || !notes.get(noteId) || !user) return
|
||||||
user.cursor = null
|
user.cursor = undefined
|
||||||
const out = {
|
const out = {
|
||||||
id: socket.id
|
id: socket.id
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,16 @@ import { logger } from './logger'
|
||||||
import { Revision } from './models'
|
import { Revision } from './models'
|
||||||
import { realtime } from './realtime'
|
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 {
|
export function getImageMimeType (imagePath: string): string | undefined {
|
||||||
const fileExtension = /[^.]+$/.exec(imagePath)
|
const fileExtension = /[^.]+$/.exec(imagePath)
|
||||||
switch (fileExtension?.[0]) {
|
switch (fileExtension?.[0]) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const DropboxMiddleware: AuthMiddleware = {
|
||||||
}, (
|
}, (
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
refreshToken: string,
|
refreshToken: string,
|
||||||
profile: any,
|
profile,
|
||||||
done: (err?: Error | null, user?: User) => void
|
done: (err?: Error | null, user?: User) => void
|
||||||
): void => {
|
): void => {
|
||||||
// the Dropbox plugin wraps the email addresses in an object
|
// the Dropbox plugin wraps the email addresses in an object
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const GoogleMiddleware: AuthMiddleware = {
|
||||||
}, (
|
}, (
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
refreshToken: string,
|
refreshToken: string,
|
||||||
profile: any,
|
profile,
|
||||||
done) => {
|
done) => {
|
||||||
/*
|
/*
|
||||||
This ugly hack is neccessary, because the Google Strategy wants a done-callback with an err as Error | null | undefined
|
This ugly hack is neccessary, because the Google Strategy wants a done-callback with an err as Error | null | undefined
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { InternalOAuthError, Strategy as OAuth2Strategy } from 'passport-oauth2'
|
||||||
import { config } from '../../../config'
|
import { config } from '../../../config'
|
||||||
import { Profile, ProviderEnum } from '../../../models/user'
|
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`
|
// can handle stuff like `attrs[0].name`
|
||||||
const pathArray = path.split('.')
|
const pathArray = path.split('.')
|
||||||
for (const segment of pathArray) {
|
for (const segment of pathArray) {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { Profile } from 'passport'
|
||||||
import { User } from '../../models'
|
import { User } from '../../models'
|
||||||
import { logger } from '../../logger'
|
import { logger } from '../../logger'
|
||||||
|
|
||||||
export function passportGeneralCallback (
|
export function passportGeneralCallback (
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
refreshToken: string,
|
refreshToken: string,
|
||||||
profile: any,
|
profile: Profile,
|
||||||
done: (err?: Error | null, user?: User) => void
|
done: (err?: Error | null, user?: User) => void
|
||||||
): void {
|
): void {
|
||||||
const stringifiedProfile = JSON.stringify(profile)
|
const stringifiedProfile = JSON.stringify(profile)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
import { Note, Revision } from '../../models'
|
import { Note, Revision } from '../../models'
|
||||||
import { logger } from '../../logger'
|
import { logger } from '../../logger'
|
||||||
|
@ -8,7 +8,7 @@ import shortId from 'shortid'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import querystring from 'querystring'
|
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 body = note.content
|
||||||
const extracted = Note.extractMeta(body)
|
const extracted = Note.extractMeta(body)
|
||||||
const markdown = extracted.markdown
|
const markdown = extracted.markdown
|
||||||
|
@ -31,7 +31,7 @@ export function getInfo (req: any, res: Response, note: Note): void {
|
||||||
res.send(data)
|
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 = {
|
const data = {
|
||||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||||
client_id: config.github.clientID,
|
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)
|
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
|
const actionId = req.params.actionId
|
||||||
if (actionId) {
|
if (actionId) {
|
||||||
const time = moment(parseInt(actionId))
|
const time = moment(parseInt(actionId))
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { NextFunction, Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { config } from '../../config'
|
import { config } from '../../config'
|
||||||
import { errors } from '../../errors'
|
import { errors } from '../../errors'
|
||||||
import { logger } from '../../logger'
|
import { logger } from '../../logger'
|
||||||
import { Note, User } from '../../models'
|
import { Note } from '../../models'
|
||||||
import * as ActionController from './actions'
|
import * as ActionController from './actions'
|
||||||
import * as NoteUtils from './util'
|
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) {
|
NoteUtils.findNoteOrCreate(req, res, function (note) {
|
||||||
const action = req.params.action
|
const action = req.params.action
|
||||||
switch (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) {
|
export function showPublishNote (req: Request, res: Response): void {
|
||||||
const include = [{
|
|
||||||
model: User,
|
|
||||||
as: 'owner'
|
|
||||||
}, {
|
|
||||||
model: User,
|
|
||||||
as: 'lastchangeuser'
|
|
||||||
}]
|
|
||||||
NoteUtils.findNoteOrCreate(req, res, function (note) {
|
NoteUtils.findNoteOrCreate(req, res, function (note) {
|
||||||
// force to use short id
|
// force to use short id
|
||||||
const shortid = req.params.shortid
|
const shortid = req.params.shortid
|
||||||
|
@ -51,10 +44,10 @@ export function showPublishNote (req: any, res: Response, next: NextFunction) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return errors.errorInternalError(res)
|
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) {
|
NoteUtils.findNoteOrCreate(req, res, function (note) {
|
||||||
// force to use note id
|
// force to use note id
|
||||||
const noteId = req.params.noteId
|
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 = ''
|
let body = ''
|
||||||
if (req.body && req.body.length > config.documentMaxLength) {
|
if (req.body && req.body.length > config.documentMaxLength) {
|
||||||
return errors.errorTooLong(res)
|
return errors.errorTooLong(res)
|
||||||
|
@ -90,7 +83,7 @@ export function createFromPOST (req: any, res: Response, next: NextFunction) {
|
||||||
return NoteUtils.newNote(req, res, body)
|
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
|
const noteId = req.params.noteId
|
||||||
NoteUtils.findNoteOrCreate(req, res, (note) => {
|
NoteUtils.findNoteOrCreate(req, res, (note) => {
|
||||||
const action = req.params.action
|
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
|
const body = note.content
|
||||||
let filename = Note.decodeTitle(note.title)
|
let filename = Note.decodeTitle(note.title)
|
||||||
filename = encodeURIComponent(filename)
|
filename = encodeURIComponent(filename)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { NextFunction, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { config } from '../../config'
|
import { config } from '../../config'
|
||||||
import { errors } from '../../errors'
|
import { errors } from '../../errors'
|
||||||
import { logger } from '../../logger'
|
import { logger } from '../../logger'
|
||||||
import { Note, User } from '../../models'
|
import { Note } from '../../models'
|
||||||
import * as NoteUtils from './util'
|
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) {
|
NoteUtils.findNoteOrCreate(req, res, function (note) {
|
||||||
const action = req.params.action
|
const action = req.params.action
|
||||||
if (action === 'edit') {
|
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) {
|
export function showPublishSlide (req: Request, res: Response): void {
|
||||||
const include = [{
|
|
||||||
model: User,
|
|
||||||
as: 'owner'
|
|
||||||
}, {
|
|
||||||
model: User,
|
|
||||||
as: 'lastchangeuser'
|
|
||||||
}]
|
|
||||||
NoteUtils.findNoteOrCreate(req, res, function (note) {
|
NoteUtils.findNoteOrCreate(req, res, function (note) {
|
||||||
// force to use short id
|
// force to use short id
|
||||||
const shortid = req.params.shortid
|
const shortid = req.params.shortid
|
||||||
|
@ -44,5 +37,5 @@ export function showPublishSlide (req: any, res: Response, next: NextFunction) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return errors.errorInternalError(res)
|
return errors.errorInternalError(res)
|
||||||
})
|
})
|
||||||
}, include)
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import { Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { Includeable } from 'sequelize'
|
|
||||||
import { config } from '../../config'
|
import { config } from '../../config'
|
||||||
import { errors } from '../../errors'
|
import { errors } from '../../errors'
|
||||||
import { logger } from '../../logger'
|
import { logger } from '../../logger'
|
||||||
import { Note, User } from '../../models'
|
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
|
let owner = null
|
||||||
const noteId = req.params.noteId ? req.params.noteId : null
|
const noteId = req.params.noteId ? req.params.noteId : null
|
||||||
if (req.isAuthenticated()) {
|
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') {
|
if (note.permission === 'private') {
|
||||||
return req.isAuthenticated() && note.ownerId === req.user.id
|
return req.isAuthenticated() && note.ownerId === req.user.id
|
||||||
} else if (note.permission === 'limited' || note.permission === 'protected') {
|
} 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
|
const id = req.params.noteId || req.params.shortid
|
||||||
Note.parseNoteId(id, function (err, _id) {
|
Note.parseNoteId(id, function (err, _id) {
|
||||||
if (err) {
|
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'))) {
|
if (fs.existsSync(path.join(__dirname, '..', '..', '..', '..', 'public', 'build', 'reveal.js', 'css', 'theme', theme + '.css'))) {
|
||||||
return theme
|
return theme
|
||||||
}
|
}
|
||||||
return undefined
|
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 body = note.content
|
||||||
const extracted = Note.extractMeta(body)
|
const extracted = Note.extractMeta(body)
|
||||||
const markdown = extracted.markdown
|
const markdown = extracted.markdown
|
||||||
|
|
|
@ -98,7 +98,7 @@ function createPatch (lastDoc: string, currDoc: string): string {
|
||||||
|
|
||||||
class Data {
|
class Data {
|
||||||
msg: string
|
msg: string
|
||||||
cacheKey: any
|
cacheKey: string
|
||||||
lastDoc?: string
|
lastDoc?: string
|
||||||
currDoc?: string
|
currDoc?: string
|
||||||
revisions?: Revision[]
|
revisions?: Revision[]
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"lib": ["ES2019"],
|
"lib": ["ES2019", "DOM"],
|
||||||
"alwaysStrict": true
|
"alwaysStrict": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
|
|
73
yarn.lock
73
yarn.lock
|
@ -130,6 +130,13 @@
|
||||||
"@types/connect" "*"
|
"@types/connect" "*"
|
||||||
"@types/node" "*"
|
"@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":
|
"@types/color-name@^1.1.1":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||||
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
|
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":
|
"@types/estree@0.0.39":
|
||||||
version "0.0.39"
|
version "0.0.39"
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||||
|
@ -248,6 +260,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@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":
|
"@types/node@*", "@types/node@>=8.0.0", "@types/node@^13.11.1":
|
||||||
version "13.11.1"
|
version "13.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
|
||||||
|
@ -392,6 +409,18 @@
|
||||||
"@types/express-serve-static-core" "*"
|
"@types/express-serve-static-core" "*"
|
||||||
"@types/mime" "*"
|
"@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@*":
|
"@types/socket.io@*":
|
||||||
version "2.1.4"
|
version "2.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-2.1.4.tgz#674e7bc193c5ccdadd4433f79f3660d31759e9ac"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02"
|
||||||
integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ==
|
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":
|
"@types/tunnel@0.0.0":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.0.tgz#c2a42943ee63c90652a5557b8c4e56cda77f944e"
|
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.0.tgz#c2a42943ee63c90652a5557b8c4e56cda77f944e"
|
||||||
|
@ -4781,11 +4817,6 @@ generate-function@^2.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-property "^1.0.2"
|
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:
|
get-caller-file@^2.0.1:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
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:
|
dependencies:
|
||||||
assert-plus "0.1.5"
|
assert-plus "0.1.5"
|
||||||
|
|
||||||
ldapauth-fork@^4.3.2:
|
ldapauth-fork@^4.3.0, ldapauth-fork@^4.3.2:
|
||||||
version "4.3.2"
|
version "4.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/ldapauth-fork/-/ldapauth-fork-4.3.2.tgz#bc408f0d4fb8d07e5e9a7287c7310fdceea226f9"
|
resolved "https://registry.yarnpkg.com/ldapauth-fork/-/ldapauth-fork-4.3.3.tgz#d62c8f18a5035fd47a572f2ac7aa8c8227b3f4c2"
|
||||||
integrity sha512-XaO/kLaY9XGH/O58qTgtGtS0u7Qfq9IMDYWBvjRDuNfh7PbqOA5JXwF7DeKW6kIWQ842fGoIpB/cZmJ0SaJFbQ==
|
integrity sha512-x76VpQ5ZqkwAJmqwcD6KIwDiNEbgIGIPGwC/eA17e1dxWhlTx36w0DlLOFwjTuZ2iuaLTsZsUprlVqvSlwc/1Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/ldapjs" "^1.0.0"
|
"@types/ldapjs" "^1.0.0"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
@ -7009,14 +7040,6 @@ mocha@^5.2.0:
|
||||||
mkdirp "0.5.1"
|
mkdirp "0.5.1"
|
||||||
supports-color "5.4.0"
|
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:
|
moment-mini@^2.22.1:
|
||||||
version "2.24.0"
|
version "2.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18"
|
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18"
|
||||||
|
@ -9520,7 +9543,7 @@ simple-swizzle@^0.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish "^0.3.1"
|
is-arrayish "^0.3.1"
|
||||||
|
|
||||||
sinon@^9.0.1:
|
sinon@^9.0.1, sinon@^9.0.2:
|
||||||
version "9.0.2"
|
version "9.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d"
|
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d"
|
||||||
integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==
|
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"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||||
|
|
||||||
sqlite3@^4.1.0:
|
sqlite3@^4.2.0:
|
||||||
version "4.1.1"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.1.1.tgz#539a42e476640796578e22d589b3283c28055242"
|
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.2.0.tgz#49026d665e9fc4f922e56fb9711ba5b4c85c4901"
|
||||||
integrity sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==
|
integrity sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==
|
||||||
dependencies:
|
dependencies:
|
||||||
nan "^2.12.1"
|
nan "^2.12.1"
|
||||||
node-pre-gyp "^0.11.0"
|
node-pre-gyp "^0.11.0"
|
||||||
request "^2.87.0"
|
|
||||||
|
|
||||||
sqlstring@^2.3.1:
|
sqlstring@^2.3.1:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/try-to-catch/-/try-to-catch-1.1.1.tgz#770162dd13b9a0e55da04db5b7f888956072038a"
|
||||||
integrity sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA==
|
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:
|
ts-node@^8.8.2:
|
||||||
version "8.8.2"
|
version "8.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f"
|
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f"
|
||||||
|
|
Loading…
Reference in a new issue