RSS Git Download  Clone
Raw Blame History 3kB 67 lines
import { isbot } from 'isbot';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

export function botSsr(options) {
    if (!options) {
        throw new Error('ngx-bot-ssr: options required');
    }
    if (!options.shell) {
        throw new Error('ngx-bot-ssr: options.shell (path to index.csr.html) required');
    }
    if (typeof options.ssr !== 'function') {
        throw new Error('ngx-bot-ssr: options.ssr (handler returning Promise<Response|null|undefined>) required');
    }
    if (typeof options.writeResponse !== 'function') {
        throw new Error('ngx-bot-ssr: options.writeResponse (e.g. writeResponseToNodeResponse from @angular/ssr/node) required');
    }

    // Read the CSR shell once at construction. Sync I/O at boot is fine —
    // Node already does this for every require()/import. Beats paying the
    // latency on the first request, beats hiding ENOENT until traffic shows up.
    const shellHtml = readFileSync(options.shell, 'utf-8');

    const detect = options.isBot || ((ua) => !ua || isbot(ua));
    const cacheControl = options.shellCacheControl || 'no-cache';

    return function ngxBotSsrMiddleware(req, res, next) {
        res.setHeader('Vary', 'User-Agent');

        if (!detect(req.headers['user-agent'])) {
            res.setHeader('Content-Type', 'text/html; charset=utf-8');
            res.setHeader('Cache-Control', cacheControl);
            res.send(shellHtml);
            return;
        }

        Promise.resolve(options.ssr(req))
            .then((response) => (response ? options.writeResponse(response, res) : next()))
            .catch(next);
    };
}

export function angularBotSsr(options) {
    if (!options) {
        throw new Error('ngx-bot-ssr: angularBotSsr options required');
    }
    if (!options.browserDistFolder) {
        throw new Error('ngx-bot-ssr: angularBotSsr options.browserDistFolder required');
    }
    if (!options.angularApp || typeof options.angularApp.handle !== 'function') {
        throw new Error('ngx-bot-ssr: angularBotSsr options.angularApp (AngularNodeAppEngine instance) required');
    }
    if (typeof options.writeResponseToNodeResponse !== 'function') {
        throw new Error('ngx-bot-ssr: angularBotSsr options.writeResponseToNodeResponse (from @angular/ssr/node) required');
    }

    return botSsr({
        shell: options.shell || join(options.browserDistFolder, 'index.csr.html'),
        ssr: (req) => options.angularApp.handle(req),
        writeResponse: options.writeResponseToNodeResponse,
        isBot: options.isBot,
        shellCacheControl: options.shellCacheControl,
    });
}

export default { botSsr, angularBotSsr };