From e4a83f0704ef3f9b32bd8e9ad50361818141f107 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 15 May 2020 17:27:02 +0200 Subject: [PATCH 1/4] fixed ui environment vars issue --- package.json | 2 +- scripts/serve.ts | 8 ++ server.ts | 200 +++++++++++++++++++++++++++++++++-------------- src/routes.ts | 15 ---- 4 files changed, 150 insertions(+), 75 deletions(-) create mode 100644 scripts/serve.ts delete mode 100644 src/routes.ts diff --git a/package.json b/package.json index 4c6bd31cac..6b87db247a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "pree2e": "yarn run config:prod", "pree2e:ci": "yarn run config:prod", "start": "yarn run start:prod", - "serve": "ng serve", + "serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts", "start:dev": "npm-run-all --parallel config:dev:watch serve", "start:prod": "yarn run build:prod && yarn run serve:ssr", "build": "ng build", diff --git a/scripts/serve.ts b/scripts/serve.ts new file mode 100644 index 0000000000..ff3554804e --- /dev/null +++ b/scripts/serve.ts @@ -0,0 +1,8 @@ +import { environment } from '../src/environments/environment'; + +import * as child from 'child_process'; + +child.spawn( + `ng serve --host ${environment.ui.host} --port ${environment.ui.port} --servePath ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`, + { stdio:'inherit', shell: true } +); diff --git a/server.ts b/server.ts index 31cefe4ec5..131b6bb4ae 100644 --- a/server.ts +++ b/server.ts @@ -1,75 +1,157 @@ -/** - * *** NOTE ON IMPORTING FROM ANGULAR AND NGUNIVERSAL IN THIS FILE *** - * - * If your application uses third-party dependencies, you'll need to - * either use Webpack or the Angular CLI's `bundleDependencies` feature - * in order to adequately package them for use on the server without a - * node_modules directory. - * - * However, due to the nature of the CLI's `bundleDependencies`, importing - * Angular in this file will create a different instance of Angular than - * the version in the compiled application code. This leads to unavoidable - * conflicts. Therefore, please do not explicitly import from @angular or - * @nguniversal in this file. You can export any needed resources - * from your application's main.server.ts file, as seen below with the - * import for `ngExpressEngine`. - */ - import 'zone.js/dist/zone-node'; import 'reflect-metadata'; +import 'rxjs'; +import * as fs from 'fs'; +import * as pem from 'pem'; +import * as https from 'https'; +import * as morgan from 'morgan'; import * as express from 'express'; -import { join } from 'path'; -import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import * as bodyParser from 'body-parser'; +import * as compression from 'compression'; import * as cookieParser from 'cookie-parser'; + +import { enableProdMode, NgModuleFactory, Type } from '@angular/core'; + +import { ngExpressEngine } from '@nguniversal/express-engine'; + +import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import { environment } from './src/environments/environment'; -// Express server -const app = express(); +export function startServer(bootstrap: Type<{}> | NgModuleFactory<{}>) { + const app = express(); -const PORT = environment.ui.port || 4000; -const DIST_FOLDER = join(process.cwd(), 'dist/browser'); + if (environment.production) { + enableProdMode(); + app.use(compression()); + } -// * NOTE :: leave this as require() since this file is built Dynamically from webpack -const { ServerAppModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main'); + app.use(morgan('dev')); -app.use(cookieParser()); -app.use(bodyParser.json()); + app.use(cookieParser()); + app.use(bodyParser.json()); -app.engine('html', (_, options, callback) => - ngExpressEngine({ - bootstrap: ServerAppModuleNgFactory, - providers: [ - { - provide: REQUEST, - useValue: (options as any).req, - }, - { - provide: RESPONSE, - useValue: (options as any).req.res, - }, - provideModuleMap(LAZY_MODULE_MAP) - ], - })(_, options, callback) -); + app.engine('html', (_, options, callback) => + ngExpressEngine({ + bootstrap: bootstrap, + providers: [ + { + provide: REQUEST, + useValue: (options as any).req, + }, + { + provide: RESPONSE, + useValue: (options as any).req.res, + }, + ], + })(_, (options as any), callback) + ); -app.set('view engine', 'html'); -app.set('views', DIST_FOLDER); + app.set('view engine', 'ejs'); + app.set('view engine', 'html'); + app.set('views', 'src'); -// Example Express Rest API endpoints -// app.get('/api/**', (req, res) => { }); -// Serve static files from /browser -app.get('*.*', express.static(DIST_FOLDER, { - maxAge: '1y' -})); + function cacheControl(req, res, next) { + // instruct browser to revalidate + res.header('Cache-Control', environment.cache.control || 'max-age=60'); + next(); + } -// All regular routes use the Universal engine -app.get('*', (req, res) => { - res.render('index', { req }); -}); + app.use('/', cacheControl, express.static('dist', { index: false })); -// Start up the Node server -app.listen(PORT, () => { - console.log(`Node Express server listening on http://localhost:${PORT}`); -}); +// TODO: either remove or update mock backend +// app.get('/data.json', serverApi); +// app.use('/api', createMockApi()); + + function ngApp(req, res) { + const dspace = { + originalRequest: { + headers: req.headers, + body: req.body, + method: req.method, + params: req.params, + reportProgress: req.reportProgress, + withCredentials: req.withCredentials, + responseType: req.responseType, + urlWithParams: req.urlWithParams + } + }; + + function onHandleError(parentZoneDelegate, currentZone, targetZone, error) { + if (!res._headerSent) { + console.warn('Error in SSR, serving for direct CSR. Error details : ', error); + res.sendFile('index.csr.html', { root: './src' }); + } + } + + if (environment.universal.preboot) { + Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => { + res.render('../dist/index.html', { + req, + res, + preboot: environment.universal.preboot, + async: environment.universal.async, + time: environment.universal.time, + baseUrl: environment.ui.nameSpace, + originUrl: environment.ui.baseUrl, + requestUrl: req.originalUrl + }); + }); + } else { + console.log('Universal off, serving for direct CSR'); + res.render('index-csr.ejs', { + root: './src', + scripts: `` + }); + } + } + + function serverStarted() { + console.log(`[${new Date().toTimeString()}] Listening at ${environment.ui.baseUrl}`); + } + + function createHttpsServer(keys) { + https.createServer({ + key: keys.serviceKey, + cert: keys.certificate + }, app).listen(environment.ui.port, environment.ui.host, () => { + serverStarted(); + }); + } + + if (environment.ui.ssl) { + let serviceKey; + try { + serviceKey = fs.readFileSync('./config/ssl/key.pem'); + } catch (e) { + console.warn('Service key not found at ./config/ssl/key.pem'); + } + + let certificate; + try { + certificate = fs.readFileSync('./config/ssl/cert.pem'); + } catch (e) { + console.warn('Certificate not found at ./config/ssl/key.pem'); + } + + if (serviceKey && certificate) { + createHttpsServer({ + serviceKey: serviceKey, + certificate: certificate + }); + } else { + + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + + pem.createCertificate({ + days: 1, + selfSigned: true + }, (error, keys) => { + createHttpsServer(keys); + }); + } + } else { + app.listen(environment.ui.port, environment.ui.host, () => { + serverStarted(); + }); + }} diff --git a/src/routes.ts b/src/routes.ts deleted file mode 100644 index f3e963b25a..0000000000 --- a/src/routes.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const ROUTES: string[] = [ - 'home', - 'items/:id', - 'login', - 'logout', - 'collections/:id', - 'communities/:id', - 'login', - 'logout', - 'search', - 'submit', - 'workspaceitems/:id/edit', - 'workflowitems/:id/edit', - '**' -]; From e7043f87345180f79188b95c3513f0157865a2dc Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 18 May 2020 11:24:00 +0200 Subject: [PATCH 2/4] changes to server.ts --- server.ts | 261 +++++++++++++++++++++++++++++------------------------- 1 file changed, 142 insertions(+), 119 deletions(-) diff --git a/server.ts b/server.ts index 131b6bb4ae..f1e8e1943f 100644 --- a/server.ts +++ b/server.ts @@ -1,3 +1,20 @@ +/** + * *** NOTE ON IMPORTING FROM ANGULAR AND NGUNIVERSAL IN THIS FILE *** + * + * If your application uses third-party dependencies, you'll need to + * either use Webpack or the Angular CLI's `bundleDependencies` feature + * in order to adequately package them for use on the server without a + * node_modules directory. + * + * However, due to the nature of the CLI's `bundleDependencies`, importing + * Angular in this file will create a different instance of Angular than + * the version in the compiled application code. This leads to unavoidable + * conflicts. Therefore, please do not explicitly import from @angular or + * @nguniversal in this file. You can export any needed resources + * from your application's main.server.ts file, as seen below with the + * import for `ngExpressEngine`. + */ + import 'zone.js/dist/zone-node'; import 'reflect-metadata'; import 'rxjs'; @@ -10,148 +27,154 @@ import * as express from 'express'; import * as bodyParser from 'body-parser'; import * as compression from 'compression'; import * as cookieParser from 'cookie-parser'; +import { join } from 'path'; import { enableProdMode, NgModuleFactory, Type } from '@angular/core'; -import { ngExpressEngine } from '@nguniversal/express-engine'; - import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import { environment } from './src/environments/environment'; -export function startServer(bootstrap: Type<{}> | NgModuleFactory<{}>) { - const app = express(); +const DIST_FOLDER = join(process.cwd(), 'dist/browser'); - if (environment.production) { - enableProdMode(); - app.use(compression()); - } +// * NOTE :: leave this as require() since this file is built Dynamically from webpack +const { ServerAppModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main'); - app.use(morgan('dev')); +const app = express(); - app.use(cookieParser()); - app.use(bodyParser.json()); +if (environment.production) { + enableProdMode(); + app.use(compression()); +} - app.engine('html', (_, options, callback) => - ngExpressEngine({ - bootstrap: bootstrap, - providers: [ - { - provide: REQUEST, - useValue: (options as any).req, - }, - { - provide: RESPONSE, - useValue: (options as any).req.res, - }, - ], - })(_, (options as any), callback) - ); +app.use(morgan('dev')); - app.set('view engine', 'ejs'); - app.set('view engine', 'html'); - app.set('views', 'src'); +app.use(cookieParser()); +app.use(bodyParser.json()); - function cacheControl(req, res, next) { - // instruct browser to revalidate - res.header('Cache-Control', environment.cache.control || 'max-age=60'); - next(); - } +app.engine('html', (_, options, callback) => + ngExpressEngine({ + bootstrap: ServerAppModuleNgFactory, + providers: [ + { + provide: REQUEST, + useValue: (options as any).req, + }, + { + provide: RESPONSE, + useValue: (options as any).req.res, + }, + provideModuleMap(LAZY_MODULE_MAP) + ], + })(_, (options as any), callback) +); - app.use('/', cacheControl, express.static('dist', { index: false })); +app.set('view engine', 'ejs'); +app.set('view engine', 'html'); +app.set('views', DIST_FOLDER); + +function cacheControl(req, res, next) { + // instruct browser to revalidate + res.header('Cache-Control', environment.cache.control || 'max-age=60'); + next(); +} + +app.use('/', cacheControl, express.static('dist', { index: false })); // TODO: either remove or update mock backend // app.get('/data.json', serverApi); // app.use('/api', createMockApi()); - function ngApp(req, res) { - const dspace = { - originalRequest: { - headers: req.headers, - body: req.body, - method: req.method, - params: req.params, - reportProgress: req.reportProgress, - withCredentials: req.withCredentials, - responseType: req.responseType, - urlWithParams: req.urlWithParams - } - }; - - function onHandleError(parentZoneDelegate, currentZone, targetZone, error) { - if (!res._headerSent) { - console.warn('Error in SSR, serving for direct CSR. Error details : ', error); - res.sendFile('index.csr.html', { root: './src' }); - } +function ngApp(req, res) { + const dspace = { + originalRequest: { + headers: req.headers, + body: req.body, + method: req.method, + params: req.params, + reportProgress: req.reportProgress, + withCredentials: req.withCredentials, + responseType: req.responseType, + urlWithParams: req.urlWithParams } + }; - if (environment.universal.preboot) { - Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => { - res.render('../dist/index.html', { - req, - res, - preboot: environment.universal.preboot, - async: environment.universal.async, - time: environment.universal.time, - baseUrl: environment.ui.nameSpace, - originUrl: environment.ui.baseUrl, - requestUrl: req.originalUrl - }); - }); - } else { - console.log('Universal off, serving for direct CSR'); - res.render('index-csr.ejs', { - root: './src', - scripts: `` - }); + function onHandleError(parentZoneDelegate, currentZone, targetZone, error) { + if (!res._headerSent) { + console.warn('Error in SSR, serving for direct CSR. Error details : ', error); + res.sendFile('index.csr.html', { root: DIST_FOLDER }); } } - function serverStarted() { - console.log(`[${new Date().toTimeString()}] Listening at ${environment.ui.baseUrl}`); - } - - function createHttpsServer(keys) { - https.createServer({ - key: keys.serviceKey, - cert: keys.certificate - }, app).listen(environment.ui.port, environment.ui.host, () => { - serverStarted(); + if (environment.universal.preboot) { + Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => { + res.render(DIST_FOLDER, { + req, + res, + preboot: environment.universal.preboot, + async: environment.universal.async, + time: environment.universal.time, + baseUrl: environment.ui.nameSpace, + originUrl: environment.ui.baseUrl, + requestUrl: req.originalUrl + }); }); - } - - if (environment.ui.ssl) { - let serviceKey; - try { - serviceKey = fs.readFileSync('./config/ssl/key.pem'); - } catch (e) { - console.warn('Service key not found at ./config/ssl/key.pem'); - } - - let certificate; - try { - certificate = fs.readFileSync('./config/ssl/cert.pem'); - } catch (e) { - console.warn('Certificate not found at ./config/ssl/key.pem'); - } - - if (serviceKey && certificate) { - createHttpsServer({ - serviceKey: serviceKey, - certificate: certificate - }); - } else { - - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - - pem.createCertificate({ - days: 1, - selfSigned: true - }, (error, keys) => { - createHttpsServer(keys); - }); - } } else { - app.listen(environment.ui.port, environment.ui.host, () => { - serverStarted(); + console.log('Universal off, serving for direct CSR'); + res.render('index-csr.ejs', { + root: DIST_FOLDER, + scripts: `` }); - }} + } +} + +app.get('*.*', ngApp); + +function serverStarted() { + console.log(`[${new Date().toTimeString()}] Listening at ${environment.ui.baseUrl}`); +} + +function createHttpsServer(keys) { + https.createServer({ + key: keys.serviceKey, + cert: keys.certificate + }, app).listen(environment.ui.port, environment.ui.host, () => { + serverStarted(); + }); +} + +if (environment.ui.ssl) { + let serviceKey; + try { + serviceKey = fs.readFileSync('./config/ssl/key.pem'); + } catch (e) { + console.warn('Service key not found at ./config/ssl/key.pem'); + } + + let certificate; + try { + certificate = fs.readFileSync('./config/ssl/cert.pem'); + } catch (e) { + console.warn('Certificate not found at ./config/ssl/key.pem'); + } + + if (serviceKey && certificate) { + createHttpsServer({ + serviceKey: serviceKey, + certificate: certificate + }); + } else { + + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + + pem.createCertificate({ + days: 1, + selfSigned: true + }, (error, keys) => { + createHttpsServer(keys); + }); + } +} else { + app.listen(environment.ui.port, environment.ui.host, () => { + serverStarted(); + }); +} From f881c2f428324ab1d5dd0dd586bf3ddce0313397 Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 18 May 2020 16:20:09 +0200 Subject: [PATCH 3/4] fixed SSR issues --- angular.json | 2 +- server.ts | 6 +++--- src/styles/_bootstrap_variables.scss | 2 +- tsconfig.server.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/angular.json b/angular.json index b3fbd82f02..92c0f27d2b 100644 --- a/angular.json +++ b/angular.json @@ -21,7 +21,7 @@ "path": "./webpack/webpack.common.ts", "mergeStrategies": { "loaders": "prepend" - } + }, }, "outputPath": "dist/browser", "index": "src/index.html", diff --git a/server.ts b/server.ts index f1e8e1943f..f13c8ef272 100644 --- a/server.ts +++ b/server.ts @@ -78,7 +78,7 @@ function cacheControl(req, res, next) { next(); } -app.use('/', cacheControl, express.static('dist', { index: false })); +app.get('*.*', cacheControl, express.static(DIST_FOLDER, { index: false })); // TODO: either remove or update mock backend // app.get('/data.json', serverApi); @@ -107,7 +107,7 @@ function ngApp(req, res) { if (environment.universal.preboot) { Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => { - res.render(DIST_FOLDER, { + res.render(DIST_FOLDER + '/index.html', { req, res, preboot: environment.universal.preboot, @@ -127,7 +127,7 @@ function ngApp(req, res) { } } -app.get('*.*', ngApp); +app.get('*', ngApp); function serverStarted() { console.log(`[${new Date().toTimeString()}] Listening at ${environment.ui.baseUrl}`); diff --git a/src/styles/_bootstrap_variables.scss b/src/styles/_bootstrap_variables.scss index 399cc064f3..42f52282dc 100644 --- a/src/styles/_bootstrap_variables.scss +++ b/src/styles/_bootstrap_variables.scss @@ -8,7 +8,7 @@ $sidebar-items-width: 250px !default; $total-sidebar-width: $collapsed-sidebar-width + $sidebar-items-width !default; /* Fonts */ -$fa-font-path: "node_modules/@fortawesome/fontawesome-free/webfonts" !default; +$fa-font-path: "/assets/fonts" !default; /* Images */ $image-path: "../assets/images" !default; diff --git a/tsconfig.server.json b/tsconfig.server.json index d3e7ca2f81..1329b32ace 100644 --- a/tsconfig.server.json +++ b/tsconfig.server.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.app.json", "compilerOptions": { "outDir": "./out-tsc/app-server", - "module": "commonjs" + "module": "commonjs", }, "angularCompilerOptions": { "entryModule": "./src/modules/app/server-app.module#ServerAppModule" From a9cb6aeaa617a6f30e175411c1cdb1b2c58b1b54 Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 25 May 2020 13:45:11 +0200 Subject: [PATCH 4/4] added doc to scripts --- scripts/serve.ts | 3 ++ server.ts | 76 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/scripts/serve.ts b/scripts/serve.ts index ff3554804e..c69f8e8a21 100644 --- a/scripts/serve.ts +++ b/scripts/serve.ts @@ -2,6 +2,9 @@ import { environment } from '../src/environments/environment'; import * as child from 'child_process'; +/** + * Calls `ng serve` with the following arguments configured for the UI in the environment file: host, port, nameSpace, ssl + */ child.spawn( `ng serve --host ${environment.ui.host} --port ${environment.ui.port} --servePath ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`, { stdio:'inherit', shell: true } diff --git a/server.ts b/server.ts index f13c8ef272..a5d47d8bd7 100644 --- a/server.ts +++ b/server.ts @@ -34,23 +34,50 @@ import { enableProdMode, NgModuleFactory, Type } from '@angular/core'; import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import { environment } from './src/environments/environment'; +/* + * Set path for the browser application's dist folder + */ const DIST_FOLDER = join(process.cwd(), 'dist/browser'); // * NOTE :: leave this as require() since this file is built Dynamically from webpack const { ServerAppModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main'); +/* + * Create a new express application + */ const app = express(); +/* + * If production mode is enabled in the environment file: + * - Enable Angular's production mode + * - Enable compression for response bodies. See [compression](https://github.com/expressjs/compression) + */ if (environment.production) { enableProdMode(); app.use(compression()); } +/* + * Enable request logging + * See [morgan](https://github.com/expressjs/morgan) + */ app.use(morgan('dev')); +/* + * Add cookie parser middleware + * See [morgan](https://github.com/expressjs/cookie-parser) + */ app.use(cookieParser()); + +/* + * Add parser for request bodies + * See [morgan](https://github.com/expressjs/body-parser) + */ app.use(bodyParser.json()); +/* + * Render html pages by running angular server side + */ app.engine('html', (_, options, callback) => ngExpressEngine({ bootstrap: ServerAppModuleNgFactory, @@ -68,23 +95,39 @@ app.engine('html', (_, options, callback) => })(_, (options as any), callback) ); +/* + * Register the view engines for html and ejs + */ app.set('view engine', 'ejs'); app.set('view engine', 'html'); + +/* + * Set views folder path to directory where template files are stored + */ app.set('views', DIST_FOLDER); +/* + * Adds a cache control header to the response + * The cache control value can be configured in the environments file and defaults to max-age=60 + */ function cacheControl(req, res, next) { // instruct browser to revalidate res.header('Cache-Control', environment.cache.control || 'max-age=60'); next(); } +/* + * Serve static resources (images, i18n messages, …) + */ app.get('*.*', cacheControl, express.static(DIST_FOLDER, { index: false })); -// TODO: either remove or update mock backend -// app.get('/data.json', serverApi); -// app.use('/api', createMockApi()); - +/* + * The callback function to serve server side angular + */ function ngApp(req, res) { + // Object to be set to window.dspace when CSR is used + // this allows us to pass the info in the original request + // to the dspace7-angular instance running in the client's browser const dspace = { originalRequest: { headers: req.headers, @@ -98,14 +141,20 @@ function ngApp(req, res) { } }; + // callback function for the case when SSR throws an error. function onHandleError(parentZoneDelegate, currentZone, targetZone, error) { if (!res._headerSent) { console.warn('Error in SSR, serving for direct CSR. Error details : ', error); - res.sendFile('index.csr.html', { root: DIST_FOLDER }); + res.sendFile('index.csr.ejs', { + root: DIST_FOLDER, + scripts: `` + }); } } if (environment.universal.preboot) { + // If preboot is enabled, create a new zone for SSR, and + // register the error handler for when it throws an error Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => { res.render(DIST_FOLDER + '/index.html', { req, @@ -119,6 +168,8 @@ function ngApp(req, res) { }); }); } else { + // If preboot is disabled, just serve the client side ejs template and pass it the required + // variables console.log('Universal off, serving for direct CSR'); res.render('index-csr.ejs', { root: DIST_FOLDER, @@ -127,12 +178,20 @@ function ngApp(req, res) { } } +// Register the ngApp callback function to handle incoming requests app.get('*', ngApp); +/* + * Callback function for when the server has started + */ function serverStarted() { console.log(`[${new Date().toTimeString()}] Listening at ${environment.ui.baseUrl}`); } +/* + * Create an HTTPS server with the configured port and host + * @param keys SSL credentials + */ function createHttpsServer(keys) { https.createServer({ key: keys.serviceKey, @@ -142,6 +201,13 @@ function createHttpsServer(keys) { }); } +/* + * If SSL is enabled + * - Read credentials from configuration files + * - Call script to start an HTTPS server with these credentials + * When SSL is disabled + * - Start an HTTP server on the configured port and host + */ if (environment.ui.ssl) { let serviceKey; try {