subapp-server
Version:
Electrode SubApp app server support
203 lines (168 loc) • 5.6 kB
JavaScript
// Copy from electrode-react-webapp for now
/* eslint-disable max-statements, complexity, global-require, no-magic-numbers, no-console */
const _ = require("lodash");
const Fs = require("fs");
const Path = require("path");
const util = require("util");
const Boom = require("@hapi/boom");
const HttpStatus = require("./http-status");
const readFile = util.promisify(Fs.readFile);
const xaa = require("xaa");
const subAppUtil = require("subapp-util");
const registerRoutes = require("./register-routes");
const routesFromFile = require("./routes-from-file");
const routesFromDir = require("./routes-from-dir");
const { errorResponse, getSrcDir } = require("./utils");
async function handleFavIcon(server, options) {
//
// favicon handling, turn off by setting options.favicon to false
//
if (options.favicon === false) {
return;
}
// look in CWD/static
const iconFile = Path.resolve(options.favicon || "static/favicon.ico");
const icon = await xaa.try(() => readFile(iconFile));
server.route({
method: "get",
path: "/favicon.ico",
async handler() {
if (icon) return icon;
else return Boom.notFound();
}
});
}
async function registerRoutesFromFile({ server, srcDir, routes, topOpts }) {
const subApps = await subAppUtil.scanSubAppsFromDir(srcDir);
const subAppsByPath = subAppUtil.getSubAppByPathMap(subApps);
// setup for initialize callback
server.ext("onPreAuth", async (request, h) => {
const rte = routes[request.route.path];
if (rte && rte.initialize) {
const x = await rte.initialize(request, h);
if (x) return x;
}
return h.continue;
});
// register routes
for (const path in routes) {
const route = routes[path];
const routeOptions = Object.assign({}, topOpts, route, { path });
routeOptions.uiConfig = _.get(server, "settings.app.config.ui", {});
// setup the template for rendering the route
const routeRenderer = routesFromFile.setupRouteTemplate({
server,
subAppsByPath,
srcDir,
routeOptions
});
const useStream = routeOptions.useStream !== false;
const handler = async (request, h) => {
try {
const context = await routeRenderer({
useStream,
mode: "",
request
});
const data = context.result;
const status = data.status;
if (data instanceof Error) {
// rethrow to get default error behavior below with helpful errors in dev mode
throw data;
} else if (status === undefined) {
return h.response(data).type("text/html; charset=UTF-8").code(200);
} else if (HttpStatus.redirect[status]) {
return h.redirect(data.path).code(status);
} else if (HttpStatus.displayHtml[status]) {
return h.response(data.html !== undefined ? data.html : data).code(status);
} else if (status >= 200 && status < 300) {
return data.html !== undefined ? data.html : data;
} else {
return h.response(data).code(status);
}
} catch (err) {
return errorResponse({
routeName: path,
request,
h,
err
});
}
};
const defaultMethods = [].concat(route.methods || "get");
const paths = _.uniq([path].concat(route.paths).filter(x => x)).map(x => {
if (typeof x === "string") {
return { [x]: defaultMethods };
}
return x;
});
paths.forEach(pathObj => {
_.each(pathObj, (method, xpath) => {
server.route(
Object.assign({}, route.settings, {
path: xpath,
method,
handler
})
);
});
});
}
}
async function setupRoutesFromFile(srcDir, server, pluginOpts) {
const { routes, topOpts } = routesFromFile.searchRoutes(srcDir, pluginOpts);
await handleFavIcon(server, topOpts);
// invoke setup callback
for (const path in routes) {
if (routes[path].setup) {
await routes[path].setup(server);
}
}
await registerRoutesFromFile({
server,
routes,
topOpts,
srcDir
});
}
async function setupRoutesFromDir(server, fromDir) {
const { routes, topOpts } = fromDir;
topOpts.routes = _.merge({}, routes, topOpts.routes);
const routesWithSetup = routes.filter(x => x.route.setup);
for (const route of routesWithSetup) {
await route.route.setup(server);
}
await handleFavIcon(server, topOpts);
// register onRequest
const routesWithInit = routes.filter(x => x.route.initialize);
if (routesWithInit.length > 0) {
server.ext("onPreAuth", async (request, h) => {
const rte = routes[request.route.path];
if (rte && rte.initialize) {
const x = await rte.initialize(request, h);
if (x) return x;
}
return h.continue;
});
}
// in case needed, add full protocol/host/port to dev bundle base URL
topOpts.devBundleBase = subAppUtil.formUrl({
..._.pick(topOpts.devServer, ["protocol", "host", "port"]),
path: topOpts.devBundleBase
});
registerRoutes({ routes, topOpts, server });
}
async function setupSubAppHapiRoutes(server, pluginOpts) {
const srcDir = getSrcDir(pluginOpts);
const fromDir = await routesFromDir.searchRoutes(srcDir, pluginOpts);
if (fromDir) {
return await setupRoutesFromDir(server, fromDir);
}
// no directory based routes, then they must in a JS file
return await xaa.wrap(() => setupRoutesFromFile(srcDir, server, pluginOpts));
}
module.exports = {
setupRoutesFromFile,
setupSubAppHapiRoutes
};
;