mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
[real-time] Promisify RealTimeClient (#30500)
* Define `Client` at the end of the file * Promisify `setSession` * Promisify `setAnonSession` * Remove unused `unsetSession` * Promisify `connect` * Promisify `getConnectedClients`, add `@overleaf/fetch-utils` * Promisify `countConnectedClients` * Promisify `getConnectedClient` * Promisify `disconnectClient` * Promisify `disconnectAllClients` * Remove decaffeinate comment GitOrigin-RevId: 4b0b376cdae5b1b69fe7e66b131d4b2ec7059eaa
This commit is contained in:
1
package-lock.json
generated
1
package-lock.json
generated
@@ -57955,6 +57955,7 @@
|
||||
"zod-validation-error": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@overleaf/fetch-utils": "*",
|
||||
"chai": "^4.3.6",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"cookie-signature": "^1.1.0",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"zod-validation-error": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@overleaf/fetch-utils": "*",
|
||||
"chai": "^4.3.6",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"cookie-signature": "^1.1.0",
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
/* 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')
|
||||
const { callbackify } = require('node:util')
|
||||
const { fetchJson, fetchNothing } = require('@overleaf/fetch-utils')
|
||||
|
||||
io.util.request = function () {
|
||||
const xhr = new XMLHttpRequest()
|
||||
@@ -34,142 +22,132 @@ io.util.request = function () {
|
||||
return xhr
|
||||
}
|
||||
|
||||
module.exports = Client = {
|
||||
cookie: null,
|
||||
async function setSession(session) {
|
||||
const sessionId = uid(24)
|
||||
session.cookie = {}
|
||||
|
||||
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()
|
||||
})
|
||||
},
|
||||
await rclient.set('sess:' + sessionId, JSON.stringify(session))
|
||||
|
||||
setAnonSession(projectId, anonymousAccessToken, callback) {
|
||||
Client.setSession(
|
||||
{
|
||||
anonTokenAccess: {
|
||||
[projectId]: anonymousAccessToken,
|
||||
},
|
||||
},
|
||||
callback
|
||||
)
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
unsetSession(callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
Client.cookie = null
|
||||
return callback()
|
||||
},
|
||||
async function setAnonSession(projectId, anonymousAccessToken) {
|
||||
await Client.promises.setSession({
|
||||
anonTokenAccess: {
|
||||
[projectId]: anonymousAccessToken,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
function connect(projectId) {
|
||||
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
|
||||
})
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
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)
|
||||
reject(err)
|
||||
}, 10)
|
||||
})
|
||||
|
||||
client.on('joinProjectResponse', resp => {
|
||||
const { publicId, project, permissionsLevel, protocolVersion } = resp
|
||||
client.publicId = publicId
|
||||
callback(null, project, permissionsLevel, protocolVersion)
|
||||
resolve({ project, permissionsLevel, protocolVersion, client })
|
||||
})
|
||||
})
|
||||
return { client, promise }
|
||||
}
|
||||
|
||||
async function getConnectedClients() {
|
||||
return await fetchJson('http://127.0.0.1:3026/clients')
|
||||
}
|
||||
|
||||
async function countConnectedClients(projectId) {
|
||||
return await fetchJson(
|
||||
`http://127.0.0.1:3026/project/${projectId}/count-connected-clients`
|
||||
)
|
||||
}
|
||||
|
||||
async function getConnectedClient(clientId) {
|
||||
try {
|
||||
return await fetchJson(`http://127.0.0.1:3026/clients/${clientId}`)
|
||||
} catch (err) {
|
||||
if (err.info?.status === 404) throw new Error('not found')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function disconnectClient(clientId) {
|
||||
await fetchNothing(`http://127.0.0.1:3026/client/${clientId}/disconnect`, {
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
async function disconnectAllClients() {
|
||||
const clients = await Client.promises.getConnectedClients()
|
||||
await Promise.all(
|
||||
clients.map(clientView =>
|
||||
Client.promises.disconnectClient(clientView.client_id)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const Client = {
|
||||
cookie: null,
|
||||
setSession: callbackify(setSession),
|
||||
setAnonSession: callbackify(setAnonSession),
|
||||
connect: (projectId, callback) => {
|
||||
const { client, promise } = connect(projectId)
|
||||
if (callback) {
|
||||
promise
|
||||
.then(({ project, permissionsLevel, protocolVersion }) =>
|
||||
callback(null, project, permissionsLevel, protocolVersion)
|
||||
)
|
||||
.catch(err => callback(err))
|
||||
}
|
||||
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)
|
||||
)
|
||||
},
|
||||
|
||||
countConnectedClients(projectId, callback) {
|
||||
request.get(
|
||||
{
|
||||
url: `http://127.0.0.1:3026/project/${projectId}/count-connected-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
|
||||
)
|
||||
})
|
||||
getConnectedClients: callbackify(getConnectedClients),
|
||||
countConnectedClients: callbackify(countConnectedClients),
|
||||
getConnectedClient: callbackify(getConnectedClient),
|
||||
disconnectClient: callbackify(disconnectClient),
|
||||
disconnectAllClients: callbackify(disconnectAllClients),
|
||||
promises: {
|
||||
setSession,
|
||||
setAnonSession,
|
||||
connect: async projectId => {
|
||||
const { client, promise } = connect(projectId)
|
||||
const { project, permissionsLevel, protocolVersion } = await promise
|
||||
return { project, permissionsLevel, protocolVersion, client }
|
||||
},
|
||||
getConnectedClients,
|
||||
countConnectedClients,
|
||||
getConnectedClient,
|
||||
disconnectClient,
|
||||
disconnectAllClients,
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Client
|
||||
|
||||
Reference in New Issue
Block a user