123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.tryGetPreviewData = tryGetPreviewData;
- exports.parseBody = parseBody;
- exports.apiResolver = apiResolver;
- var _ = require(".");
- var _bytes = _interopRequireDefault(require("next/dist/compiled/bytes"));
- var _jsonwebtoken = _interopRequireDefault(require("next/dist/compiled/jsonwebtoken"));
- var _cryptoUtils = require("../crypto-utils");
- var _etag = require("../lib/etag");
- var _sendPayload = require("../send-payload");
- var _stream = require("stream");
- var _contentType = require("next/dist/compiled/content-type");
- var _isError = _interopRequireDefault(require("../../lib/is-error"));
- var _utils = require("../../shared/lib/utils");
- var _interopDefault = require("../../lib/interop-default");
- var _index = require("./index");
- var _mockRequest = require("../lib/mock-request");
- function _interopRequireDefault(obj) {
- return obj && obj.__esModule ? obj : {
- default: obj
- };
- }
- function tryGetPreviewData(req, res, options) {
- // if an On-Demand revalidation is being done preview mode
- // is disabled
- if (options && (0, _).checkIsManualRevalidate(req, options).isManualRevalidate) {
- return false;
- }
- // Read cached preview data if present
- if (_index.SYMBOL_PREVIEW_DATA in req) {
- return req[_index.SYMBOL_PREVIEW_DATA];
- }
- const getCookies = (0, _index).getCookieParser(req.headers);
- let cookies;
- try {
- cookies = getCookies();
- } catch {
- // TODO: warn
- return false;
- }
- const hasBypass = _index.COOKIE_NAME_PRERENDER_BYPASS in cookies;
- const hasData = _index.COOKIE_NAME_PRERENDER_DATA in cookies;
- // Case: neither cookie is set.
- if (!(hasBypass || hasData)) {
- return false;
- }
- // Case: one cookie is set, but not the other.
- if (hasBypass !== hasData) {
- (0, _index).clearPreviewData(res);
- return false;
- }
- // Case: preview session is for an old build.
- if (cookies[_index.COOKIE_NAME_PRERENDER_BYPASS] !== options.previewModeId) {
- (0, _index).clearPreviewData(res);
- return false;
- }
- const tokenPreviewData = cookies[_index.COOKIE_NAME_PRERENDER_DATA];
- let encryptedPreviewData;
- try {
- encryptedPreviewData = _jsonwebtoken.default.verify(tokenPreviewData, options.previewModeSigningKey);
- } catch {
- // TODO: warn
- (0, _index).clearPreviewData(res);
- return false;
- }
- const decryptedPreviewData = (0, _cryptoUtils).decryptWithSecret(Buffer.from(options.previewModeEncryptionKey), encryptedPreviewData.data);
- try {
- // TODO: strict runtime type checking
- const data = JSON.parse(decryptedPreviewData);
- // Cache lookup
- Object.defineProperty(req, _index.SYMBOL_PREVIEW_DATA, {
- value: data,
- enumerable: false
- });
- return data;
- } catch {
- return false;
- }
- }
- /**
- * Parse `JSON` and handles invalid `JSON` strings
- * @param str `JSON` string
- */ function parseJson(str) {
- if (str.length === 0) {
- // special-case empty json body, as it's a common client-side mistake
- return {};
- }
- try {
- return JSON.parse(str);
- } catch (e) {
- throw new _index.ApiError(400, "Invalid JSON");
- }
- }
- async function parseBody(req, limit) {
- let contentType;
- try {
- contentType = (0, _contentType).parse(req.headers["content-type"] || "text/plain");
- } catch {
- contentType = (0, _contentType).parse("text/plain");
- }
- const { type , parameters } = contentType;
- const encoding = parameters.charset || "utf-8";
- let buffer;
- try {
- const getRawBody = require("next/dist/compiled/raw-body");
- buffer = await getRawBody(req, {
- encoding,
- limit
- });
- } catch (e) {
- if ((0, _isError).default(e) && e.type === "entity.too.large") {
- throw new _index.ApiError(413, `Body exceeded ${limit} limit`);
- } else {
- throw new _index.ApiError(400, "Invalid body");
- }
- }
- const body = buffer.toString();
- if (type === "application/json" || type === "application/ld+json") {
- return parseJson(body);
- } else if (type === "application/x-www-form-urlencoded") {
- const qs = require("querystring");
- return qs.decode(body);
- } else {
- return body;
- }
- }
- function getMaxContentLength(responseLimit) {
- if (responseLimit && typeof responseLimit !== "boolean") {
- return _bytes.default.parse(responseLimit);
- }
- return _index.RESPONSE_LIMIT_DEFAULT;
- }
- /**
- * Send `any` body to response
- * @param req request object
- * @param res response object
- * @param body of response
- */ function sendData(req, res, body) {
- if (body === null || body === undefined) {
- res.end();
- return;
- }
- // strip irrelevant headers/body
- if (res.statusCode === 204 || res.statusCode === 304) {
- res.removeHeader("Content-Type");
- res.removeHeader("Content-Length");
- res.removeHeader("Transfer-Encoding");
- if (process.env.NODE_ENV === "development" && body) {
- console.warn(`A body was attempted to be set with a 204 statusCode for ${req.url}, this is invalid and the body was ignored.\n` + `See more info here https://nextjs.org/docs/messages/invalid-api-status-body`);
- }
- res.end();
- return;
- }
- const contentType = res.getHeader("Content-Type");
- if (body instanceof _stream.Stream) {
- if (!contentType) {
- res.setHeader("Content-Type", "application/octet-stream");
- }
- body.pipe(res);
- return;
- }
- const isJSONLike = [
- "object",
- "number",
- "boolean"
- ].includes(typeof body);
- const stringifiedBody = isJSONLike ? JSON.stringify(body) : body;
- const etag = (0, _etag).generateETag(stringifiedBody);
- if ((0, _sendPayload).sendEtagResponse(req, res, etag)) {
- return;
- }
- if (Buffer.isBuffer(body)) {
- if (!contentType) {
- res.setHeader("Content-Type", "application/octet-stream");
- }
- res.setHeader("Content-Length", body.length);
- res.end(body);
- return;
- }
- if (isJSONLike) {
- res.setHeader("Content-Type", "application/json; charset=utf-8");
- }
- res.setHeader("Content-Length", Buffer.byteLength(stringifiedBody));
- res.end(stringifiedBody);
- }
- /**
- * Send `JSON` object
- * @param res response object
- * @param jsonBody of data
- */ function sendJson(res, jsonBody) {
- // Set header to application/json
- res.setHeader("Content-Type", "application/json; charset=utf-8");
- // Use send to handle request
- res.send(JSON.stringify(jsonBody));
- }
- function isNotValidData(str) {
- return typeof str !== "string" || str.length < 16;
- }
- function setPreviewData(res, data, options) {
- if (isNotValidData(options.previewModeId)) {
- throw new Error("invariant: invalid previewModeId");
- }
- if (isNotValidData(options.previewModeEncryptionKey)) {
- throw new Error("invariant: invalid previewModeEncryptionKey");
- }
- if (isNotValidData(options.previewModeSigningKey)) {
- throw new Error("invariant: invalid previewModeSigningKey");
- }
- const payload = _jsonwebtoken.default.sign({
- data: (0, _cryptoUtils).encryptWithSecret(Buffer.from(options.previewModeEncryptionKey), JSON.stringify(data))
- }, options.previewModeSigningKey, {
- algorithm: "HS256",
- ...options.maxAge !== undefined ? {
- expiresIn: options.maxAge
- } : undefined
- });
- // limit preview mode cookie to 2KB since we shouldn't store too much
- // data here and browsers drop cookies over 4KB
- if (payload.length > 2048) {
- throw new Error(`Preview data is limited to 2KB currently, reduce how much data you are storing as preview data to continue`);
- }
- const { serialize } = require("next/dist/compiled/cookie");
- const previous = res.getHeader("Set-Cookie");
- res.setHeader(`Set-Cookie`, [
- ...typeof previous === "string" ? [
- previous
- ] : Array.isArray(previous) ? previous : [],
- serialize(_index.COOKIE_NAME_PRERENDER_BYPASS, options.previewModeId, {
- httpOnly: true,
- sameSite: process.env.NODE_ENV !== "development" ? "none" : "lax",
- secure: process.env.NODE_ENV !== "development",
- path: "/",
- ...options.maxAge !== undefined ? {
- maxAge: options.maxAge
- } : undefined,
- ...options.path !== undefined ? {
- path: options.path
- } : undefined
- }),
- serialize(_index.COOKIE_NAME_PRERENDER_DATA, payload, {
- httpOnly: true,
- sameSite: process.env.NODE_ENV !== "development" ? "none" : "lax",
- secure: process.env.NODE_ENV !== "development",
- path: "/",
- ...options.maxAge !== undefined ? {
- maxAge: options.maxAge
- } : undefined,
- ...options.path !== undefined ? {
- path: options.path
- } : undefined
- }),
- ]);
- return res;
- }
- async function revalidate(urlPath, opts, req, context) {
- if (typeof urlPath !== "string" || !urlPath.startsWith("/")) {
- throw new Error(`Invalid urlPath provided to revalidate(), must be a path e.g. /blog/post-1, received ${urlPath}`);
- }
- const revalidateHeaders = {
- [_index.PRERENDER_REVALIDATE_HEADER]: context.previewModeId,
- ...opts.unstable_onlyGenerated ? {
- [_.PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER]: "1"
- } : {}
- };
- try {
- if (context.trustHostHeader) {
- const res = await fetch(`https://${req.headers.host}${urlPath}`, {
- method: "HEAD",
- headers: {
- ...revalidateHeaders,
- cookie: req.headers.cookie || ""
- }
- });
- // we use the cache header to determine successful revalidate as
- // a non-200 status code can be returned from a successful revalidate
- // e.g. notFound: true returns 404 status code but is successful
- const cacheHeader = res.headers.get("x-vercel-cache") || res.headers.get("x-nextjs-cache");
- if ((cacheHeader == null ? void 0 : cacheHeader.toUpperCase()) !== "REVALIDATED" && !(res.status === 404 && opts.unstable_onlyGenerated)) {
- throw new Error(`Invalid response ${res.status}`);
- }
- } else if (context.revalidate) {
- const { req: mockReq , res: mockRes , streamPromise , } = (0, _mockRequest).mockRequest(urlPath, revalidateHeaders, "GET");
- await context.revalidate(mockReq, mockRes);
- await streamPromise;
- if (mockRes.getHeader("x-nextjs-cache") !== "REVALIDATED" && !(mockRes.statusCode === 404 && opts.unstable_onlyGenerated)) {
- throw new Error(`Invalid response ${mockRes.statusCode}`);
- }
- } else {
- throw new Error(`Invariant: required internal revalidate method not passed to api-utils`);
- }
- } catch (err) {
- throw new Error(`Failed to revalidate ${urlPath}: ${(0, _isError).default(err) ? err.message : err}`);
- }
- }
- async function apiResolver(req, res, query, resolverModule, apiContext, propagateError, dev, page) {
- const apiReq = req;
- const apiRes = res;
- try {
- var ref, ref1, ref2;
- if (!resolverModule) {
- res.statusCode = 404;
- res.end("Not Found");
- return;
- }
- const config = resolverModule.config || {};
- const bodyParser = ((ref = config.api) == null ? void 0 : ref.bodyParser) !== false;
- var ref3;
- const responseLimit = (ref3 = (ref1 = config.api) == null ? void 0 : ref1.responseLimit) != null ? ref3 : true;
- const externalResolver = ((ref2 = config.api) == null ? void 0 : ref2.externalResolver) || false;
- // Parsing of cookies
- (0, _index).setLazyProp({
- req: apiReq
- }, "cookies", (0, _index).getCookieParser(req.headers));
- // Parsing query string
- apiReq.query = query;
- // Parsing preview data
- (0, _index).setLazyProp({
- req: apiReq
- }, "previewData", ()=>tryGetPreviewData(req, res, apiContext));
- // Checking if preview mode is enabled
- (0, _index).setLazyProp({
- req: apiReq
- }, "preview", ()=>apiReq.previewData !== false ? true : undefined);
- // Parsing of body
- if (bodyParser && !apiReq.body) {
- apiReq.body = await parseBody(apiReq, config.api && config.api.bodyParser && config.api.bodyParser.sizeLimit ? config.api.bodyParser.sizeLimit : "1mb");
- }
- let contentLength = 0;
- const maxContentLength = getMaxContentLength(responseLimit);
- const writeData = apiRes.write;
- const endResponse = apiRes.end;
- apiRes.write = (...args)=>{
- contentLength += Buffer.byteLength(args[0] || "");
- return writeData.apply(apiRes, args);
- };
- apiRes.end = (...args)=>{
- if (args.length && typeof args[0] !== "function") {
- contentLength += Buffer.byteLength(args[0] || "");
- }
- if (responseLimit && contentLength >= maxContentLength) {
- console.warn(`API response for ${req.url} exceeds ${_bytes.default.format(maxContentLength)}. API Routes are meant to respond quickly. https://nextjs.org/docs/messages/api-routes-response-size-limit`);
- }
- endResponse.apply(apiRes, args);
- };
- apiRes.status = (statusCode)=>(0, _index).sendStatusCode(apiRes, statusCode);
- apiRes.send = (data)=>sendData(apiReq, apiRes, data);
- apiRes.json = (data)=>sendJson(apiRes, data);
- apiRes.redirect = (statusOrUrl, url)=>(0, _index).redirect(apiRes, statusOrUrl, url);
- apiRes.setPreviewData = (data, options = {})=>setPreviewData(apiRes, data, Object.assign({}, apiContext, options));
- apiRes.clearPreviewData = (options = {})=>(0, _index).clearPreviewData(apiRes, options);
- apiRes.revalidate = (urlPath, opts)=>revalidate(urlPath, opts || {}, req, apiContext);
- // TODO: remove in next minor (current v12.2)
- apiRes.unstable_revalidate = ()=>{
- throw new Error(`"unstable_revalidate" has been renamed to "revalidate" see more info here: https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation`);
- };
- const resolver = (0, _interopDefault).interopDefault(resolverModule);
- let wasPiped = false;
- if (process.env.NODE_ENV !== "production") {
- // listen for pipe event and don't show resolve warning
- res.once("pipe", ()=>wasPiped = true);
- }
- // Call API route method
- await resolver(req, res);
- if (process.env.NODE_ENV !== "production" && !externalResolver && !(0, _utils).isResSent(res) && !wasPiped) {
- console.warn(`API resolved without sending a response for ${req.url}, this may result in stalled requests.`);
- }
- } catch (err) {
- if (err instanceof _index.ApiError) {
- (0, _index).sendError(apiRes, err.statusCode, err.message);
- } else {
- if (dev) {
- if ((0, _isError).default(err)) {
- err.page = page;
- }
- throw err;
- }
- console.error(err);
- if (propagateError) {
- throw err;
- }
- (0, _index).sendError(apiRes, 500, "Internal Server Error");
- }
- }
- }
- //# sourceMappingURL=node.js.map
|