first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let FixturesManager
const RealTimeClient = require('./RealTimeClient')
const MockWebServer = require('./MockWebServer')
const MockDocUpdaterServer = require('./MockDocUpdaterServer')
module.exports = FixturesManager = {
setUpProject(options, callback) {
if (options == null) {
options = {}
}
if (callback == null) {
callback = function () {}
}
if (!options.user_id) {
options.user_id = FixturesManager.getRandomId()
}
if (!options.project_id) {
options.project_id = FixturesManager.getRandomId()
}
if (!options.project) {
options.project = { name: 'Test Project' }
}
let {
project_id: projectId,
user_id: userId,
privilegeLevel,
project,
publicAccess,
userMetadata,
anonymousAccessToken,
} = options
if (privilegeLevel === 'owner') {
project.owner = { _id: userId }
} else {
project.owner = { _id: '404404404404404404404404' }
}
const privileges = {}
privileges[userId] = privilegeLevel
if (publicAccess) {
anonymousAccessToken =
anonymousAccessToken || FixturesManager.getRandomId()
privileges[anonymousAccessToken] = publicAccess
}
const metadataByUser = {}
metadataByUser[userId] = userMetadata
MockWebServer.createMockProject(
projectId,
privileges,
project,
metadataByUser
)
return MockWebServer.run(error => {
if (error != null) {
throw error
}
return RealTimeClient.setSession(
{
user: {
_id: userId,
first_name: 'Joe',
last_name: 'Bloggs',
},
},
error => {
if (error != null) {
throw error
}
return callback(null, {
project_id: projectId,
user_id: userId,
privilegeLevel,
project,
anonymousAccessToken,
})
}
)
})
},
setUpDoc(projectId, options, callback) {
if (options == null) {
options = {}
}
if (callback == null) {
callback = function () {}
}
if (!options.doc_id) {
options.doc_id = FixturesManager.getRandomId()
}
if (!options.lines) {
options.lines = ['doc', 'lines']
}
if (!options.version) {
options.version = 42
}
if (!options.ops) {
options.ops = ['mock', 'ops']
}
const { doc_id: docId, lines, version, ops, ranges } = options
MockDocUpdaterServer.createMockDoc(projectId, docId, {
lines,
version,
ops,
ranges,
})
return MockDocUpdaterServer.run(error => {
if (error != null) {
throw error
}
return callback(null, {
project_id: projectId,
doc_id: docId,
lines,
version,
ops,
})
})
},
setUpEditorSession(options, callback) {
FixturesManager.setUpProject(options, (err, detailsProject) => {
if (err) return callback(err)
FixturesManager.setUpDoc(
detailsProject.project_id,
options,
(err, detailsDoc) => {
if (err) return callback(err)
callback(null, Object.assign({}, detailsProject, detailsDoc))
}
)
})
},
getRandomId() {
return require('node:crypto')
.createHash('sha1')
.update(Math.random().toString())
.digest('hex')
.slice(0, 24)
},
}

View File

@@ -0,0 +1,91 @@
/* eslint-disable
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let MockDocUpdaterServer
const sinon = require('sinon')
const express = require('express')
module.exports = MockDocUpdaterServer = {
docs: {},
createMockDoc(projectId, docId, data) {
return (MockDocUpdaterServer.docs[`${projectId}:${docId}`] = data)
},
getDocument(projectId, docId, fromVersion, callback) {
if (callback == null) {
callback = function () {}
}
return callback(null, MockDocUpdaterServer.docs[`${projectId}:${docId}`])
},
deleteProject: sinon.stub().callsArg(1),
getDocumentRequest(req, res, next) {
const { project_id: projectId, doc_id: docId } = req.params
let { fromVersion } = req.query
fromVersion = parseInt(fromVersion, 10)
return MockDocUpdaterServer.getDocument(
projectId,
docId,
fromVersion,
(error, data) => {
if (error != null) {
return next(error)
}
if (!data) {
return res.sendStatus(404)
}
return res.json(data)
}
)
},
deleteProjectRequest(req, res, next) {
const { project_id: projectId } = req.params
return MockDocUpdaterServer.deleteProject(projectId, error => {
if (error != null) {
return next(error)
}
return res.sendStatus(204)
})
},
running: false,
run(callback) {
if (callback == null) {
callback = function () {}
}
if (MockDocUpdaterServer.running) {
return callback()
}
const app = express()
app.get(
'/project/:project_id/doc/:doc_id',
MockDocUpdaterServer.getDocumentRequest
)
app.delete(
'/project/:project_id',
MockDocUpdaterServer.deleteProjectRequest
)
return app
.listen(3003, error => {
MockDocUpdaterServer.running = true
return callback(error)
})
.on('error', error => {
console.error('error starting MockDocUpdaterServer:', error.message)
return process.exit(1)
})
},
}
sinon.spy(MockDocUpdaterServer, 'getDocument')

View File

@@ -0,0 +1,106 @@
/* eslint-disable
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let MockWebServer
const sinon = require('sinon')
const express = require('express')
const bodyParser = require('body-parser')
module.exports = MockWebServer = {
projects: {},
privileges: {},
userMetadata: {},
createMockProject(projectId, privileges, project, metadataByUser) {
MockWebServer.privileges[projectId] = privileges
MockWebServer.userMetadata[projectId] = metadataByUser
return (MockWebServer.projects[projectId] = project)
},
inviteUserToProject(projectId, user, privileges) {
MockWebServer.privileges[projectId][user._id] = privileges
MockWebServer.userMetadata[projectId][user._id] = user
},
joinProject(projectId, userId, anonymousAccessToken, callback) {
if (callback == null) {
callback = function () {}
}
const project = MockWebServer.projects[projectId]
const privilegeLevel =
MockWebServer.privileges[projectId]?.[userId] ||
MockWebServer.privileges[projectId]?.[anonymousAccessToken]
const userMetadata = MockWebServer.userMetadata[projectId]?.[userId]
return callback(null, project, privilegeLevel, userMetadata)
},
joinProjectRequest(req, res, next) {
const { project_id: projectId } = req.params
const { anonymousAccessToken, userId } = req.body
if (projectId === '404404404404404404404404') {
// not-found
return res.status(404).send()
}
if (projectId === '403403403403403403403403') {
// forbidden
return res.status(403).send()
}
if (projectId === '429429429429429429429429') {
// rate-limited
return res.status(429).send()
} else {
return MockWebServer.joinProject(
projectId,
userId,
anonymousAccessToken,
(error, project, privilegeLevel, userMetadata) => {
if (error != null) {
return next(error)
}
if (!project) {
return res.sendStatus(404)
}
return res.json({
project,
privilegeLevel,
isRestrictedUser: !!userMetadata?.isRestrictedUser,
isTokenMember: !!userMetadata?.isTokenMember,
isInvitedMember: !!userMetadata?.isInvitedMember,
})
}
)
}
},
running: false,
run(callback) {
if (callback == null) {
callback = function () {}
}
if (MockWebServer.running) {
return callback()
}
const app = express()
app.use(bodyParser.json())
app.post('/project/:project_id/join', MockWebServer.joinProjectRequest)
return app
.listen(3000, error => {
MockWebServer.running = true
return callback(error)
})
.on('error', error => {
console.error('error starting MockWebServer:', error.message)
return process.exit(1)
})
},
}
sinon.spy(MockWebServer, 'joinProject')

View File

@@ -0,0 +1,165 @@
/* eslint-disable
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let Client
const { XMLHttpRequest } = require('../../libs/XMLHttpRequest')
const io = require('socket.io-client')
const async = require('async')
const request = require('request')
const Settings = require('@overleaf/settings')
const redis = require('@overleaf/redis-wrapper')
const rclient = redis.createClient(Settings.redis.websessions)
const uid = require('uid-safe').sync
const signature = require('cookie-signature')
io.util.request = function () {
const xhr = new XMLHttpRequest()
const _open = xhr.open
xhr.open = function () {
_open.apply(xhr, arguments)
if (Client.cookie != null) {
return xhr.setRequestHeader('Cookie', Client.cookie)
}
}
return xhr
}
module.exports = Client = {
cookie: null,
setSession(session, callback) {
if (callback == null) {
callback = function () {}
}
const sessionId = uid(24)
session.cookie = {}
return rclient.set('sess:' + sessionId, JSON.stringify(session), error => {
if (error != null) {
return callback(error)
}
Client.cookieSignedWith = {}
// prepare cookie strings for all supported session secrets
for (const secretName of [
'sessionSecret',
'sessionSecretFallback',
'sessionSecretUpcoming',
]) {
const secret = Settings.security[secretName]
const cookieKey = 's:' + signature.sign(sessionId, secret)
Client.cookieSignedWith[secretName] =
`${Settings.cookieName}=${cookieKey}`
}
// default to the current session secret
Client.cookie = Client.cookieSignedWith.sessionSecret
return callback()
})
},
setAnonSession(projectId, anonymousAccessToken, callback) {
Client.setSession(
{
anonTokenAccess: {
[projectId]: anonymousAccessToken,
},
},
callback
)
},
unsetSession(callback) {
if (callback == null) {
callback = function () {}
}
Client.cookie = null
return callback()
},
connect(projectId, callback) {
const client = io.connect('http://127.0.0.1:3026', {
'force new connection': true,
query: new URLSearchParams({ projectId }).toString(),
})
let disconnected = false
client.on('disconnect', () => {
disconnected = true
})
client.on('connectionRejected', err => {
// Wait for disconnect ahead of continuing with the test sequence.
setTimeout(() => {
if (!disconnected) {
throw new Error('should disconnect after connectionRejected')
}
callback(err)
}, 10)
})
client.on('joinProjectResponse', resp => {
const { publicId, project, permissionsLevel, protocolVersion } = resp
client.publicId = publicId
callback(null, project, permissionsLevel, protocolVersion)
})
return client
},
getConnectedClients(callback) {
if (callback == null) {
callback = function () {}
}
return request.get(
{
url: 'http://127.0.0.1:3026/clients',
json: true,
},
(error, response, data) => callback(error, data)
)
},
getConnectedClient(clientId, callback) {
if (callback == null) {
callback = function () {}
}
return request.get(
{
url: `http://127.0.0.1:3026/clients/${clientId}`,
json: true,
},
(error, response, data) => {
if (response?.statusCode === 404) {
callback(new Error('not found'))
} else {
callback(error, data)
}
}
)
},
disconnectClient(clientId, callback) {
request.post(
{
url: `http://127.0.0.1:3026/client/${clientId}/disconnect`,
},
(error, response, data) => callback(error, data)
)
return null
},
disconnectAllClients(callback) {
return Client.getConnectedClients((error, clients) => {
if (error) return callback(error)
async.each(
clients,
(clientView, cb) => Client.disconnectClient(clientView.client_id, cb),
callback
)
})
},
}

View File

@@ -0,0 +1,61 @@
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS103: Rewrite code to no longer use __guard__
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const app = require('../../../../app')
const logger = require('@overleaf/logger')
const Settings = require('@overleaf/settings')
module.exports = {
running: false,
initing: false,
callbacks: [],
ensureRunning(callback) {
if (callback == null) {
callback = function () {}
}
if (this.running) {
return callback()
} else if (this.initing) {
return this.callbacks.push(callback)
} else {
this.initing = true
this.callbacks.push(callback)
return app.listen(
__guard__(
Settings.internal != null ? Settings.internal.realtime : undefined,
x => x.port
),
'127.0.0.1',
error => {
if (error != null) {
throw error
}
this.running = true
logger.info('clsi running in dev mode')
return (() => {
const result = []
for (callback of Array.from(this.callbacks)) {
result.push(callback())
}
return result
})()
}
)
}
},
}
function __guard__(value, transform) {
return typeof value !== 'undefined' && value !== null
? transform(value)
: undefined
}