|
|
@@ -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
|
|
|
- },
|
|
|
+ })
|
|
|
+ return { client, promise }
|
|
|
+}
|
|
|
|
|
|
- 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)
|
|
|
- )
|
|
|
- },
|
|
|
+async function getConnectedClients() {
|
|
|
+ return await fetchJson('http://127.0.0.1:3026/clients')
|
|
|
+}
|
|
|
|
|
|
- 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)
|
|
|
- )
|
|
|
- },
|
|
|
+async function countConnectedClients(projectId) {
|
|
|
+ return await fetchJson(
|
|
|
+ `http://127.0.0.1:3026/project/${projectId}/count-connected-clients`
|
|
|
+ )
|
|
|
+}
|
|
|
|
|
|
- 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)
|
|
|
- }
|
|
|
- }
|
|
|
- )
|
|
|
- },
|
|
|
+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
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- disconnectClient(clientId, callback) {
|
|
|
- request.post(
|
|
|
- {
|
|
|
- url: `http://127.0.0.1:3026/client/${clientId}/disconnect`,
|
|
|
- },
|
|
|
- (error, response, data) => callback(error, data)
|
|
|
+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)
|
|
|
)
|
|
|
- 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
|
|
|
- )
|
|
|
- })
|
|
|
+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: 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
|