Sfoglia il codice sorgente

[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
Antoine Clausse 7 mesi fa
parent
commit
7dc937b0f0

+ 1 - 0
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",

+ 1 - 0
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",

+ 107 - 129
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
-  },
+  })
+  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