tanam
Version:
Pluggable CMS for Firebase
220 lines • 10.6 kB
JavaScript
;
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