diff --git a/package-lock.json b/package-lock.json index 02b77ff45f..acac4f1602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/services/real-time/package.json b/services/real-time/package.json index d416fd099a..de90f08bad 100644 --- a/services/real-time/package.json +++ b/services/real-time/package.json @@ -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", diff --git a/services/real-time/test/acceptance/js/helpers/RealTimeClient.js b/services/real-time/test/acceptance/js/helpers/RealTimeClient.js index 6cc7001896..4c6bd89223 100644 --- a/services/real-time/test/acceptance/js/helpers/RealTimeClient.js +++ b/services/real-time/test/acceptance/js/helpers/RealTimeClient.js @@ -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