ts: lib/realtime/realtime.ts

Signed-off-by: Raccoon <raccoon@hackmd.io>
This commit is contained in:
Raccoon 2021-06-12 06:46:32 +08:00
parent 16618e1e61
commit 7cea9a29a9
No known key found for this signature in database
GPG Key ID: 06770355DC9ECD38

View File

@ -1,62 +1,53 @@
'use strict'
// realtime // realtime
// external modules // external modules
const cookie = require('cookie') import * as cookie from "cookie";
const cookieParser = require('cookie-parser') import * as cookieParser from "cookie-parser";
const url = require('url') import * as url from "url";
const randomcolor = require('randomcolor') import * as randomcolor from "randomcolor";
const Chance = require('chance') import * as Chance from "chance";
const chance = new Chance() import * as moment from "moment";
const moment = require('moment') import {get} from "lodash";
const get = require('lodash/get')
// core // core
const config = require('../config') import * as config from "../config";
const logger = require('../logger') import * as logger from "../logger";
const history = require('../history') import * as history from "../history";
const models = require('../models') import * as models from "../models";
// ot // ot
const ot = require('../ot') import * as ot from "../ot";
const { ProcessQueue } = require('./processQueue') import {ProcessQueue} from "./processQueue";
const { RealtimeClientConnection } = require('./realtimeClientConnection') import {RealtimeClientConnection} from "./realtimeClientConnection";
const { UpdateDirtyNoteJob } = require('./realtimeUpdateDirtyNoteJob') import {UpdateDirtyNoteJob} from "./realtimeUpdateDirtyNoteJob";
const { CleanDanglingUserJob } = require('./realtimeCleanDanglingUserJob') import {CleanDanglingUserJob} from "./realtimeCleanDanglingUserJob";
const { SaveRevisionJob } = require('./realtimeSaveRevisionJob') import {SaveRevisionJob} from "./realtimeSaveRevisionJob";
const chance = new Chance()
export let io = null
export let maintenance = true
// public // public
const realtime = {
io: null,
onAuthorizeSuccess: onAuthorizeSuccess,
onAuthorizeFail: onAuthorizeFail,
secure: secure,
connection: connection,
getStatus: getStatus,
isReady: isReady,
maintenance: true
}
const connectProcessQueue = new ProcessQueue({}) const connectProcessQueue = new ProcessQueue({})
const disconnectProcessQueue = new ProcessQueue({}) export const disconnectProcessQueue = new ProcessQueue({})
const updateDirtyNoteJob = new UpdateDirtyNoteJob(realtime) const updateDirtyNoteJob = new UpdateDirtyNoteJob(exports)
const cleanDanglingUserJob = new CleanDanglingUserJob(realtime) const cleanDanglingUserJob = new CleanDanglingUserJob(exports)
const saveRevisionJob = new SaveRevisionJob(realtime) export const saveRevisionJob = new SaveRevisionJob(exports)
// TODO: test it // TODO: test it
function onAuthorizeSuccess (data, accept) { export function onAuthorizeSuccess(data, accept) {
accept() accept()
} }
// TODO: test it // TODO: test it
function onAuthorizeFail (data, message, error, accept) { export function onAuthorizeFail(data, message, error, accept) {
accept() // accept whether authorize or not to allow anonymous usage accept() // accept whether authorize or not to allow anonymous usage
} }
// TODO: test it // TODO: test it
// secure the origin by the cookie // secure the origin by the cookie
function secure (socket, next) { export function secure(socket, next) {
try { try {
var handshakeData = socket.request var handshakeData = socket.request
if (handshakeData.headers.cookie) { if (handshakeData.headers.cookie) {
@ -82,7 +73,7 @@ function secure (socket, next) {
// TODO: only use in `updateDirtyNote` // TODO: only use in `updateDirtyNote`
// TODO: test it // TODO: test it
function emitCheck (note) { export function emitCheck(note) {
var out = { var out = {
title: note.title, title: note.title,
updatetime: note.updatetime, updatetime: note.updatetime,
@ -91,50 +82,50 @@ function emitCheck (note) {
authors: note.authors, authors: note.authors,
authorship: note.authorship authorship: note.authorship
} }
realtime.io.to(note.id).emit('check', out) io.to(note.id).emit('check', out)
} }
// actions // actions
var users = {} export var users = {}
var notes = {} export var notes = {}
function getNotePool () { export function getNotePool() {
return notes return notes
} }
function isNoteExistsInPool (noteId) { export function isNoteExistsInPool(noteId) {
return !!notes[noteId] return !!notes[noteId]
} }
function addNote (note) { export function addNote(note) {
if (exports.isNoteExistsInPool(note.id)) return false if (exports.isNoteExistsInPool(note.id)) return false
notes[note.id] = note notes[note.id] = note
return true return true
} }
function getNotePoolSize () { export function getNotePoolSize() {
return Object.keys(notes).length return Object.keys(notes).length
} }
function deleteNoteFromPool (noteId) { export function deleteNoteFromPool(noteId) {
delete notes[noteId] delete notes[noteId]
} }
function deleteAllNoteFromPool () { export function deleteAllNoteFromPool() {
Object.keys(notes).forEach(noteId => { Object.keys(notes).forEach(noteId => {
delete notes[noteId] delete notes[noteId]
}) })
} }
function getNoteFromNotePool (noteId) { export function getNoteFromNotePool(noteId) {
return notes[noteId] return notes[noteId]
} }
function getUserPool () { export function getUserPool() {
return users return users
} }
function getUserFromUserPool (userId) { export function getUserFromUserPool(userId) {
return users[userId] return users[userId]
} }
@ -143,7 +134,7 @@ updateDirtyNoteJob.start()
cleanDanglingUserJob.start() cleanDanglingUserJob.start()
saveRevisionJob.start() saveRevisionJob.start()
function disconnectSocketOnNote (note) { export function disconnectSocketOnNote(note) {
note.socks.forEach((sock) => { note.socks.forEach((sock) => {
if (sock) { if (sock) {
sock.emit('delete') sock.emit('delete')
@ -154,7 +145,7 @@ function disconnectSocketOnNote (note) {
}) })
} }
function updateNote (note, callback) { export function updateNote(note, callback) {
_updateNoteAsync(note).then(_note => { _updateNoteAsync(note).then(_note => {
callback(null, _note) callback(null, _note)
}).catch((err) => { }).catch((err) => {
@ -247,7 +238,7 @@ async function _updateNoteAsync (note) {
} }
// TODO: test it // TODO: test it
function getStatus () { export function getStatus() {
return models.Note.count() return models.Note.count()
.then(function (notecount) { .then(function (notecount) {
var distinctaddresses = [] var distinctaddresses = []
@ -308,8 +299,8 @@ function getStatus () {
} }
// TODO: test it // TODO: test it
function isReady () { export function isReady() {
return realtime.io && return io &&
Object.keys(notes).length === 0 && Object.keys(users).length === 0 && Object.keys(notes).length === 0 && Object.keys(users).length === 0 &&
connectProcessQueue.queue.length === 0 && !connectProcessQueue.lock && connectProcessQueue.queue.length === 0 && !connectProcessQueue.lock &&
disconnectProcessQueue.queue.length === 0 && !disconnectProcessQueue.lock disconnectProcessQueue.queue.length === 0 && !disconnectProcessQueue.lock
@ -329,7 +320,7 @@ function parseUrl (data) {
return null return null
} }
function extractNoteIdFromSocket (socket) { export function extractNoteIdFromSocket(socket) {
function extractNoteIdFromReferer(referer) { function extractNoteIdFromReferer(referer) {
if (referer) { if (referer) {
const hostUrl = parseUrl(referer) const hostUrl = parseUrl(referer)
@ -362,7 +353,7 @@ function extractNoteIdFromSocket (socket) {
return false return false
} }
async function parseNoteIdFromSocketAsync (socket) { export async function parseNoteIdFromSocketAsync(socket) {
const noteId = extractNoteIdFromSocket(socket) const noteId = extractNoteIdFromSocket(socket)
if (!noteId) { if (!noteId) {
return null return null
@ -382,7 +373,7 @@ async function parseNoteIdFromSocketAsync (socket) {
} }
// TODO: test it // TODO: test it
function emitOnlineUsers (socket) { export function emitOnlineUsers(socket) {
var noteId = socket.noteId var noteId = socket.noteId
if (!noteId || !notes[noteId]) return if (!noteId || !notes[noteId]) return
var users = [] var users = []
@ -395,11 +386,11 @@ function emitOnlineUsers (socket) {
var out = { var out = {
users: users users: users
} }
realtime.io.to(noteId).emit('online users', out) io.to(noteId).emit('online users', out)
} }
// TODO: test it // TODO: test it
function emitUserStatus (socket) { export function emitUserStatus(socket) {
var noteId = socket.noteId var noteId = socket.noteId
var user = users[socket.id] var user = users[socket.id]
if (!noteId || !notes[noteId] || !user) return if (!noteId || !notes[noteId] || !user) return
@ -408,7 +399,7 @@ function emitUserStatus (socket) {
} }
// TODO: test it // TODO: test it
function emitRefresh (socket) { export function emitRefresh(socket) {
var noteId = socket.noteId var noteId = socket.noteId
if (!noteId || !notes[noteId]) return if (!noteId || !notes[noteId]) return
var note = notes[noteId] var note = notes[noteId]
@ -428,7 +419,7 @@ function emitRefresh (socket) {
socket.emit('refresh', out) socket.emit('refresh', out)
} }
function checkViewPermission (req, note) { export function checkViewPermission(req, note) {
if (note.permission === 'private') { if (note.permission === 'private') {
if (req.user && req.user.logged_in && req.user.id === note.owner) { if (req.user && req.user.logged_in && req.user.id === note.owner) {
return true return true
@ -509,7 +500,7 @@ function makeNewServerNote (note) {
} }
// TODO: test it // TODO: test it
function failConnection (code, err, socket) { export function failConnection(code, err, socket) {
logger.error(err) logger.error(err)
// emit error info // emit error info
socket.emit('info', { socket.emit('info', {
@ -518,7 +509,7 @@ function failConnection (code, err, socket) {
return socket.disconnect(true) return socket.disconnect(true)
} }
function queueForDisconnect (socket) { export function queueForDisconnect(socket) {
disconnectProcessQueue.push(socket.id, async function () { disconnectProcessQueue.push(socket.id, async function () {
if (users[socket.id]) { if (users[socket.id]) {
delete users[socket.id] delete users[socket.id]
@ -559,7 +550,7 @@ function queueForDisconnect (socket) {
}) })
} }
function buildUserOutData (user) { export function buildUserOutData(user) {
var out = { var out = {
id: user.id, id: user.id,
login: user.login, login: user.login,
@ -575,7 +566,7 @@ function buildUserOutData (user) {
} }
// TODO: test it // TODO: test it
function updateUserData (socket, user) { export function updateUserData(socket, user) {
// retrieve user data from passport // retrieve user data from passport
if (socket.request.user && socket.request.user.logged_in) { if (socket.request.user && socket.request.user.logged_in) {
var profile = models.User.getProfile(socket.request.user) var profile = models.User.getProfile(socket.request.user)
@ -606,7 +597,7 @@ function canEditNote (notePermission, noteOwnerId, currentUserId) {
} }
} }
function ifMayEdit (socket, callback) { export function ifMayEdit(socket, callback) {
const note = getNoteFromNotePool(socket.noteId) const note = getNoteFromNotePool(socket.noteId)
if (!note) return if (!note) return
const mayEdit = canEditNote(note.permission, note.owner, socket.request.user.id) const mayEdit = canEditNote(note.permission, note.owner, socket.request.user.id)
@ -666,7 +657,7 @@ function operationCallback (socket, operation) {
} }
// TODO: test it // TODO: test it
function updateHistory (userId, note, time) { export function updateHistory(userId, note, time) {
var noteId = note.alias ? note.alias : models.Note.encodeNoteId(note.id) var noteId = note.alias ? note.alias : models.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)
} }
@ -792,47 +783,14 @@ function queueForConnect (socket) {
}) })
} }
function connection (socket) { export function connection(socket) {
if (realtime.maintenance) return if (maintenance) return
queueForConnect(socket) queueForConnect(socket)
} }
// TODO: test it // TODO: test it
function terminate () { export function terminate() {
disconnectProcessQueue.stop() disconnectProcessQueue.stop()
connectProcessQueue.stop() connectProcessQueue.stop()
updateDirtyNoteJob.stop() updateDirtyNoteJob.stop()
} }
exports = module.exports = realtime
exports.extractNoteIdFromSocket = extractNoteIdFromSocket
exports.updateNote = updateNote
exports.failConnection = failConnection
exports.updateUserData = updateUserData
exports.emitRefresh = emitRefresh
exports.emitUserStatus = emitUserStatus
exports.emitOnlineUsers = emitOnlineUsers
exports.checkViewPermission = checkViewPermission
exports.getUserFromUserPool = getUserFromUserPool
exports.buildUserOutData = buildUserOutData
exports.emitCheck = emitCheck
exports.disconnectSocketOnNote = disconnectSocketOnNote
exports.queueForDisconnect = queueForDisconnect
exports.terminate = terminate
exports.updateHistory = updateHistory
exports.ifMayEdit = ifMayEdit
exports.parseNoteIdFromSocketAsync = parseNoteIdFromSocketAsync
exports.disconnectProcessQueue = disconnectProcessQueue
exports.users = users
exports.getUserPool = getUserPool
exports.notes = notes
exports.getNotePool = getNotePool
exports.getNotePoolSize = getNotePoolSize
exports.isNoteExistsInPool = isNoteExistsInPool
exports.addNote = addNote
exports.getNoteFromNotePool = getNoteFromNotePool
exports.deleteNoteFromPool = deleteNoteFromPool
exports.deleteAllNoteFromPool = deleteAllNoteFromPool
exports.saveRevisionJob = saveRevisionJob