const utils = require("../lib/utils") const { release } = require("../config-client") const { ssrValidatePath } = require("../config") const { ssrRequest } = require("../lib/ssr-server") ;(function () { var request = context.request() var trackingCall = request.header("x-ssr-skip") if (trackingCall) { // skip tracking // no cache header context.response.header("Cache-Control", "no-cache, no-store, must-revalidate") throw { status: parseInt(trackingCall), html: "", log: false, } } let url = request.query("url") || request.header("x-ssr-url") || "/" const noCache = request.query("noCache") const trace_id = context.debug.sentryTraceId() /** * @param {string} content */ function addSentryTrace(content) { return content.replace("", '') } context.response.header("sentry-trace", trace_id) const auth = context.user.auth() if (auth && auth.role == 0) { } else if (url) { /** @type {Date} */ // @ts-ignore context.ssrCacheValidUntil = null var comment = "" url = url.split("?")[0] comment += "url: " + url if (url && url.length > 1) { url = url.replace(/\/$/, "") } if (url == "/index" || !url) { url = "/" // see .htaccess } function useCache(/** @type {string} */ _url) { var cache = !noCache && context.db.find("ssr", { filter: { path: _url, }, }) if (cache && cache.length) { // check that entry is still allowed to be published const validUntil = cache[0].validUntil ? new Date(cache[0].validUntil.unixMilli()) : null if (!validUntil || validUntil > new Date()) { // use cache context.response.header("X-SSR-Cache", "true") throw { status: 200, log: false, html: addSentryTrace(cache[0].content), } } else { // cache is invalid, delete it context.response.header("X-SSR-Cache", "invalid") context.db.delete("ssr", cache[0].id) } } } // validate url var status = 200 var pNorender = false var pNotfound = false const pR = ssrValidatePath(url) if (pR === -1) { pNotfound = true comment += ", notFound" } else if (!pR) { pNorender = true comment += ", noRender" } else if (typeof pR === "string") { url = pR comment += ", cache url: " + url } if (noCache) { comment += ", noCache" } if (!pNorender && !pNotfound) { // check if we have a cache useCache(url) } let head = "" let html = "" let error = "" comment += ", path: " + url var cacheIt = false if (pNorender) { html = "" } else if (pNotfound) { status = 404 html = "404 NOT FOUND" } else { // @ts-ignore context.ssrCache = {} // @ts-ignore – tracks dependencies as { "col:id": true, "col:*": true } context.ssrDeps = {} // @ts-ignore context.ssrRequest = ssrRequest try { // if error, output plain html without prerendering // @ts-ignore const app = require("../lib/app.server") const rendered = app.default.render({ url: url, }) head = rendered.head html = rendered.html head += "\n\n" + "" // status from webapp // @ts-ignore if (context.is404) { status = 404 } else { cacheIt = true } } catch (/** @type {any} */ e) { utils.log(e.message) utils.log(e.stack) error = "error: " + e.message + "\n\n" + e.stack // utils.log(e) // for (var property in e) { // utils.log(property + ": " + e[property]) // } // error = JSON.stringify(e) } } var tpl = context.fs.readFile("templates/spa.html") // Extract language from URL for (before other replacements) var langMatch = url.match(/^\/(de|en)(\/|$)/) var pageLang = langMatch ? langMatch[1] : "de" tpl = tpl.replace(//, '') tpl = tpl.replace("", head) tpl = tpl.replace("", html) tpl = tpl.replace("", error ? "" : "") // Deduplicate deps: if "col:*" exists, drop all "col:" for that collection // @ts-ignore – ssrDeps is set dynamically above var depsKeys = context.ssrDeps ? Object.keys(context.ssrDeps) : [] /** @type {{[key: string]: boolean}} */ var wildcardCols = {} depsKeys.forEach(function (k) { if (k.endsWith(":*")) wildcardCols[k.split(":")[0]] = true }) depsKeys = depsKeys.filter(function (k) { if (k.endsWith(":*")) return true return !wildcardCols[k.split(":")[0]] }) // @ts-ignore – append deps for debugging comment += ", deps: [" + depsKeys.join(", ") + "]" tpl = tpl.replace("", comment ? "" : "") if (cacheIt && !noCache) { // save cache context.db.create("ssr", { path: url, content: tpl, // @ts-ignore validUntil: context.ssrCacheValidUntil, // dependency strings: "col:id" for detail, "col:*" for list (deduplicated) dependencies: depsKeys, }) } throw { status: status, log: false, html: addSentryTrace(tpl), } } else { // only admins are allowed throw { status: 403, message: "invalid auth", auth: auth, release: release, } } })()