Преглед изворни кода

Merge pull request #2713 from overleaf/jpa-custom-wsurl-for-beta-users

[misc] custom wsUrl for beta users

GitOrigin-RevId: e731ae7245e9c5586ae82cbc4c6716a74e56f2c9
Jakob Ackermann пре 6 година
родитељ
комит
155b22caf9

+ 18 - 5
services/web/app/src/Features/Project/ProjectController.js

@@ -622,11 +622,6 @@ const ProjectController = {
 
 
     const projectId = req.params.Project_id
     const projectId = req.params.Project_id
 
 
-    // record failures to load the custom websocket
-    if ((req.query != null ? req.query.ws : undefined) === 'fallback') {
-      metrics.inc('load-editor-ws-fallback')
-    }
-
     async.auto(
     async.auto(
       {
       {
         project(cb) {
         project(cb) {
@@ -777,6 +772,23 @@ const ProjectController = {
               allowedFreeTrial = !!subscription.freeTrial.allowed || true
               allowedFreeTrial = !!subscription.freeTrial.allowed || true
             }
             }
 
 
+            let wsUrl = Settings.wsUrl
+            let metricName = 'load-editor-ws'
+            if (user.betaProgram && Settings.wsUrlBeta !== undefined) {
+              wsUrl = Settings.wsUrlBeta
+              metricName += '-beta'
+            }
+            if (req.query && req.query.ws === 'fallback') {
+              // `?ws=fallback` will connect to the bare origin, and ignore
+              //   the custom wsUrl. Hence it must load the client side
+              //   javascript from there too.
+              // Not resetting it here would possibly load a socket.io v2
+              //  client and connect to a v0 endpoint.
+              wsUrl = undefined
+              metricName += '-fallback'
+            }
+            metrics.inc(metricName)
+
             res.render('project/editor', {
             res.render('project/editor', {
               title: project.name,
               title: project.name,
               priority_title: true,
               priority_title: true,
@@ -831,6 +843,7 @@ const ProjectController = {
               brandVariation,
               brandVariation,
               allowedImageNames: Settings.allowedImageNames || [],
               allowedImageNames: Settings.allowedImageNames || [],
               gitBridgePublicBaseUrl: Settings.gitBridgePublicBaseUrl,
               gitBridgePublicBaseUrl: Settings.gitBridgePublicBaseUrl,
+              wsUrl,
               showSupport: Features.hasFeature('support')
               showSupport: Features.hasFeature('support')
             })
             })
             timer.done()
             timer.done()

+ 1 - 1
services/web/app/views/layout.pug

@@ -66,7 +66,7 @@ html(
 		script.
 		script.
 			window.sharelatex = {
 			window.sharelatex = {
 				siteUrl: '#{settings.siteUrl}',
 				siteUrl: '#{settings.siteUrl}',
-				wsUrl: '#{settings.wsUrl}',
+				wsUrl: '#{wsUrl}',
 			};
 			};
 			window.ab = {};
 			window.ab = {};
 			window.user_id = '#{getLoggedInUserId()}';
 			window.user_id = '#{getLoggedInUserId()}';

+ 1 - 1
services/web/app/views/project/editor.pug

@@ -160,7 +160,7 @@ block content
 			window.overallThemes = JSON.parse('!{StringHelper.stringifyJsonForScript(overallThemes)}');
 			window.overallThemes = JSON.parse('!{StringHelper.stringifyJsonForScript(overallThemes)}');
 
 
 block foot-scripts
 block foot-scripts
-	script(type="text/javascript" src='/socket.io/socket.io.js')
+	script(type="text/javascript" src=(wsUrl || '/socket.io') + '/socket.io.js')
 	script(src=mathJaxPath)
 	script(src=mathJaxPath)
 	script(src=buildJsPath('libraries.js'))
 	script(src=buildJsPath('libraries.js'))
 	script(src=buildJsPath('ide.js'))
 	script(src=buildJsPath('ide.js'))

+ 1 - 0
services/web/config/settings.defaults.coffee

@@ -196,6 +196,7 @@ module.exports = settings =
 	
 	
 	# Optional separate location for websocket connections, if unset defaults to siteUrl.
 	# Optional separate location for websocket connections, if unset defaults to siteUrl.
 	wsUrl: process.env['WEBSOCKET_URL']
 	wsUrl: process.env['WEBSOCKET_URL']
+	wsUrlBeta: process.env['WEBSOCKET_URL_BETA']
 
 
 	# cookie domain
 	# cookie domain
 	# use full domain for cookies to only be accessible from that domain,
 	# use full domain for cookies to only be accessible from that domain,

+ 3 - 1
services/web/frontend/js/ide/connection/ConnectionManager.js

@@ -101,9 +101,11 @@ define([], function() {
 
 
         // initial connection attempt
         // initial connection attempt
         this.updateConnectionManagerState('connecting')
         this.updateConnectionManagerState('connecting')
+        const parsedURL = new URL(this.wsUrl || '/socket.io', window.location)
         this.ide.socket = io.connect(
         this.ide.socket = io.connect(
-          this.wsUrl,
+          parsedURL.origin,
           {
           {
+            resource: parsedURL.pathname,
             reconnect: false,
             reconnect: false,
             'connect timeout': 30 * 1000,
             'connect timeout': 30 * 1000,
             'force new connection': true
             'force new connection': true

+ 1 - 0
services/web/modules/launchpad/app/src/LaunchpadController.js

@@ -66,6 +66,7 @@ module.exports = LaunchpadController = {
           }
           }
           if (user && user.isAdmin) {
           if (user && user.isAdmin) {
             return res.render(Path.resolve(__dirname, '../views/launchpad'), {
             return res.render(Path.resolve(__dirname, '../views/launchpad'), {
+              wsUrl: Settings.wsUrl,
               adminUserExists,
               adminUserExists,
               authMethod
               authMethod
             })
             })

+ 1 - 1
services/web/modules/launchpad/app/views/launchpad.pug

@@ -9,7 +9,7 @@ block content
 			authMethod: "!{authMethod}"
 			authMethod: "!{authMethod}"
 		}
 		}
 
 
-	script(type="text/javascript" src='/socket.io/socket.io.js')
+	script(type="text/javascript" src=(wsUrl || '/socket.io') + '/socket.io.js')
 
 
 	style.
 	style.
 		hr { margin-bottom: 5px; }
 		hr { margin-bottom: 5px; }

+ 1 - 0
services/web/modules/launchpad/test/unit/src/LaunchpadControllerTests.js

@@ -171,6 +171,7 @@ describe('LaunchpadController', function() {
           this.res.render.callCount.should.equal(1)
           this.res.render.callCount.should.equal(1)
           return this.res.render
           return this.res.render
             .calledWith(viewPath, {
             .calledWith(viewPath, {
+              wsUrl: undefined,
               adminUserExists: true,
               adminUserExists: true,
               authMethod: 'local'
               authMethod: 'local'
             })
             })

+ 82 - 6
services/web/test/unit/src/Project/ProjectControllerTests.js

@@ -129,6 +129,12 @@ describe('ProjectController', function() {
         }
         }
       }
       }
     ])
     ])
+    this.Metrics = {
+      Timer: class {
+        done() {}
+      },
+      inc: sinon.stub()
+    }
 
 
     this.ProjectController = SandboxedModule.require(MODULE_PATH, {
     this.ProjectController = SandboxedModule.require(MODULE_PATH, {
       globals: {
       globals: {
@@ -140,12 +146,7 @@ describe('ProjectController', function() {
           log() {},
           log() {},
           err() {}
           err() {}
         },
         },
-        'metrics-sharelatex': {
-          Timer: class {
-            done() {}
-          },
-          inc() {}
-        },
+        'metrics-sharelatex': this.Metrics,
         '@overleaf/o-error/http': HttpErrors,
         '@overleaf/o-error/http': HttpErrors,
         './ProjectDeleter': this.ProjectDeleter,
         './ProjectDeleter': this.ProjectDeleter,
         './ProjectDuplicator': this.ProjectDuplicator,
         './ProjectDuplicator': this.ProjectDuplicator,
@@ -1145,6 +1146,81 @@ describe('ProjectController', function() {
       }
       }
       this.ProjectController.loadEditor(this.req, this.res)
       this.ProjectController.loadEditor(this.req, this.res)
     })
     })
+
+    describe('wsUrl', function() {
+      function checkLoadEditorWsMetric(metric) {
+        it(`should inc metric ${metric}`, function(done) {
+          this.res.render = () => {
+            this.Metrics.inc.calledWith(metric).should.equal(true)
+            done()
+          }
+          this.ProjectController.loadEditor(this.req, this.res)
+        })
+      }
+      function checkWsFallback(isBeta) {
+        describe('with ws=fallback', function() {
+          beforeEach(function() {
+            this.req.query = {}
+            this.req.query.ws = 'fallback'
+          })
+          it('should unset the wsUrl', function(done) {
+            this.res.render = (pageName, opts) => {
+              ;(opts.wsUrl || '/socket.io').should.equal('/socket.io')
+              done()
+            }
+            this.ProjectController.loadEditor(this.req, this.res)
+          })
+          checkLoadEditorWsMetric(
+            `load-editor-ws${isBeta ? '-beta' : ''}-fallback`
+          )
+        })
+      }
+
+      beforeEach(function() {
+        this.settings.wsUrl = '/other.socket.io'
+      })
+      it('should set the custom wsUrl', function(done) {
+        this.res.render = (pageName, opts) => {
+          opts.wsUrl.should.equal('/other.socket.io')
+          done()
+        }
+        this.ProjectController.loadEditor(this.req, this.res)
+      })
+      checkLoadEditorWsMetric('load-editor-ws')
+      checkWsFallback(false)
+
+      describe('beta program', function() {
+        beforeEach(function() {
+          this.settings.wsUrlBeta = '/beta.socket.io'
+        })
+        describe('for a normal user', function() {
+          it('should set the normal custom wsUrl', function(done) {
+            this.res.render = (pageName, opts) => {
+              opts.wsUrl.should.equal('/other.socket.io')
+              done()
+            }
+            this.ProjectController.loadEditor(this.req, this.res)
+          })
+          checkLoadEditorWsMetric('load-editor-ws')
+          checkWsFallback(false)
+        })
+
+        describe('for a beta user', function() {
+          beforeEach(function() {
+            this.user.betaProgram = true
+          })
+          it('should set the beta wsUrl', function(done) {
+            this.res.render = (pageName, opts) => {
+              opts.wsUrl.should.equal('/beta.socket.io')
+              done()
+            }
+            this.ProjectController.loadEditor(this.req, this.res)
+          })
+          checkLoadEditorWsMetric('load-editor-ws-beta')
+          checkWsFallback(true)
+        })
+      })
+    })
   })
   })
 
 
   describe('userProjectsJson', function() {
   describe('userProjectsJson', function() {