.
This commit is contained in:
Generated
Vendored
+117
@@ -0,0 +1,117 @@
|
||||
'use client';
|
||||
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
||||
/**
|
||||
* HTTPAccessFallbackBoundary is a boundary that catches errors and renders a
|
||||
* fallback component for HTTP errors.
|
||||
*
|
||||
* It receives the status code, and determine if it should render fallbacks for few HTTP 4xx errors.
|
||||
*
|
||||
* e.g. 404
|
||||
* 404 represents not found, and the fallback component pair contains the component and its styles.
|
||||
*
|
||||
*/ import React, { useContext } from 'react';
|
||||
import { useUntrackedPathname } from '../navigation-untracked';
|
||||
import { HTTPAccessErrorStatus, getAccessFallbackHTTPStatus, getAccessFallbackErrorTypeByStatus, isHTTPAccessFallbackError } from './http-access-fallback';
|
||||
import { warnOnce } from '../../../shared/lib/utils/warn-once';
|
||||
import { MissingSlotContext } from '../../../shared/lib/app-router-context.shared-runtime';
|
||||
class HTTPAccessFallbackErrorBoundary extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
triggeredStatus: undefined,
|
||||
previousPathname: props.pathname
|
||||
};
|
||||
}
|
||||
componentDidCatch() {
|
||||
if (process.env.NODE_ENV === 'development' && this.props.missingSlots && this.props.missingSlots.size > 0 && // A missing children slot is the typical not-found case, so no need to warn
|
||||
!this.props.missingSlots.has('children')) {
|
||||
let warningMessage = 'No default component was found for a parallel route rendered on this page. Falling back to nearest NotFound boundary.\n' + 'Learn more: https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#defaultjs\n\n';
|
||||
const formattedSlots = Array.from(this.props.missingSlots).sort((a, b)=>a.localeCompare(b)).map((slot)=>`@${slot}`).join(', ');
|
||||
warningMessage += 'Missing slots: ' + formattedSlots;
|
||||
warnOnce(warningMessage);
|
||||
}
|
||||
}
|
||||
static getDerivedStateFromError(error) {
|
||||
if (isHTTPAccessFallbackError(error)) {
|
||||
const httpStatus = getAccessFallbackHTTPStatus(error);
|
||||
return {
|
||||
triggeredStatus: httpStatus
|
||||
};
|
||||
}
|
||||
// Re-throw if error is not for 404
|
||||
throw error;
|
||||
}
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
/**
|
||||
* Handles reset of the error boundary when a navigation happens.
|
||||
* Ensures the error boundary does not stay enabled when navigating to a new page.
|
||||
* Approach of setState in render is safe as it checks the previous pathname and then overrides
|
||||
* it as outlined in https://react.dev/reference/react/useState#storing-information-from-previous-renders
|
||||
*/ if (props.pathname !== state.previousPathname && state.triggeredStatus) {
|
||||
return {
|
||||
triggeredStatus: undefined,
|
||||
previousPathname: props.pathname
|
||||
};
|
||||
}
|
||||
return {
|
||||
triggeredStatus: state.triggeredStatus,
|
||||
previousPathname: props.pathname
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const { notFound, forbidden, unauthorized, children } = this.props;
|
||||
const { triggeredStatus } = this.state;
|
||||
const errorComponents = {
|
||||
[HTTPAccessErrorStatus.NOT_FOUND]: notFound,
|
||||
[HTTPAccessErrorStatus.FORBIDDEN]: forbidden,
|
||||
[HTTPAccessErrorStatus.UNAUTHORIZED]: unauthorized
|
||||
};
|
||||
if (triggeredStatus) {
|
||||
const isNotFound = triggeredStatus === HTTPAccessErrorStatus.NOT_FOUND && notFound;
|
||||
const isForbidden = triggeredStatus === HTTPAccessErrorStatus.FORBIDDEN && forbidden;
|
||||
const isUnauthorized = triggeredStatus === HTTPAccessErrorStatus.UNAUTHORIZED && unauthorized;
|
||||
// If there's no matched boundary in this layer, keep throwing the error by rendering the children
|
||||
if (!(isNotFound || isForbidden || isUnauthorized)) {
|
||||
return children;
|
||||
}
|
||||
return /*#__PURE__*/ _jsxs(_Fragment, {
|
||||
children: [
|
||||
/*#__PURE__*/ _jsx("meta", {
|
||||
name: "robots",
|
||||
content: "noindex"
|
||||
}),
|
||||
process.env.NODE_ENV === 'development' && /*#__PURE__*/ _jsx("meta", {
|
||||
name: "boundary-next-error",
|
||||
content: getAccessFallbackErrorTypeByStatus(triggeredStatus)
|
||||
}),
|
||||
errorComponents[triggeredStatus]
|
||||
]
|
||||
});
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
||||
export function HTTPAccessFallbackBoundary({ notFound, forbidden, unauthorized, children }) {
|
||||
// When we're rendering the missing params shell, this will return null. This
|
||||
// is because we won't be rendering any not found boundaries or error
|
||||
// boundaries for the missing params shell. When this runs on the client
|
||||
// (where these error can occur), we will get the correct pathname.
|
||||
const pathname = useUntrackedPathname();
|
||||
const missingSlots = useContext(MissingSlotContext);
|
||||
const hasErrorFallback = !!(notFound || forbidden || unauthorized);
|
||||
if (hasErrorFallback) {
|
||||
return /*#__PURE__*/ _jsx(HTTPAccessFallbackErrorBoundary, {
|
||||
pathname: pathname,
|
||||
notFound: notFound,
|
||||
forbidden: forbidden,
|
||||
unauthorized: unauthorized,
|
||||
missingSlots: missingSlots,
|
||||
children: children
|
||||
});
|
||||
}
|
||||
return /*#__PURE__*/ _jsx(_Fragment, {
|
||||
children: children
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=error-boundary.js.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
||||
import { styles } from '../styles/access-error-styles';
|
||||
export function HTTPAccessErrorFallback({ status, message }) {
|
||||
return /*#__PURE__*/ _jsxs(_Fragment, {
|
||||
children: [
|
||||
/*#__PURE__*/ _jsx("title", {
|
||||
children: `${status}: ${message}`
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("div", {
|
||||
style: styles.error,
|
||||
children: /*#__PURE__*/ _jsxs("div", {
|
||||
children: [
|
||||
/*#__PURE__*/ _jsx("style", {
|
||||
dangerouslySetInnerHTML: {
|
||||
/* Minified CSS from
|
||||
body { margin: 0; color: #000; background: #fff; }
|
||||
.next-error-h1 {
|
||||
border-right: 1px solid rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body { color: #fff; background: #000; }
|
||||
.next-error-h1 {
|
||||
border-right: 1px solid rgba(255, 255, 255, .3);
|
||||
}
|
||||
}
|
||||
*/ __html: `body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}`
|
||||
}
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("h1", {
|
||||
className: "next-error-h1",
|
||||
style: styles.h1,
|
||||
children: status
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("div", {
|
||||
style: styles.desc,
|
||||
children: /*#__PURE__*/ _jsx("h2", {
|
||||
style: styles.h2,
|
||||
children: message
|
||||
})
|
||||
})
|
||||
]
|
||||
})
|
||||
})
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=error-fallback.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../src/client/components/http-access-fallback/error-fallback.tsx"],"sourcesContent":["import { styles } from '../styles/access-error-styles'\n\nexport function HTTPAccessErrorFallback({\n status,\n message,\n}: {\n status: number\n message: string\n}) {\n return (\n <>\n {/* <head> */}\n <title>{`${status}: ${message}`}</title>\n {/* </head> */}\n <div style={styles.error}>\n <div>\n <style\n dangerouslySetInnerHTML={{\n /* Minified CSS from\n body { margin: 0; color: #000; background: #fff; }\n .next-error-h1 {\n border-right: 1px solid rgba(0, 0, 0, .3);\n }\n\n @media (prefers-color-scheme: dark) {\n body { color: #fff; background: #000; }\n .next-error-h1 {\n border-right: 1px solid rgba(255, 255, 255, .3);\n }\n }\n */\n __html: `body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}`,\n }}\n />\n <h1 className=\"next-error-h1\" style={styles.h1}>\n {status}\n </h1>\n <div style={styles.desc}>\n <h2 style={styles.h2}>{message}</h2>\n </div>\n </div>\n </div>\n </>\n )\n}\n"],"names":["styles","HTTPAccessErrorFallback","status","message","title","div","style","error","dangerouslySetInnerHTML","__html","h1","className","desc","h2"],"mappings":";AAAA,SAASA,MAAM,QAAQ,gCAA+B;AAEtD,OAAO,SAASC,wBAAwB,EACtCC,MAAM,EACNC,OAAO,EAIR;IACC,qBACE;;0BAEE,KAACC;0BAAO,GAAGF,OAAO,EAAE,EAAEC,SAAS;;0BAE/B,KAACE;gBAAIC,OAAON,OAAOO,KAAK;0BACtB,cAAA,MAACF;;sCACC,KAACC;4BACCE,yBAAyB;gCACvB;;;;;;;;;;;;cAYA,GACAC,QAAQ,CAAC,6NAA6N,CAAC;4BACzO;;sCAEF,KAACC;4BAAGC,WAAU;4BAAgBL,OAAON,OAAOU,EAAE;sCAC3CR;;sCAEH,KAACG;4BAAIC,OAAON,OAAOY,IAAI;sCACrB,cAAA,KAACC;gCAAGP,OAAON,OAAOa,EAAE;0CAAGV;;;;;;;;AAMnC","ignoreList":[0]}
|
||||
Generated
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
export const HTTPAccessErrorStatus = {
|
||||
NOT_FOUND: 404,
|
||||
FORBIDDEN: 403,
|
||||
UNAUTHORIZED: 401
|
||||
};
|
||||
const ALLOWED_CODES = new Set(Object.values(HTTPAccessErrorStatus));
|
||||
export const HTTP_ERROR_FALLBACK_ERROR_CODE = 'NEXT_HTTP_ERROR_FALLBACK';
|
||||
/**
|
||||
* Checks an error to determine if it's an error generated by
|
||||
* the HTTP navigation APIs `notFound()`, `forbidden()` or `unauthorized()`.
|
||||
*
|
||||
* @param error the error that may reference a HTTP access error
|
||||
* @returns true if the error is a HTTP access error
|
||||
*/ export function isHTTPAccessFallbackError(error) {
|
||||
if (typeof error !== 'object' || error === null || !('digest' in error) || typeof error.digest !== 'string') {
|
||||
return false;
|
||||
}
|
||||
const [prefix, httpStatus] = error.digest.split(';');
|
||||
return prefix === HTTP_ERROR_FALLBACK_ERROR_CODE && ALLOWED_CODES.has(Number(httpStatus));
|
||||
}
|
||||
export function getAccessFallbackHTTPStatus(error) {
|
||||
const httpStatus = error.digest.split(';')[1];
|
||||
return Number(httpStatus);
|
||||
}
|
||||
export function getAccessFallbackErrorTypeByStatus(status) {
|
||||
switch(status){
|
||||
case 401:
|
||||
return 'unauthorized';
|
||||
case 403:
|
||||
return 'forbidden';
|
||||
case 404:
|
||||
return 'not-found';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=http-access-fallback.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../src/client/components/http-access-fallback/http-access-fallback.ts"],"sourcesContent":["export const HTTPAccessErrorStatus = {\n NOT_FOUND: 404,\n FORBIDDEN: 403,\n UNAUTHORIZED: 401,\n}\n\nconst ALLOWED_CODES = new Set(Object.values(HTTPAccessErrorStatus))\n\nexport const HTTP_ERROR_FALLBACK_ERROR_CODE = 'NEXT_HTTP_ERROR_FALLBACK'\n\nexport type HTTPAccessFallbackError = Error & {\n digest: `${typeof HTTP_ERROR_FALLBACK_ERROR_CODE};${string}`\n}\n\n/**\n * Checks an error to determine if it's an error generated by\n * the HTTP navigation APIs `notFound()`, `forbidden()` or `unauthorized()`.\n *\n * @param error the error that may reference a HTTP access error\n * @returns true if the error is a HTTP access error\n */\nexport function isHTTPAccessFallbackError(\n error: unknown\n): error is HTTPAccessFallbackError {\n if (\n typeof error !== 'object' ||\n error === null ||\n !('digest' in error) ||\n typeof error.digest !== 'string'\n ) {\n return false\n }\n const [prefix, httpStatus] = error.digest.split(';')\n\n return (\n prefix === HTTP_ERROR_FALLBACK_ERROR_CODE &&\n ALLOWED_CODES.has(Number(httpStatus))\n )\n}\n\nexport function getAccessFallbackHTTPStatus(\n error: HTTPAccessFallbackError\n): number {\n const httpStatus = error.digest.split(';')[1]\n return Number(httpStatus)\n}\n\nexport function getAccessFallbackErrorTypeByStatus(\n status: number\n): 'not-found' | 'forbidden' | 'unauthorized' | undefined {\n switch (status) {\n case 401:\n return 'unauthorized'\n case 403:\n return 'forbidden'\n case 404:\n return 'not-found'\n default:\n return\n }\n}\n"],"names":["HTTPAccessErrorStatus","NOT_FOUND","FORBIDDEN","UNAUTHORIZED","ALLOWED_CODES","Set","Object","values","HTTP_ERROR_FALLBACK_ERROR_CODE","isHTTPAccessFallbackError","error","digest","prefix","httpStatus","split","has","Number","getAccessFallbackHTTPStatus","getAccessFallbackErrorTypeByStatus","status"],"mappings":"AAAA,OAAO,MAAMA,wBAAwB;IACnCC,WAAW;IACXC,WAAW;IACXC,cAAc;AAChB,EAAC;AAED,MAAMC,gBAAgB,IAAIC,IAAIC,OAAOC,MAAM,CAACP;AAE5C,OAAO,MAAMQ,iCAAiC,2BAA0B;AAMxE;;;;;;CAMC,GACD,OAAO,SAASC,0BACdC,KAAc;IAEd,IACE,OAAOA,UAAU,YACjBA,UAAU,QACV,CAAE,CAAA,YAAYA,KAAI,KAClB,OAAOA,MAAMC,MAAM,KAAK,UACxB;QACA,OAAO;IACT;IACA,MAAM,CAACC,QAAQC,WAAW,GAAGH,MAAMC,MAAM,CAACG,KAAK,CAAC;IAEhD,OACEF,WAAWJ,kCACXJ,cAAcW,GAAG,CAACC,OAAOH;AAE7B;AAEA,OAAO,SAASI,4BACdP,KAA8B;IAE9B,MAAMG,aAAaH,MAAMC,MAAM,CAACG,KAAK,CAAC,IAAI,CAAC,EAAE;IAC7C,OAAOE,OAAOH;AAChB;AAEA,OAAO,SAASK,mCACdC,MAAc;IAEd,OAAQA;QACN,KAAK;YACH,OAAO;QACT,KAAK;YACH,OAAO;QACT,KAAK;YACH,OAAO;QACT;YACE;IACJ;AACF","ignoreList":[0]}
|
||||
Reference in New Issue
Block a user