import { Cache, CacheKey } from '../lib/cache'; import { NextFetchEvent, NextMiddleware } from 'next/server'; import { URLS } from '../data/urls'; import Settings from 'settings'; import { urlLocaleMatcherRegex } from '../utils'; import { PzNextRequest } from '.'; import logger from '../utils/log'; import { ROUTES } from 'routes'; type PrettyUrlResult = { matched: boolean; path?: string; }; let APP_ROUTES: string[] = []; const legacyRoutes = Object.values(ROUTES); try { const generatedRoutes = require('routes/generated-routes'); const allRoutes = [...legacyRoutes, ...(generatedRoutes || [])]; APP_ROUTES = Array.from(new Set(allRoutes)); logger.debug('Loaded merged routes (legacy + generated)', { count: APP_ROUTES.length }); } catch (error) { APP_ROUTES = legacyRoutes; logger.debug('Loaded only legacy routes', { count: APP_ROUTES.length }); } const resolvePrettyUrlHandler = (pathname: string, ip: string | null) => async () => { let results = <{ old_path: string }[]>[]; let prettyUrlResult: PrettyUrlResult = { matched: false }; try { const requestUrl = URLS.misc.prettyUrls(pathname); logger.debug(`Resolving pretty url`, { pathname, requestUrl, ip }); const apiResponse = await fetch(requestUrl); const data = await apiResponse.json(); ({ results } = data); const matched = results.length > 0; const [{ old_path: path } = { old_path: '' }] = results; prettyUrlResult = { matched, path }; logger.trace('Pretty url result', { prettyUrlResult, ip }); } catch (error) { logger.error('Error resolving pretty url', { error, pathname, ip }); } return prettyUrlResult; }; const resolvePrettyUrl = async ( pathname: string, locale: string, ip: string | null ) => { return Cache.wrap( CacheKey.PrettyUrl(pathname), locale, resolvePrettyUrlHandler(pathname, ip), { useProxy: true, compressed: true } ); }; const withPrettyUrl = (middleware: NextMiddleware) => async (req: PzNextRequest, event: NextFetchEvent) => { if (Settings.usePrettyUrlRoute) { return middleware(req, event); } const url = req.nextUrl.clone(); const matchedLanguagePrefix = url.pathname.match( urlLocaleMatcherRegex )?.[0]; const prettyUrlPathname = url.pathname.replace(matchedLanguagePrefix, ''); const isValidPrettyUrlPath = (pathname: string) => { return ( new RegExp(/^\/[a-zA-Z0-9/]+(?:-[a-zA-Z0-9/]+)*$/).test(pathname) && !APP_ROUTES.some((route) => new RegExp(`^${route}$`).test(pathname)) ); }; const ip = req.headers.get('x-forwarded-for') ?? ''; if ( !isValidPrettyUrlPath(prettyUrlPathname) || Settings.commerceUrl === 'default' ) { return middleware(req, event); } const prettyUrlResult = await resolvePrettyUrl( Settings.localization.localeIncludedPrettyUrlPattern?.test( prettyUrlPathname ) ? url.pathname : prettyUrlPathname, matchedLanguagePrefix, ip ); if (prettyUrlResult.matched) { req.middlewareParams.rewrites.prettyUrl = prettyUrlResult.path; logger.debug('Resolved pretty url', { source: prettyUrlPathname, dest: prettyUrlResult.path, ip }); return middleware(req, event); } return middleware(req, event); }; export default withPrettyUrl;