szymski

Untitled

Apr 12th, 2022
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.54 KB | None | 0 0
  1. const https = require('https');
  2. const path = require('path');
  3. const fs = require('fs').promises;
  4. const _ = require('lodash');
  5. const cors = require('cors');
  6. const axios = require('axios').create({
  7. httpsAgent: new https.Agent({
  8. rejectUnauthorized: false,
  9. }),
  10. });
  11.  
  12. const { getLogLevel, logError } = require('./logs');
  13. const locals = require('./locals');
  14. const cleanConfig = require('./cleanConfig');
  15. const prefixConfigUrls = require('./prefixConfigUrls');
  16. const reportLogResponse = require('./reportLogResponse');
  17. const urls = require('./urls.json');
  18. const optionalRequire = require('./optionalRequire');
  19. const httpsSettings = require('./httpsSettings');
  20.  
  21. const FrameConfigPlugin = optionalRequire('@trans/frame-config-webpack-plugin');
  22. const TranslationsPlugin = optionalRequire('@trans/webpack-translations-plugin');
  23.  
  24. process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
  25.  
  26. const vendorPaths = {
  27. react: {
  28. development: 'react/umd/react.development.js',
  29. next: 'react.next/umd/react.development.js',
  30. },
  31. 'react-dom': {
  32. development: 'react-dom/umd/react-dom.development.js',
  33. next: 'react-dom.next/umd/react-dom.development.js',
  34. },
  35. };
  36.  
  37. const defaultVendor = {
  38. react: 'development',
  39. 'react-dom': 'development',
  40. };
  41.  
  42. const devServer = ({
  43. platform,
  44.  
  45. mockTranslations = platform === undefined,
  46.  
  47. host = 'platform.local.trans.eu',
  48.  
  49. target = platform === 'prod' ? 'platform.trans.eu' : `platform.${platform ?? 'dev'}.trans.eu`,
  50.  
  51. vendor = {},
  52.  
  53. localConfig: {
  54. // function used to set loggedIn variable in filter
  55. // based on (transformed by transformBase) frameConfig
  56. checkIsLogged = ({ products: { basic } = {} } = {}) => !!basic,
  57. // Filter local config entries based on:
  58. // - module name
  59. // - is user logged in
  60. // - available products
  61. filter: filterLocalConfig = (moduleName, loggedIn) => loggedIn,
  62.  
  63. // An object or a path to a file containing an object
  64. // that will be used to extend/override local configuration.
  65. extendConfig = {},
  66.  
  67. // copy these keys from platform config to module/service config
  68. getFromPlatformConfig = [],
  69. } = {},
  70. localConfig,
  71.  
  72. frameConfig: {
  73. // frame config url
  74. url: frameConfigUrl = urls.config,
  75. // Don't use platform config.
  76. // Base frame config only on local configs.
  77. ignorePlatformConfig = false,
  78.  
  79. // Transform frame config from api before it is returned.
  80. transformBase = a => a,
  81.  
  82. // Transform frame config before it is returned.
  83. transform = a => a,
  84. } = {},
  85.  
  86. proxy: { context: proxyContext = '/', bypass = () => {}, ...proxyConfig } = {},
  87.  
  88. before,
  89. stats = 'minimal',
  90. ...devServerConfig
  91. } = {}) => {
  92. const targetHost = `https://${target}`;
  93. const apiTargetHost = `https://api-${target}`;
  94. const platformConfigUrl = `${apiTargetHost}${frameConfigUrl}`;
  95. const logLevel = getLogLevel(stats);
  96.  
  97. const vendorFiles = Object.entries({
  98. ...defaultVendor,
  99. ...vendor,
  100. })
  101. .reduce((files, entry) => {
  102. const file = _.get(vendorPaths, entry);
  103. if (file) {
  104. files.push(file);
  105. }
  106. return files;
  107. }, [])
  108. .map(lib => require.resolve(lib))
  109. .map(filePath => fs.readFile(filePath));
  110.  
  111. const vendorContent = Promise.all(vendorFiles).then(files => Buffer.concat(files));
  112. let localTranslations = {};
  113. let translationsUrl = '';
  114.  
  115. // Return WebPack Dev Server configuration
  116. return {
  117. hot: false,
  118. inline: false,
  119. http2: true,
  120. https: httpsSettings(host),
  121. historyApiFallback: true,
  122. disableHostCheck: true,
  123. host,
  124. stats,
  125. proxy: [
  126. {
  127. context: '/app',
  128. target: apiTargetHost,
  129. changeOrigin: true,
  130. secure: false,
  131. logLevel,
  132. },
  133. {
  134. context: proxyContext,
  135. target: targetHost,
  136. changeOrigin: true,
  137. secure: false,
  138. logLevel,
  139.  
  140. bypass: (...args) => {
  141. const [req] = args;
  142. if (req.method === 'HEAD') {
  143. req.method = 'GET';
  144. }
  145. return bypass(...args);
  146. },
  147.  
  148. ...proxyConfig,
  149. },
  150. ],
  151. before(app, server, compiler) {
  152. app.use(
  153. cors({
  154. origin: true,
  155. credentials: true,
  156. })
  157. );
  158.  
  159. const configs = { module: {}, service: {}, layout: {} };
  160. const activeServers = locals(app, server, compiler);
  161.  
  162. // Override vendor (react/react-dom) file
  163. // with requested version
  164. app.get('/dist/vendor.*.js', (req, res) => {
  165. vendorContent.then(content => res.send(content));
  166. });
  167.  
  168. if (FrameConfigPlugin) {
  169. // Tap into hook from Frame Config Webpack Plugin
  170. // (https://git.dss.lt.trans/trans-web/frame-config-webpack-plugin)
  171. // and get config for local modules/services/layouts
  172. FrameConfigPlugin.getCompilerHooks(compiler).frameConfig.tap(
  173. 'devServer',
  174. (moduleName, type, config) => {
  175. configs[type][moduleName] = cleanConfig(config);
  176. }
  177. );
  178. FrameConfigPlugin.getHooks().frameConfig.tap('devServer', (moduleName, type, config) => {
  179. configs[type][moduleName] = cleanConfig(config);
  180. });
  181. }
  182.  
  183. if (TranslationsPlugin) {
  184. // Tap into hook from TranslationsPlugin
  185. TranslationsPlugin.getHooks().translations.tap('devServer', translations => {
  186. localTranslations = translations;
  187. });
  188. }
  189.  
  190. // Expose local config to other servers for internal use.
  191. app.get(urls.localConfig, (req, res) => {
  192. if (localConfig === false || !FrameConfigPlugin) {
  193. res.json({});
  194. return;
  195. }
  196.  
  197. const localFrameConfig = { modules: {}, services: {}, routes: {}, layouts: {} };
  198. const logged = req.query.logged !== 'false';
  199. const products = req.query.products || [];
  200. Object.entries(configs).forEach(([type, list]) => {
  201. Object.entries(list).forEach(([moduleName, config]) => {
  202. const shouldFilter = type === 'module' || (type === 'service' && config?.data?.autostart);
  203. if (shouldFilter && !filterLocalConfig(moduleName, logged, products)) {
  204. return;
  205. }
  206. const { routes = [], ...moduleConfig } = cleanConfig(config);
  207. localFrameConfig[`${type}s`][moduleName] = moduleConfig;
  208. routes.forEach(({ name, ...routeConfig }) => {
  209. localFrameConfig.routes[name] = routeConfig;
  210. });
  211. });
  212. });
  213. let overrideConfig = extendConfig;
  214.  
  215. // Import external file with config overrides
  216. if (_.isString(extendConfig)) {
  217. overrideConfig = require(extendConfig);
  218.  
  219. // Delete from cache.
  220. // So each request fetches config again.
  221. delete require.cache[path.resolve(extendConfig)];
  222. }
  223.  
  224. res.json(_.merge(localFrameConfig, overrideConfig));
  225. });
  226.  
  227. // TODO: Temporary
  228. // app.get(frameConfigUrl, (req, res) => {
  229. // fs.readFile("mock-frameconfig.json", "utf-8").then(data => {
  230. // res.json(JSON.parse(data));
  231. // });
  232. // })
  233.  
  234. app.get(frameConfigUrl, (req, res, next) => {
  235. const { host: _host, ...headers } = req.headers;
  236.  
  237. // Get platform config.
  238. // Use authorization headers from original request.
  239. axios
  240. .get(platformConfigUrl, { headers, params: req.query })
  241. .then(({ data, config: { headers: responseHeaders, url } }) =>
  242. transformBase(data, { headers: responseHeaders, url })
  243. )
  244. .then(baseFrameConfig => {
  245. translationsUrl = baseFrameConfig?.config?.urls?.translations || '';
  246. const products = Object.keys((baseFrameConfig || {}).products || {});
  247. const logged = checkIsLogged(baseFrameConfig);
  248. const base = ignorePlatformConfig ? {} : baseFrameConfig;
  249.  
  250. return Promise.all(
  251. // Collect local configuration from running servers.
  252. [...activeServers].map(clientPort => {
  253. const prefix = `${req.protocol}://${host}:${clientPort}`;
  254. const getConfigUrl = `${prefix}${urls.localConfig}`;
  255. return axios
  256. .get(getConfigUrl, { params: { logged, products } })
  257. .then(({ data }) => prefixConfigUrls(prefix, data))
  258. .catch(() => ({}));
  259. })
  260. )
  261. .then(frameConfigs => _.merge({}, ...frameConfigs))
  262. .then(localFrameConfig => {
  263. const { modules = {}, services = {}, layouts = {}, routes = {}, ...baseRest } = base;
  264. const {
  265. modules: localModules = {},
  266. services: localServices = {},
  267. layouts: localLayouts = {},
  268. routes: localRoutes = {},
  269. ...localRest
  270. } = localFrameConfig;
  271.  
  272. // Don't merge modules and services
  273. // we want local modules/services to overwrite them
  274. const mergedConfig = _.merge({}, baseRest, localRest, {
  275. config: {
  276. urls: {
  277. reportLog: urls.reportLog,
  278. ...(mockTranslations &&
  279. TranslationsPlugin && {
  280. translations:
  281. urls.translations + '?language="[LANG]"&module="[MODULE]"',
  282. }),
  283. },
  284. },
  285. });
  286.  
  287. [
  288. [localModules, modules],
  289. [localServices, services],
  290. [localLayouts, layouts],
  291. ].forEach(([local, remote]) => {
  292. const names = Object.keys(local);
  293.  
  294. names.forEach(name => {
  295. const remoteKeys = _.isFunction(getFromPlatformConfig)
  296. ? getFromPlatformConfig(name)
  297. : getFromPlatformConfig;
  298.  
  299. remoteKeys.forEach(key => {
  300. const val = (remote[name] || {})[key];
  301. if (!_.isEqual(local[name][key], val)) {
  302. local[name][key] = val;
  303. }
  304. });
  305. });
  306.  
  307. // Put local modules/services first, so their externals will be registered first
  308. Object.assign(local, _.omit(remote, names));
  309. });
  310.  
  311. Object.assign(mergedConfig, {
  312. modules: localModules,
  313. services: localServices,
  314. layouts: localLayouts,
  315. routes: {
  316. ...localRoutes,
  317. ..._.omit(routes, Object.keys(localRoutes)),
  318. },
  319. });
  320.  
  321. res.json(transform(mergedConfig));
  322. });
  323. })
  324. .catch(error => next(error));
  325. });
  326.  
  327. // TODO: Temporary
  328. // app.get(urls.translations, (req, res) => {
  329. // res.json({});
  330. // })
  331.  
  332. app.get(urls.translations, (req, res) => {
  333. let { language, module: moduleName } = req.query;
  334. language = JSON.parse(language);
  335. moduleName = JSON.parse(moduleName);
  336. const proxyTranslationsUrl = translationsUrl
  337. .replace('[MODULE]', moduleName)
  338. .replace('[LANG]', language);
  339. const overridenTranslations = localTranslations?.[language]?.[moduleName];
  340. axios
  341. .get(proxyTranslationsUrl)
  342. .catch(() => ({}))
  343. .then(translations => {
  344. res.json({
  345. ...translations.data,
  346. ...overridenTranslations,
  347. });
  348. });
  349. });
  350.  
  351. app.post(urls.reportLog, (req, res) => {
  352. new Promise(resolve => {
  353. let body = '';
  354. req.on('data', chunk => {
  355. body += chunk;
  356. });
  357. req.on('end', () => resolve(JSON.parse(body)));
  358. }).then(body => {
  359. res.send(reportLogResponse(body));
  360.  
  361. body.forEach(({ type, message, payload = '' }) => {
  362. if (type === 'error') {
  363. logError(
  364. message,
  365. payload.status || _.get(payload, 'response.status', payload),
  366. payload.statusText || _.get(payload, 'response.statusText', '')
  367. );
  368. }
  369. });
  370. });
  371. });
  372.  
  373. if (before) {
  374. before(app, server, compiler);
  375. }
  376. },
  377. ...devServerConfig,
  378. };
  379. };
  380.  
  381. module.exports = devServer;
  382.  
Advertisement
Add Comment
Please, Sign In to add comment