mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Feat/serve frontend with baseuri (#824)
* chore: update changelog * chore: update changelog * feat: format asset paths and insert baseUri in html * feat: add tests * feat: pass dependencies to pre router hook Co-authored-by: Ivar Conradi Østhus <ivarconr@gmail.com>
This commit is contained in:
		
							parent
							
								
									0efc238fdb
								
							
						
					
					
						commit
						e22c7c8b37
					
				| @ -1,4 +1,5 @@ | ||||
| import { publicFolder } from 'unleash-frontend'; | ||||
| import fs from 'fs'; | ||||
| import EventEmitter from 'events'; | ||||
| import express from 'express'; | ||||
| import cors from 'cors'; | ||||
| @ -22,6 +23,7 @@ import demoAuthentication from './middleware/demo-authentication'; | ||||
| import ossAuthentication from './middleware/oss-authentication'; | ||||
| import noAuthentication from './middleware/no-authentication'; | ||||
| import secureHeaders from './middleware/secure-headers'; | ||||
| import { rewriteHTML } from './util/rewriteHTML'; | ||||
| 
 | ||||
| export default function getApp( | ||||
|     config: IUnleashConfig, | ||||
| @ -33,6 +35,12 @@ export default function getApp( | ||||
| 
 | ||||
|     const baseUriPath = config.server.baseUriPath || ''; | ||||
| 
 | ||||
|     let indexHTML = fs | ||||
|         .readFileSync(path.join(publicFolder, 'index.html')) | ||||
|         .toString(); | ||||
| 
 | ||||
|     indexHTML = rewriteHTML(indexHTML, baseUriPath); | ||||
| 
 | ||||
|     app.set('trust proxy', true); | ||||
|     app.disable('x-powered-by'); | ||||
|     app.set('port', config.server.port); | ||||
| @ -57,7 +65,8 @@ export default function getApp( | ||||
|     app.use(secureHeaders(config)); | ||||
|     app.use(express.urlencoded({ extended: true })); | ||||
|     app.use(favicon(path.join(publicFolder, 'favicon.ico'))); | ||||
|     app.use(baseUriPath, express.static(publicFolder)); | ||||
| 
 | ||||
|     app.use(baseUriPath, express.static(publicFolder, { index: false })); | ||||
| 
 | ||||
|     if (config.enableOAS) { | ||||
|         app.use(`${baseUriPath}/oas`, express.static('docs/api/oas')); | ||||
| @ -103,7 +112,7 @@ export default function getApp( | ||||
|     ); | ||||
| 
 | ||||
|     if (typeof config.preRouterHook === 'function') { | ||||
|         config.preRouterHook(app); | ||||
|         config.preRouterHook(app, config, services, stores); | ||||
|     } | ||||
| 
 | ||||
|     // Setup API routes
 | ||||
| @ -113,6 +122,18 @@ export default function getApp( | ||||
|         app.use(errorHandler()); | ||||
|     } | ||||
| 
 | ||||
|     app.get(`${baseUriPath}`, (req, res) => { | ||||
|         res.send(indexHTML); | ||||
|     }); | ||||
| 
 | ||||
|     app.get('*', (req, res) => { | ||||
|         if (req.path.includes('api')) { | ||||
|             res.status(404).send(); | ||||
|         } | ||||
| 
 | ||||
|         res.send(indexHTML); | ||||
|     }); | ||||
| 
 | ||||
|     return app; | ||||
| } | ||||
| module.exports = getApp; | ||||
|  | ||||
							
								
								
									
										67
									
								
								src/lib/util/rewriteHTML.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/lib/util/rewriteHTML.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| import { rewriteHTML } from './rewriteHTML'; | ||||
| import test from 'ava'; | ||||
| 
 | ||||
| const input = `<!DOCTYPE html>
 | ||||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="utf-8" /> | ||||
|         <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> | ||||
|         <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||||
|         <meta name="baseUriPath" content="::baseUriPath::" /> | ||||
|         <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|         <meta name="description" content="unleash" /> | ||||
| 
 | ||||
|         <title>Unleash - Enterprise ready feature toggles</title> | ||||
|         <link | ||||
|             href="https://fonts.googleapis.com/icon?family=Material+Icons" | ||||
|             rel="stylesheet" | ||||
|         /> | ||||
|         <link | ||||
|             href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" | ||||
|             rel="stylesheet" | ||||
|         /> | ||||
|     </head> | ||||
|     <body> | ||||
|         <div id="app"></div> | ||||
|     </body> | ||||
|     <script src="/static/js/2.5ff09a33.chunk.js"></script> | ||||
|     <script src="/static/js/main.6bcf6c41.chunk.js"></script> | ||||
| </html>`;
 | ||||
| 
 | ||||
| test('rewriteHTML substitutes meta tag with existing rewrite value', t => { | ||||
|     const result = rewriteHTML(input, '/hosted'); | ||||
|     t.true(result.includes(`<meta name="baseUriPath" content="/hosted" />`)); | ||||
| }); | ||||
| 
 | ||||
| test('rewriteHTML substitutes meta tag with empty value', t => { | ||||
|     const result = rewriteHTML(input, ''); | ||||
|     t.true(result.includes(`<meta name="baseUriPath" content="" />`)); | ||||
| }); | ||||
| 
 | ||||
| test('rewriteHTML substitutes asset paths correctly with baseUriPath', t => { | ||||
|     const result = rewriteHTML(input, '/hosted'); | ||||
|     t.true( | ||||
|         result.includes( | ||||
|             `<script src="/hosted/static/js/2.5ff09a33.chunk.js"></script>`, | ||||
|         ), | ||||
|     ); | ||||
|     t.true( | ||||
|         result.includes( | ||||
|             ` <script src="/hosted/static/js/main.6bcf6c41.chunk.js"></script>`, | ||||
|         ), | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test('rewriteHTML substitutes asset paths correctly without baseUriPath', t => { | ||||
|     const result = rewriteHTML(input, ''); | ||||
|     t.true( | ||||
|         result.includes( | ||||
|             `<script src="/static/js/2.5ff09a33.chunk.js"></script>`, | ||||
|         ), | ||||
|     ); | ||||
|     t.true( | ||||
|         result.includes( | ||||
|             ` <script src="/static/js/main.6bcf6c41.chunk.js"></script>`, | ||||
|         ), | ||||
|     ); | ||||
| }); | ||||
							
								
								
									
										7
									
								
								src/lib/util/rewriteHTML.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/lib/util/rewriteHTML.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| export const rewriteHTML = (input: string, rewriteValue: string): string => { | ||||
|     let result = input; | ||||
|     result = result.replace(/::baseUriPath::/gi, rewriteValue); | ||||
|     result = result.replace(/\/static/gi, `${rewriteValue}/static`); | ||||
| 
 | ||||
|     return result; | ||||
| }; | ||||
| @ -9,7 +9,12 @@ const { createTestConfig } = require('../../config/test-config'); | ||||
| const { IAuthType } = require('../../../lib/types/option'); | ||||
| const { createServices } = require('../../../lib/services'); | ||||
| 
 | ||||
| function createApp(stores, adminAuthentication = IAuthType.NONE, preHook) { | ||||
| function createApp( | ||||
|     stores, | ||||
|     adminAuthentication = IAuthType.NONE, | ||||
|     preHook, | ||||
|     customOptions, | ||||
| ) { | ||||
|     const config = createTestConfig({ | ||||
|         authentication: { | ||||
|             type: adminAuthentication, | ||||
| @ -18,6 +23,7 @@ function createApp(stores, adminAuthentication = IAuthType.NONE, preHook) { | ||||
|         server: { | ||||
|             unleashUrl: 'http://localhost:4242', | ||||
|         }, | ||||
|         ...customOptions, | ||||
|     }); | ||||
|     const services = createServices(stores, config); | ||||
|     // TODO: use create from server-impl instead?
 | ||||
| @ -39,4 +45,14 @@ module.exports = { | ||||
|         const app = createApp(stores, IAuthType.CUSTOM, preHook); | ||||
|         return supertest.agent(app); | ||||
|     }, | ||||
|     async setupAppWithBaseUrl(stores) { | ||||
|         const app = createApp(stores, undefined, undefined, { | ||||
|             server: { | ||||
|                 unleashUrl: 'http://localhost:4242', | ||||
|                 basePathUri: '/hosted', | ||||
|             }, | ||||
|         }); | ||||
| 
 | ||||
|         return supertest.agent(app); | ||||
|     }, | ||||
| }; | ||||
|  | ||||
							
								
								
									
										40
									
								
								src/test/e2e/routes/routes.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/test/e2e/routes/routes.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| import test, { before } from 'ava'; | ||||
| import { setupAppWithBaseUrl } from '../helpers/test-helper'; | ||||
| 
 | ||||
| import dbInit from '../helpers/database-init'; | ||||
| 
 | ||||
| let db; | ||||
| let stores; | ||||
| 
 | ||||
| before(async () => { | ||||
|     db = await dbInit('custom_auth_serial'); | ||||
|     stores = db.stores; | ||||
| }); | ||||
| 
 | ||||
| test.after.always(async () => { | ||||
|     await db.destroy(); | ||||
| }); | ||||
| 
 | ||||
| test('hitting a baseUri path returns HTML document', async t => { | ||||
|     t.plan(0); | ||||
|     const request = await setupAppWithBaseUrl(stores); | ||||
|     await request | ||||
|         .get('/hosted') | ||||
|         .expect(200) | ||||
|         .expect('Content-Type', 'text/html; charset=utf-8'); | ||||
| }); | ||||
| 
 | ||||
| test('hitting an api path that does not exist returns 404', async t => { | ||||
|     t.plan(0); | ||||
|     const request = await setupAppWithBaseUrl(stores); | ||||
|     await request.get('/hosted/api/i-dont-exist').expect(404); | ||||
| }); | ||||
| 
 | ||||
| test('hitting a non-api returns HTML document', async t => { | ||||
|     t.plan(0); | ||||
|     const request = await setupAppWithBaseUrl(stores); | ||||
|     await request | ||||
|         .get('/hosted/i-dont-exist') | ||||
|         .expect(200) | ||||
|         .expect('Content-Type', 'text/html; charset=utf-8'); | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user