changeset 201:0dc8909dd90e

Determine web-gateway URL at runtime Web-Client now expects the deploying webserver to respond to GET /gatewayurl with a JSON response pointing the client to its associated Web-Gateway instance. This allows the Web-Client to be built once and distributed for deployment, rather than requiring a rebuild with the GATEWAY_URL environment variable set. If /gatewayUrl is not implemented then the client makes a best- effort guess that the deploying webserver is the Web-Gateway. Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/025022.html
author Andrew Azores <aazores@redhat.com>
date Thu, 14 Sep 2017 07:52:04 -0400
parents 42e34f861a44
children 70ac564886ab
files .s2i/bin/assemble README.md server.js src/app/app.module.js src/app/components/user-prefs/en.locale.yaml src/app/components/user-prefs/user-prefs.controller.js src/app/components/user-prefs/user-prefs.controller.spec.js src/app/components/user-prefs/user-prefs.html src/app/components/user-prefs/user-prefs.service.js src/app/shared/config/config.module.js src/app/shared/config/config.module.spec.js src/tests.webpack.js webpack.config.js
diffstat 13 files changed, 147 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/.s2i/bin/assemble	Wed Sep 13 16:27:00 2017 -0400
+++ b/.s2i/bin/assemble	Thu Sep 14 07:52:04 2017 -0400
@@ -12,12 +12,6 @@
 fi
 
 echo "NODE_ENV=$NODE_ENV"
-echo "GATEWAY_URL=$GATEWAY_URL"
-
-if [ -z "$GATEWAY_URL" ]; then
-    echo "GATEWAY_URL must be set"
-    exit 1
-fi
 
 NODE_ENV=development npm install
 npm run build
--- a/README.md	Wed Sep 13 16:27:00 2017 -0400
+++ b/README.md	Thu Sep 14 07:52:04 2017 -0400
@@ -20,11 +20,12 @@
         "clientId": "BarClientId"
     }
 
-## Environments
+The webserver which serves the client should expose a path `/gatewayurl`, which returns
+a string representation of a JSON object like this:
+`{ "gatewayUrl": "http://example.com:1234/" }`, where the URL returned points to the
+root of the associated Thermostat Web-Gateway server.
 
-`GATEWAY_URL` should be set to the URL of a Thermostat Web-Gateway instance.
-The default value of this variable is the default URL for the web-client
-mockapi server.
+## Environments
 
 Expected values for `NODE_ENV`:
 
--- a/server.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/server.js	Thu Sep 14 07:52:04 2017 -0400
@@ -45,3 +45,9 @@
 app.listen(app.get('port'), app.get('host'), function () {
   console.info('Server started on http://' + app.get('host') + ':' + app.get('port'));
 });
+
+app.get('/gatewayurl', function (req, res, next) {
+  res.setHeader('Content-Type', 'application/json');
+  res.send(JSON.stringify({ gatewayUrl: 'http://localhost:8888/' }));
+  next();
+});
--- a/src/app/app.module.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/app/app.module.js	Thu Sep 14 07:52:04 2017 -0400
@@ -33,14 +33,8 @@
 import 'bootstrap';
 import 'bootstrap-switch';
 
-import configModule from 'shared/config/config.module.js';
 import {default as authModule, config as authModBootstrap} from 'components/auth/auth.module.js';
-import filters from 'shared/filters/filters.module.js';
-import services from 'shared/services/services.module.js';
-import components from 'shared/components/components.module.js';
-import appRouting from './app.routing.js';
 import authInterceptorFactory from './auth-interceptor.factory.js';
-import AppController from './app.controller.js';
 
 require.ensure([], () => {
   require('angular-patternfly/node_modules/patternfly/dist/css/patternfly.css');
@@ -49,42 +43,63 @@
   require('scss/app.scss');
 });
 
-export const appModule = angular
-  .module('appModule', [
-    'ui.router',
-    'ui.bootstrap',
-    angularTranslate,
-    configModule,
-    authModule,
-    // non-core modules
-    services,
-    filters,
-    components,
-    appRouting,
-    authInterceptorFactory,
-    AppController
-  ])
-  .config($httpProvider => {
-    'ngInject';
-    $httpProvider.interceptors.push(authInterceptorFactory);
-  })
-  .config($translateProvider => {
-    'ngInject';
-    $translateProvider
-      .useSanitizeValueStrategy('escapeParameters')
-      .addInterpolation('$translateMessageFormatInterpolation')
-      .registerAvailableLanguageKeys(['en'], {
-        'en_*': 'en'
-      })
-      .fallbackLanguage('en')
-      .determinePreferredLanguage();
+function initializeApplication () {
+  return angular
+    .module('appModule', [
+      'ui.router',
+      'ui.bootstrap',
+      angularTranslate,
+      authModule,
+      // non-core modules
+      require('./app.routing.js').default,
+      require('./app.controller.js').default,
+      authInterceptorFactory
+    ])
+    .config($httpProvider => {
+      'ngInject';
+      $httpProvider.interceptors.push(authInterceptorFactory);
+    })
+    .config($translateProvider => {
+      'ngInject';
+      $translateProvider
+        .useSanitizeValueStrategy('escapeParameters')
+        .addInterpolation('$translateMessageFormatInterpolation')
+        .registerAvailableLanguageKeys(['en'], {
+          'en_*': 'en'
+        })
+        .fallbackLanguage('en')
+        .determinePreferredLanguage();
 
-    let req = require.context('./', true, /\/([a-z]{2})\.locale\.yaml$/);
-    req.keys().map(key => {
-      let lang = /\/([a-z]{2})\.locale\.yaml$/.exec(key)[1];
-      let translations = req(key);
-      $translateProvider.translations(lang, translations);
+      let req = require.context('./', true, /\/([a-z]{2})\.locale\.yaml$/);
+      req.keys().map(key => {
+        let lang = /\/([a-z]{2})\.locale\.yaml$/.exec(key)[1];
+        let translations = req(key);
+        $translateProvider.translations(lang, translations);
+      });
+    })
+    .name;
+}
+
+/* istanbul ignore next */
+if (window.tmsGatewayUrl) {
+  let appModule = initializeApplication();
+  authModBootstrap(process.env.NODE_ENV, () => angular.element(() => angular.bootstrap(document, [appModule])));
+} else {
+  $.get('/gatewayurl')
+    .done(res => {
+      window.tmsGatewayUrl = res.gatewayUrl;
+    })
+    .fail(() => {
+      let url = require('url');
+      let parsed = url.parse(window.location.href);
+      let gateway = {
+        protocol: parsed.protocol,
+        host: parsed.host
+      };
+      window.tmsGatewayUrl = url.format(gateway);
+    })
+    .always(() => {
+      let appModule = initializeApplication();
+      authModBootstrap(process.env.NODE_ENV, () => angular.element(() => angular.bootstrap(document, [appModule])));
     });
-  });
-
-authModBootstrap(process.env.NODE_ENV, () => angular.element(() => angular.bootstrap(document, [appModule.name])));
+}
--- a/src/app/components/user-prefs/en.locale.yaml	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/app/components/user-prefs/en.locale.yaml	Thu Sep 14 07:52:04 2017 -0400
@@ -2,6 +2,8 @@
   USE_TLS: Use TLS
   TLS_DESCRIPTION: Use secure connections to the web-gateway and command channel.
 
+  GATEWAY_URL: Web-Gateway URL
+
   RESTART_NOTE: >
     <i>App restart is recommended after changing this setting.
     You may refresh the application in your browser, or close the tab
--- a/src/app/components/user-prefs/user-prefs.controller.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/app/components/user-prefs/user-prefs.controller.js	Thu Sep 14 07:52:04 2017 -0400
@@ -28,13 +28,14 @@
 import service from './user-prefs.service.js';
 
 class UserPreferencesController {
-  constructor (userPrefsService) {
+  constructor (userPrefsService, gatewayUrl) {
     'ngInject';
     this._userPrefsService = userPrefsService;
+    this.gatewayUrl = gatewayUrl;
 
     let tlsSwitch = angular.element('#tlsSwitch');
     tlsSwitch.bootstrapSwitch();
-    tlsSwitch.bootstrapSwitch('state', userPrefsService.tlsEnabled);
+    tlsSwitch.bootstrapSwitch('state', this._userPrefsService.tlsEnabled);
     tlsSwitch.on('switchChange.bootstrapSwitch', () => {
       this.tlsEnabled = tlsSwitch.bootstrapSwitch('state');
     });
--- a/src/app/components/user-prefs/user-prefs.controller.spec.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/app/components/user-prefs/user-prefs.controller.spec.js	Thu Sep 14 07:52:04 2017 -0400
@@ -45,7 +45,8 @@
       sinon.stub(angular, 'element').returns(bootstrapSwitch);
 
       ctrl = $controller('UserPreferencesController', {
-        userPrefsService: userPrefsSvc
+        userPrefsService: userPrefsSvc,
+        gatewayUrl: 'fake-url'
       });
     });
   });
@@ -84,4 +85,9 @@
     ctrl.tlsEnabled.should.equal('new-state');
   });
 
+  it('should set gatewayUrl', () => {
+    ctrl.should.have.ownProperty('gatewayUrl');
+    ctrl.gatewayUrl.should.equal('fake-url');
+  });
+
 });
--- a/src/app/components/user-prefs/user-prefs.html	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/app/components/user-prefs/user-prefs.html	Thu Sep 14 07:52:04 2017 -0400
@@ -1,4 +1,10 @@
 <div class="container-fluid container-cards-pf">
+
+  <div class="form-group">
+    <label for="gatewayUrl" class="label label-info pull-left" translate>userPrefs.GATEWAY_URL</label>
+    <input name="gatewayUrl" type="text" value="{{$ctrl.gatewayUrl}}" disabled/>
+  </div>
+
   <div class="form-group">
     <label for="tlsSwitch" class="label label-info pull-left" translate>userPrefs.USE_TLS</label>
     <input class="bootstrap-switch pull-right" id="tlsSwitch" name="tlsSwitch"
@@ -12,4 +18,5 @@
       <span translate>userPrefs.RESTART_NOTE</span>
     </p>
   </div>
+
 </div>
--- a/src/app/components/user-prefs/user-prefs.service.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/app/components/user-prefs/user-prefs.service.js	Thu Sep 14 07:52:04 2017 -0400
@@ -39,11 +39,11 @@
   }
 
   get tlsEnabled () {
-    // can't use gatewayUrl value here due to circular reference, but process.env
-    // is not available in test suite
+    // can't use gatewayUrl value here due to circular reference,
+    // and we don't want to reassign window.tmsGatewayUrl in test suite
     /* istanbul ignore next */
     if (!this._storage.hasItem('tlsEnabled')) {
-      let protocol = url.parse(process.env.GATEWAY_URL).protocol;
+      let protocol = url.parse(window.tmsGatewayUrl).protocol;
       this.tlsEnabled = protocol === 'https:';
     }
     return JSON.parse(this._storage.getItem('tlsEnabled'));
--- a/src/app/shared/config/config.module.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/app/shared/config/config.module.js	Thu Sep 14 07:52:04 2017 -0400
@@ -25,20 +25,28 @@
  * exception statement from your version.
  */
 
+import * as url from 'url';
+
 export function cmdChanUrl (gatewayUrl) {
-  if (gatewayUrl.startsWith('http://')) {
-    return 'ws://' + gatewayUrl.substring(7);
-  } else if (gatewayUrl.startsWith('https://')) {
-    return 'wss://' + gatewayUrl.substring(8);
+  if (!gatewayUrl) {
+    throw new Error('gatewayUrl could not be determined');
+  }
+  let parsed = url.parse(gatewayUrl);
+  let protocol = parsed.protocol;
+  if (protocol === 'http:') {
+    parsed.protocol = 'ws:';
+  } else if (protocol === 'https:') {
+    parsed.protocol = 'wss:';
   } else {
-    throw new Error('GATEWAY_URL protocol unknown');
+    throw new Error('gatewayUrl protocol unknown');
   }
+  return url.format(parsed);
 };
 
 export default angular
   .module('configModule', [])
   .constant('environment', process.env.NODE_ENV)
   .constant('debug', process.env.DEBUG)
-  .value('gatewayUrl', process.env.GATEWAY_URL)
-  .value('commandChannelUrl', cmdChanUrl(process.env.GATEWAY_URL))
+  .value('gatewayUrl', window.tmsGatewayUrl)
+  .value('commandChannelUrl', cmdChanUrl(window.tmsGatewayUrl))
   .name;
--- a/src/app/shared/config/config.module.spec.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/app/shared/config/config.module.spec.js	Thu Sep 14 07:52:04 2017 -0400
@@ -120,18 +120,45 @@
     });
 
     it('should yield ws:// URL when gateway URL is http://', () => {
-      fn('http://example.com:8888').should.equal('ws://example.com:8888');
+      fn('http://example.com:8888/').should.equal('ws://example.com:8888/');
     });
 
     it('should yield wss:// URL when gateway URL is https://', () => {
-      fn('https://example.com:8888').should.equal('wss://example.com:8888');
+      fn('https://example.com:8888/').should.equal('wss://example.com:8888/');
+    });
+
+    it('should throw error when gateway URL is undefined', done => {
+      try {
+        fn(undefined);
+      } catch (e) {
+        e.message.should.equal('gatewayUrl could not be determined');
+        done();
+      }
+    });
+
+    it('should throw error when gateway URL is empty string', done => {
+      try {
+        fn('');
+      } catch (e) {
+        e.message.should.equal('gatewayUrl could not be determined');
+        done();
+      }
+    });
+
+    it('should throw error when gateway URL string is not URL formatted', done => {
+      try {
+        fn('this is not a url');
+      } catch (e) {
+        e.message.should.equal('gatewayUrl protocol unknown');
+        done();
+      }
     });
 
     it('should throw error when gateway URL protocol is unknown', done => {
       try {
         fn('ftp://example.com');
       } catch (e) {
-        e.message.should.equal('GATEWAY_URL protocol unknown');
+        e.message.should.equal('gatewayUrl protocol unknown');
         done();
       }
     });
--- a/src/tests.webpack.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/src/tests.webpack.js	Thu Sep 14 07:52:04 2017 -0400
@@ -29,6 +29,8 @@
 import 'angular-mocks/angular-mocks';
 import 'babel-polyfill';
 
+window.tmsGatewayUrl = 'http://localhost:8888/';
+
 const context = require.context('./app', true, /\.js$/);
 
 context.keys().forEach(context);
--- a/webpack.config.js	Wed Sep 13 16:27:00 2017 -0400
+++ b/webpack.config.js	Thu Sep 14 07:52:04 2017 -0400
@@ -120,8 +120,7 @@
   config.plugins.push(
     new webpack.EnvironmentPlugin({
       NODE_ENV: 'development',
-      DEBUG: false,
-      GATEWAY_URL: 'http://localhost:8888'
+      DEBUG: false
     })
   );
 
@@ -158,7 +157,14 @@
     contentBase: './src/assets',
     stats: 'minimal',
     inline: true,
-    historyApiFallback: true
+    historyApiFallback: true,
+    setup: function (app) {
+      app.get('/gatewayurl', function (req, res, next) {
+        res.setHeader('Content-Type', 'application/json');
+        res.send(JSON.stringify({ gatewayUrl: 'http://localhost:8888/' }));
+        next();
+      });
+    }
   };
 
   return config;