UNPKG

@knapsack/app

Version:

Build Design Systems on top of knapsack, by Basalt

396 lines (338 loc) • 11.4 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getServer = getServer; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _express = _interopRequireDefault(require("express")); var _apolloServerExpress = require("apollo-server-express"); var _graphqlTools = require("graphql-tools"); var _ws = _interopRequireDefault(require("ws")); var _bodyParser = _interopRequireDefault(require("body-parser")); require("isomorphic-fetch"); var _path = require("path"); var _fsExtra = require("fs-extra"); var _cloudConnect = require("../cloud/cloud-connect"); var log = _interopRequireWildcard(require("../cli/log")); var _events = require("./events"); var _restApi = require("./rest-api"); var _routes = require("./routes"); var _features = require("../lib/features"); var _auth = require("./auth"); var _constants = require("../lib/constants"); var _pageBuilder = require("./page-builder"); var _designTokens = require("./design-tokens"); var _bootstrap = require("../lib/bootstrap"); var _webSockets = require("../schemas/web-sockets"); var _utils = require("../lib/utils"); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } async function getServer({ meta }) { const { patterns, customPages, pageBuilderPages, settings, tokens, navs, config, assetSets } = (0, _bootstrap.getBrain)(); const port = process.env.KNAPSACK_PORT || meta.serverPort; const knapsackDistDir = (0, _path.join)(__dirname, '../../dist/client'); const metaTypeDef = (0, _apolloServerExpress.gql)` type Meta { websocketsPort: Int! knapsackVersion: String changelog: String version: String } type Query { meta: Meta } `; const metaResolvers = { Query: { meta: () => meta } }; const gqlServer = new _apolloServerExpress.ApolloServer({ schema: (0, _graphqlTools.mergeSchemas)({ schemas: [(0, _graphqlTools.makeExecutableSchema)({ typeDefs: _pageBuilder.pageBuilderPagesTypeDef, resolvers: _pageBuilder.pageBuilderPagesResolvers }), (0, _graphqlTools.makeExecutableSchema)({ typeDefs: _designTokens.designTokensTypeDef, resolvers: _designTokens.designTokensResolvers }), // makeExecutableSchema({ // typeDefs: patternsTypeDef, // resolvers: patternsResolvers, // }), (0, _graphqlTools.makeExecutableSchema)({ typeDefs: metaTypeDef, resolvers: metaResolvers })] }), // https://www.apollographql.com/docs/apollo-server/essentials/data.html#context context: ({ req }) => { // const { host, origin } = req.headers; // log.verbose('request received', { host, origin }, 'graphql'); const { role } = (0, _auth.getUserInfo)(req); const canWrite = role.permissions.includes(_constants.PERMISSIONS.WRITE); return { pageBuilderPages, settings, tokens, assetSets, navs, patterns, canWrite, customPages, config }; }, playground: true, introspection: true }); const app = (0, _express.default)(); app.use(_bodyParser.default.json({ limit: '5000kb' })); gqlServer.applyMiddleware({ app }); const endpoints = []; function registerEndpoint(pathname, method = 'GET') { endpoints.push({ pathname, method }); } const restApiRoutes = (0, _restApi.getApiRoutes)({ registerEndpoint, webroot: config.dist, public: config.public, baseUrl: _constants.apiUrlBase, meta, patternManifest: patterns, templateRenderers: config.templateRenderers, pageBuilder: pageBuilderPages, settingsStore: settings, tokens, config }); app.use(restApiRoutes); async function getDataStore() { return { settingsState: { settings: await settings.getData() }, patternsState: await patterns.getData(), customPagesState: await customPages.getData(), assetSetsState: await assetSets.getData(), navsState: await navs.getData() }; } const fileSavers = {}; async function saveFilesLocally({ files }) { await Promise.all(files.map(({ contents, path, encoding, isDeleted }) => { if (isDeleted) { return (0, _fsExtra.remove)(path); } switch (encoding) { case 'utf8': return (0, _fsExtra.writeFile)(path, contents, { encoding }); case 'base64': { const data = Buffer.from(contents, 'base64'); return (0, _fsExtra.writeFile)(path, data); } default: throw new Error(`Incorrect encoding used when saving files locally: "${encoding}" for file ${path}.`); } })); return { ok: true, message: 'All config files saved locally.' }; } fileSavers.local = saveFilesLocally; if (config.cloud) { const { saveFilesToCloud } = new _cloudConnect.KsCloudConnect(config.cloud); fileSavers.cloud = saveFilesToCloud; } async function handleNewDataStore({ state, title, message, storageLocation, user }) { const configFiles = await Promise.all([settings.savePrep(state.settingsState.settings), customPages.savePrep(state.customPagesState), navs.savePrep(state.navsState), patterns.savePrep(state.patternsState)]).then(results => (0, _utils.flattenArray)(results)); configFiles.forEach(configFile => { if (configFile.encoding === 'base64') { if (!(0, _utils.isBase64)(configFile.contents)) { console.log(configFile); throw new Error(`Pre-save check on Knapsack File "${configFile.path}" expected a base64 encoding and it is not.`); } } }); if (!fileSavers[storageLocation]) { throw new Error(`Must declare save location, passed in ${storageLocation}`); } return fileSavers[storageLocation]({ files: configFiles, title, message, user }); } app.get(`${_constants.apiUrlBase}/data-store`, async (req, res) => { const dataStore = await getDataStore(); const userInfo = (0, _auth.getUserInfo)(req); const features = (0, _features.getFeaturesForUser)(userInfo); log.verbose('features', features); const fullDataStore = _objectSpread({}, dataStore, { userState: { isLocalDev: process.env.NODE_ENV !== 'production', canEdit: process.env.NODE_ENV !== 'production', features }, metaState: { meta, plugins: config.plugins.map(p => { return { id: p.id, hasContent: !!p.loadContent, clientPluginPath: p.clientPluginPath ? (0, _path.join)(`/plugins/${p.id}`, p.clientPluginPath) : null }; }) } }); res.send(fullDataStore); }); app.post(`${_constants.apiUrlBase}/data-store`, async (req, res) => { var _user$role; const { state, title, message, storageLocation } = req.body; if (!(storageLocation === 'local' || storageLocation === 'cloud')) { return res.status(_constants.HTTP_STATUS.BAD.BAD_REQUEST).send({ ok: false, message: `loc param must be local or cloud, was: ${storageLocation}` }); } const isLocalDev = state.userState.isLocalDev && process.env.NODE_ENV === 'production'; const user = (0, _auth.getUserInfo)(req); const permissions = user === null || user === void 0 ? void 0 : (_user$role = user.role) === null || _user$role === void 0 ? void 0 : _user$role.permissions; if (!permissions.includes(_constants.PERMISSIONS.WRITE) && !isLocalDev && storageLocation !== 'local') { res.status(_constants.HTTP_STATUS.BAD.UNAUTHORIZED).send(); } else { try { const results = await handleNewDataStore({ storageLocation, title, message, state, user }); // console.log('handleNewDataStore results', results); res.send(results); } catch (e) { console.error('handleNewDataStore', e); res.status(_constants.HTTP_STATUS.FAIL.INTERNAL_ERROR).send({ ok: false, message: `Could not handleNewDataStore. ${e.message}` }); } } }); const regularRoutes = (0, _routes.setupRoutes)({ patterns, knapsackDistDir, distDir: config.dist, publicDir: config.public, cacheDir: meta.cacheDir, plugins: config.plugins }); app.use(regularRoutes); let wss; /** * @returns if successful */ function sendWsMessage(msg) { if (!wss) { console.error('Attempted to fire "sendWsMessage" but no WebSockets Server setup due to lack of "websocketsPort" in config'); return false; } log.verbose('sendWsMessage', msg, 'server'); wss.clients.forEach(client => { if (client.readyState === _ws.default.OPEN) { client.send(JSON.stringify(msg)); } }); return true; } function serve() { if (meta.websocketsPort && _features.enableTemplatePush) { wss = new _ws.default.Server({ port: meta.websocketsPort, clientTracking: true }); // wss.on('connection', ws => { // ws.on('message', msg => { // console.log('wss messge', msg); // }); // }); } app.listen(port, () => { log.silly('Available endpoints', endpoints, 'server'); // want url to not get buried with info // @todo show this after event is fired from WebPack being ready setTimeout(() => { log.info(`🚀 Server listening on http://localhost:${port}`, null, 'server'); }, 250); }); if (_features.enableTemplatePush && wss) { _events.knapsackEvents.on(_events.EVENTS.PATTERN_TEMPLATE_CHANGED, data => { setTimeout(() => { sendWsMessage({ event: _webSockets.WS_EVENTS.PATTERN_TEMPLATE_CHANGED, data }); }, 100); }); _events.knapsackEvents.on(_events.EVENTS.PATTERN_ASSET_CHANGED, data => { setTimeout(() => { sendWsMessage({ event: _webSockets.WS_EVENTS.PATTERN_ASSET_CHANGED, data }); }, 100); }); } } return { app, serve }; }