Mercurial > hg > thermostat-ng > web-client
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;