UNPKG

tanam

Version:

Pluggable CMS for Firebase

220 lines 10.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); const crypto_js_1 = require("crypto-js"); const express = require("express"); const admin = require("firebase-admin"); const functions = require("firebase-functions"); const fs = require("fs"); const path_1 = require("path"); const render_1 = require("./render"); const configService = require("./services/config.service"); const page_context_service_1 = require("./services/page-context.service"); const fileService = require("./services/file.service"); const DIST_FOLDER = path_1.join(process.cwd(), 'browser'); const TANAM_FOLDER = path_1.join(process.cwd(), 'node_modules', 'tanam', 'browser'); if (admin.apps.length === 0) { admin.initializeApp(); } admin.firestore().settings({ timestampsInSnapshots: true }); __export(require("./triggers")); const app = express(); exports.tanam = functions.https.onRequest(app); const CACHE_CONFIG = { s_max_age: 60 * 5, max_age: 60 * 10 }; exports.initializeApp = configService.setConfig; /** * Get a cache header string that can be set in a HTTP response header. */ function getCacheHeader() { const funcionsConf = functions.config().cache; if (funcionsConf) { CACHE_CONFIG.max_age = funcionsConf.max_age || CACHE_CONFIG.max_age; CACHE_CONFIG.s_max_age = funcionsConf.s_max_age || CACHE_CONFIG.s_max_age; } console.log(`[getCacheHeader] Get cache header configuration: ${JSON.stringify(CACHE_CONFIG)}`); return `public, max-age=${CACHE_CONFIG.max_age}, s-maxage=${CACHE_CONFIG.s_max_age}, stale-while-revalidate=120`; } exports.getCacheHeader = getCacheHeader; function _getDistFolder() { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => { fs.exists(TANAM_FOLDER, exists => { const folder = exists ? TANAM_FOLDER : DIST_FOLDER; console.log(`[_getDistFolder]: ${folder}`); resolve(folder); }); }); }); } function _registerHostname(request) { return __awaiter(this, void 0, void 0, function* () { const domainsRef = admin.database().ref('tanam') .child(process.env.GCLOUD_PROJECT) .child('domains'); const defaultDomains = [`${process.env.GCLOUD_PROJECT}.firebaseapp.com`, `${process.env.GCLOUD_PROJECT}.web.app`]; const promises = []; for (const domain of defaultDomains) { promises.push(domainsRef.child(crypto_js_1.MD5(domain).toString()).set(domain)); } // Save "known" domains upon request to resources that are likely to seldom change -> cached for long // So that the overhead is done as seldom as possible const hostname = request.hostname; if (hostname !== 'localhost' && !hostname.endsWith(`.cloudfunctions.net`)) { console.log(`[_registerHostname] Saving hostname: ${hostname}`); promises.push(domainsRef.child(crypto_js_1.MD5(hostname).toString()).set(hostname)); } return Promise.all(promises); }); } // Match the Angular generated files with the unique hash // Serve them with fairly long cache lifetime since they'll be unique for each deploy app.get(/^\/?main|polyfills|runtime|styles|vendor\.[\w\d]{20}\.js|css\/?$/i, (request, response) => __awaiter(this, void 0, void 0, function* () { console.log(`Angular scripts: GET ${request.url}`); const requestUrl = request.url.split('/'); const distFolder = yield _getDistFolder(); response.setHeader('Cache-Control', `public, max-age=604800, s-maxage=31536000, stale-while-revalidate=600`); response.sendFile(path_1.join(distFolder, ...requestUrl)); return null; })); // Handle Angular app's assets app.get(/^\/?assets\/(.*)\/?$/i, (request, response) => __awaiter(this, void 0, void 0, function* () { console.log(`Assets: ${request.url}`); response.setHeader('Cache-Control', `public, max-age=600, s-maxage=3600, stale-while-revalidate=120`); if (request.url.match(/^\/?assets\/tanam\.config\.json$/i)) { const tanamConfig = configService.getPublicConfig(); console.log(`[express.assets] ${JSON.stringify({ tanamConfig })}`); response.json(tanamConfig); return null; } const requestUrl = request.url.split('/'); const distFolder = yield _getDistFolder(); response.sendFile(path_1.join(distFolder, ...requestUrl)); return null; })); // Handle request for user uploaded files // DEPRECATED: use /_/file/:fileId app.get('/_/image/:fileId', (request, response) => __awaiter(this, void 0, void 0, function* () { const fileId = request.params.fileId; console.warn(`DEPRECATED: use /_/file/${fileId}`); console.log(`GET ${request.url} (fileId: ${fileId})`); console.log(`URL query parameters: ${JSON.stringify(request.query, null, 2)}`); const userFile = yield fileService.getUserFile(fileId); if (!userFile) { console.log(`[HTTP 404] No such file: ${request.url}`); response.status(404).send(`Not found: ${request.url}`); return; } const storagePath = userFile.variants[request.query.s || request.query.size] || userFile.filePath; response.setHeader('Cache-Control', `public, max-age=604800, s-maxage=31536000, stale-while-revalidate=600`); response.setHeader('Content-Type', fileService.getContentTypeFromPath(storagePath)); response.send(yield fileService.getFileContents(storagePath)); return null; })); // Handle request for user uploaded files app.get('/_/file/:fileId', (request, response) => __awaiter(this, void 0, void 0, function* () { const fileId = request.params.fileId; console.log(`GET ${request.url} (fileId: ${fileId})`); console.log(`URL query parameters: ${JSON.stringify(request.query, null, 2)}`); const userFile = yield fileService.getUserFile(fileId); if (!userFile) { console.log(`[HTTP 404] No such file: ${request.url}`); response.status(404).send(`Not found: ${request.url}`); return; } const storagePath = userFile.variants[request.query.s || request.query.size] || userFile.filePath; response.setHeader('Cache-Control', `public, max-age=604800, s-maxage=31536000, stale-while-revalidate=600`); response.setHeader('Content-Type', fileService.getContentTypeFromPath(storagePath)); response.send(yield fileService.getFileContents(storagePath)); return null; })); // Handle request for user uploaded files app.get(/^\/?_\/theme\/(.*)$/, (request, response) => __awaiter(this, void 0, void 0, function* () { console.log(`[theme handler] GET ${request.url}`); const match = request.url.match(/\/?_\/theme\/(.*)$/); const filePath = match[1]; if (!filePath) { console.error(`Could not match a file path for URL: ${request.url}`); response.status(500).send(`Could not handle request: ${request.url}`); return null; } const themeAssetFile = yield fileService.geThemeAssetsFile(filePath); if (!themeAssetFile) { console.log(`[HTTP 404] No media file: ${request.url}`); response.status(404).send(`Not found: ${request.url}`); return; } response.setHeader('Content-Type', fileService.getContentTypeFromPath(filePath)); response.setHeader('Cache-Control', `public, max-age=604800, s-maxage=31536000, stale-while-revalidate=600`); response.send(yield fileService.getFileContents(themeAssetFile.filePath)); return null; })); // Any other "underscore path" -> serve tanam Angular Admin App app.get(/^\/?_\/(.*)\/?$/i, (request, response) => __awaiter(this, void 0, void 0, function* () { const distFolder = yield _getDistFolder(); response.setHeader('Cache-Control', `public, max-age=600, s-maxage=3600, stale-while-revalidate=120`); response.sendFile(path_1.join(distFolder, 'admin.html')); return _registerHostname(request); })); app.get('/manifest.json', (request, response) => { response.status(404).send('Not implemented yet'); }); app.get('/sitemap.xml', (request, response) => __awaiter(this, void 0, void 0, function* () { const sitemap = yield fileService.getSitemap(); response.setHeader('Cache-Control', getCacheHeader()); response.send(sitemap); return null; })); app.get('/robots.txt', (request, response) => { const robotsDefinition = ['User-agent: *', 'Disallow: /_/']; response.send(robotsDefinition.join('\n')); }); app.get('/favicon.ico', (request, response) => __awaiter(this, void 0, void 0, function* () { response.setHeader('Content-Type', 'image/ico'); response.setHeader('Cache-Control', getCacheHeader()); const favicon = yield fileService.getFavicon(); if (favicon) { response.send(favicon); return null; } const distFolder = yield _getDistFolder(); response.sendFile(path_1.join(distFolder, 'favicon.ico')); return null; })); app.get('*', (request, response) => __awaiter(this, void 0, void 0, function* () { console.log(`GET ${request.url}`); const url = request.url.replace(/^\//, ''); // Remove leading slash const context = yield page_context_service_1.getPageContextByUrl(url); if (!context) { response.status(404); console.log(`[HTTP 404] page not found for: ${request.url}`); try { const html404 = yield render_1.renderPage404(); return response.send(html404); } catch (error) { return response.send(`Not found: ${request.url}`); } } const html = yield render_1.renderPage(context); if (!html) { console.error(`[HTTP 500] could not create template for: ${request.url}`); return response.status(500).send('Could not create HTML template document'); } response.setHeader('Cache-Control', getCacheHeader()); response.send(html); return _registerHostname(request); })); //# sourceMappingURL=index.js.map