Files
devolucion_proveedores/apps/api/src/web-estatico.ts
T

41 lines
1.8 KiB
TypeScript
Raw Normal View History

import { extname, join } from 'node:path';
import type { NestExpressApplication } from '@nestjs/platform-express';
import type { NextFunction, Request, Response } from 'express';
/** Nombres con hash de Angular (main-HFVHQMEH.js, styles-RS7YTGMB.css): no cambian, se pueden cachear un año. */
const CON_HASH = /-[A-Z0-9]{8}\.(js|css)$/;
/**
* Cabeceras de seguridad básicas. HSTS solo si la API atiende por HTTPS: con HTTP obligaría al
* navegador a usar un HTTPS que no existe.
*/
export function cabecerasSeguridad(https: boolean) {
return (_req: Request, res: Response, next: NextFunction): void => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Referrer-Policy', 'same-origin');
if (https) res.setHeader('Strict-Transport-Security', 'max-age=31536000');
next();
};
}
/**
* La API sirve también la web compilada, así que todo queda en el mismo origen (sin CORS y con la
* cookie de sesión SameSite=strict). Las rutas de Angular (sin extensión y fuera de /api) devuelven
* index.html; un archivo que no existe responde 404 en lugar de index.html.
*/
export function servirWeb(app: NestExpressApplication, carpeta: string): void {
app.useStaticAssets(carpeta, {
index: false,
setHeaders: (res, ruta) => {
res.setHeader('Cache-Control', CON_HASH.test(ruta) ? 'public, max-age=31536000, immutable' : 'no-cache');
},
});
app.use((req: Request, res: Response, next: NextFunction) => {
const esRutaDeLaApp = (req.method === 'GET' || req.method === 'HEAD') && !req.path.startsWith('/api') && extname(req.path) === '';
if (!esRutaDeLaApp) return next();
res.setHeader('Cache-Control', 'no-cache');
res.sendFile(join(carpeta, 'index.html'), (error) => error && next(error));
});
}