UNPKG

next

Version:

The React Framework

329 lines (328 loc) • 16.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getUtils = getUtils; exports.vercelHeader = void 0; var _url = require("url"); var _querystring = require("querystring"); var _normalizeLocalePath = require("../../../../shared/lib/i18n/normalize-locale-path"); var _pathMatch = _interopRequireDefault(require("../../../../shared/lib/router/utils/path-match")); var _routeRegex = require("../../../../shared/lib/router/utils/route-regex"); var _routeMatcher = require("../../../../shared/lib/router/utils/route-matcher"); var _prepareDestination = require("../../../../shared/lib/router/utils/prepare-destination"); var _acceptHeader = require("../../../../server/accept-header"); var _detectLocaleCookie = require("../../../../shared/lib/i18n/detect-locale-cookie"); var _detectDomainLocale = require("../../../../shared/lib/i18n/detect-domain-locale"); var _denormalizePagePath = require("../../../../server/denormalize-page-path"); var _cookie = _interopRequireDefault(require("next/dist/compiled/cookie")); var _constants = require("../../../../shared/lib/constants"); var _requestMeta = require("../../../../server/request-meta"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const getCustomRouteMatcher = (0, _pathMatch).default(true); const vercelHeader = 'x-vercel-id'; exports.vercelHeader = vercelHeader; function getUtils({ page , i18n , basePath , rewrites , pageIsDynamic }) { let defaultRouteRegex; let dynamicRouteMatcher; let defaultRouteMatches; if (pageIsDynamic) { defaultRouteRegex = (0, _routeRegex).getRouteRegex(page); dynamicRouteMatcher = (0, _routeMatcher).getRouteMatcher(defaultRouteRegex); defaultRouteMatches = dynamicRouteMatcher(page); } function handleRewrites(req, parsedUrl) { for (const rewrite of rewrites){ const matcher = getCustomRouteMatcher(rewrite.source); let params = matcher(parsedUrl.pathname); if (rewrite.has && params) { const hasParams = (0, _prepareDestination).matchHas(req, rewrite.has, parsedUrl.query); if (hasParams) { Object.assign(params, hasParams); } else { params = false; } } if (params) { const { parsedDestination } = (0, _prepareDestination).prepareDestination({ appendParamsToQuery: true, destination: rewrite.destination, params: params, query: parsedUrl.query }); Object.assign(parsedUrl.query, parsedDestination.query); delete parsedDestination.query; Object.assign(parsedUrl, parsedDestination); let fsPathname = parsedUrl.pathname; if (basePath) { fsPathname = fsPathname.replace(new RegExp(`^${basePath}`), '') || '/'; } if (i18n) { const destLocalePathResult = (0, _normalizeLocalePath).normalizeLocalePath(fsPathname, i18n.locales); fsPathname = destLocalePathResult.pathname; parsedUrl.query.nextInternalLocale = destLocalePathResult.detectedLocale || params.nextInternalLocale; } if (fsPathname === page) { break; } if (pageIsDynamic && dynamicRouteMatcher) { const dynamicParams = dynamicRouteMatcher(fsPathname); if (dynamicParams) { parsedUrl.query = { ...parsedUrl.query, ...dynamicParams }; break; } } } } return parsedUrl; } function handleBasePath(req, parsedUrl) { // always strip the basePath if configured since it is required req.url = req.url.replace(new RegExp(`^${basePath}`), '') || '/'; parsedUrl.pathname = parsedUrl.pathname.replace(new RegExp(`^${basePath}`), '') || '/'; } function getParamsFromRouteMatches(req, renderOpts, detectedLocale) { return (0, _routeMatcher).getRouteMatcher(function() { const { groups , routeKeys } = defaultRouteRegex; return { re: { // Simulate a RegExp match from the \`req.url\` input exec: (str)=>{ const obj = (0, _querystring).parse(str); const matchesHasLocale = i18n && detectedLocale && obj['1'] === detectedLocale; // favor named matches if available const routeKeyNames = Object.keys(routeKeys || {}); const filterLocaleItem = (val)=>{ if (i18n) { // locale items can be included in route-matches // for fallback SSG pages so ensure they are // filtered const isCatchAll = Array.isArray(val); const _val = isCatchAll ? val[0] : val; if (typeof _val === 'string' && i18n.locales.some((item)=>{ if (item.toLowerCase() === _val.toLowerCase()) { detectedLocale = item; renderOpts.locale = detectedLocale; return true; } return false; })) { // remove the locale item from the match if (isCatchAll) { val.splice(0, 1); } // the value is only a locale item and // shouldn't be added return isCatchAll ? val.length === 0 : true; } } return false; }; if (routeKeyNames.every((name)=>obj[name] )) { return routeKeyNames.reduce((prev, keyName)=>{ const paramName = routeKeys === null || routeKeys === void 0 ? void 0 : routeKeys[keyName]; if (paramName && !filterLocaleItem(obj[keyName])) { prev[groups[paramName].pos] = obj[keyName]; } return prev; }, {}); } return Object.keys(obj).reduce((prev, key)=>{ if (!filterLocaleItem(obj[key])) { let normalizedKey = key; if (matchesHasLocale) { normalizedKey = parseInt(key, 10) - 1 + ''; } return Object.assign(prev, { [normalizedKey]: obj[key] }); } return prev; }, {}); } }, groups }; }())(req.headers['x-now-route-matches']); } function interpolateDynamicPath(pathname, params) { if (!defaultRouteRegex) return pathname; for (const param of Object.keys(defaultRouteRegex.groups)){ const { optional , repeat } = defaultRouteRegex.groups[param]; let builtParam = `[${repeat ? '...' : ''}${param}]`; if (optional) { builtParam = `[${builtParam}]`; } const paramIdx = pathname.indexOf(builtParam); if (paramIdx > -1) { let paramValue; if (Array.isArray(params[param])) { paramValue = params[param].map((v)=>v && encodeURIComponent(v) ).join('/'); } else { paramValue = params[param] && encodeURIComponent(params[param]); } pathname = pathname.slice(0, paramIdx) + (paramValue || '') + pathname.slice(paramIdx + builtParam.length); } } return pathname; } function normalizeVercelUrl(req, trustQuery) { // make sure to normalize req.url on Vercel to strip dynamic params // from the query which are added during routing if (pageIsDynamic && trustQuery && defaultRouteRegex) { const _parsedUrl = (0, _url).parse(req.url, true); delete _parsedUrl.search; for (const param of Object.keys(defaultRouteRegex.groups)){ delete _parsedUrl.query[param]; } req.url = (0, _url).format(_parsedUrl); } } function normalizeDynamicRouteParams(params) { let hasValidParams = true; if (!defaultRouteRegex) return { params, hasValidParams: false }; params = Object.keys(defaultRouteRegex.groups).reduce((prev, key)=>{ let value = params[key]; // if the value matches the default value we can't rely // on the parsed params, this is used to signal if we need // to parse x-now-route-matches or not const defaultValue = defaultRouteMatches[key]; const isDefaultValue = Array.isArray(defaultValue) ? defaultValue.some((defaultVal)=>{ return Array.isArray(value) ? value.some((val)=>val.includes(defaultVal) ) : value === null || value === void 0 ? void 0 : value.includes(defaultVal); }) : value === null || value === void 0 ? void 0 : value.includes(defaultValue); if (isDefaultValue || typeof value === 'undefined') { hasValidParams = false; } // non-provided optional values should be undefined so normalize // them to undefined if (defaultRouteRegex.groups[key].optional && (!value || Array.isArray(value) && value.length === 1 && // fallback optional catch-all SSG pages have // [[...paramName]] for the root path on Vercel (value[0] === 'index' || value[0] === `[[...${key}]]`))) { value = undefined; delete params[key]; } // query values from the proxy aren't already split into arrays // so make sure to normalize catch-all values if (value && typeof value === 'string' && defaultRouteRegex.groups[key].repeat) { value = value.split('/'); } if (value) { prev[key] = value; } return prev; }, {}); return { params, hasValidParams }; } function handleLocale(req, res, parsedUrl, routeNoAssetPath, shouldNotRedirect) { if (!i18n) return; const pathname = parsedUrl.pathname || '/'; let defaultLocale = i18n.defaultLocale; let detectedLocale = (0, _detectLocaleCookie).detectLocaleCookie(req, i18n.locales); let acceptPreferredLocale; try { acceptPreferredLocale = i18n.localeDetection !== false ? (0, _acceptHeader).acceptLanguage(req.headers['accept-language'], i18n.locales) : detectedLocale; } catch (_) { acceptPreferredLocale = detectedLocale; } const { host } = req.headers || {}; // remove port from host and remove port if present const hostname = host && host.split(':')[0].toLowerCase(); const detectedDomain = (0, _detectDomainLocale).detectDomainLocale(i18n.domains, hostname); if (detectedDomain) { defaultLocale = detectedDomain.defaultLocale; detectedLocale = defaultLocale; (0, _requestMeta).addRequestMeta(req, '__nextIsLocaleDomain', true); } // if not domain specific locale use accept-language preferred detectedLocale = detectedLocale || acceptPreferredLocale; let localeDomainRedirect; const localePathResult = (0, _normalizeLocalePath).normalizeLocalePath(pathname, i18n.locales); routeNoAssetPath = (0, _normalizeLocalePath).normalizeLocalePath(routeNoAssetPath, i18n.locales).pathname; if (localePathResult.detectedLocale) { detectedLocale = localePathResult.detectedLocale; req.url = (0, _url).format({ ...parsedUrl, pathname: localePathResult.pathname }); (0, _requestMeta).addRequestMeta(req, '__nextStrippedLocale', true); parsedUrl.pathname = localePathResult.pathname; } // If a detected locale is a domain specific locale and we aren't already // on that domain and path prefix redirect to it to prevent duplicate // content from multiple domains if (detectedDomain) { const localeToCheck = localePathResult.detectedLocale ? detectedLocale : acceptPreferredLocale; const matchedDomain = (0, _detectDomainLocale).detectDomainLocale(i18n.domains, undefined, localeToCheck); if (matchedDomain && matchedDomain.domain !== detectedDomain.domain) { localeDomainRedirect = `http${matchedDomain.http ? '' : 's'}://${matchedDomain.domain}/${localeToCheck === matchedDomain.defaultLocale ? '' : localeToCheck}`; } } const denormalizedPagePath = (0, _denormalizePagePath).denormalizePagePath(pathname); const detectedDefaultLocale = !detectedLocale || detectedLocale.toLowerCase() === defaultLocale.toLowerCase(); const shouldStripDefaultLocale = false; // detectedDefaultLocale && // denormalizedPagePath.toLowerCase() === \`/\${i18n.defaultLocale.toLowerCase()}\` const shouldAddLocalePrefix = !detectedDefaultLocale && denormalizedPagePath === '/'; detectedLocale = detectedLocale || i18n.defaultLocale; if (!shouldNotRedirect && !req.headers[vercelHeader] && i18n.localeDetection !== false && (localeDomainRedirect || shouldAddLocalePrefix || shouldStripDefaultLocale)) { // set the NEXT_LOCALE cookie when a user visits the default locale // with the locale prefix so that they aren't redirected back to // their accept-language preferred locale if (shouldStripDefaultLocale && acceptPreferredLocale !== defaultLocale) { const previous = res.getHeader('set-cookie'); res.setHeader('set-cookie', [ ...typeof previous === 'string' ? [ previous ] : Array.isArray(previous) ? previous : [], _cookie.default.serialize('NEXT_LOCALE', defaultLocale, { httpOnly: true, path: '/' }), ]); } res.setHeader('Location', (0, _url).format({ // make sure to include any query values when redirecting ...parsedUrl, pathname: localeDomainRedirect ? localeDomainRedirect : shouldStripDefaultLocale ? basePath || '/' : `${basePath}/${detectedLocale}` })); res.statusCode = _constants.TEMPORARY_REDIRECT_STATUS; res.end(); return; } detectedLocale = localePathResult.detectedLocale || detectedDomain && detectedDomain.defaultLocale || defaultLocale; return { defaultLocale, detectedLocale, routeNoAssetPath }; } return { handleLocale, handleRewrites, handleBasePath, defaultRouteRegex, normalizeVercelUrl, dynamicRouteMatcher, defaultRouteMatches, interpolateDynamicPath, getParamsFromRouteMatches, normalizeDynamicRouteParams }; } //# sourceMappingURL=utils.js.map