убрал erroroverlay
This commit is contained in:
@@ -52,16 +52,26 @@ export function describeReason(reason: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
function logError(kind: string, reason: unknown): void {
|
||||
console.error(`${PREFIX} ${kind}: ${describeReason(reason)}`);
|
||||
// Стек и объект — отдельными аргументами: так консоль браузера даёт
|
||||
// раскрыть объект и перейти по стеку, а не показывает «[object Object]».
|
||||
if (reason instanceof Error && reason.stack) console.error(`${PREFIX} Стек:`, reason.stack);
|
||||
console.error(`${PREFIX} Причина целиком:`, reason);
|
||||
}
|
||||
|
||||
export function installErrorLogging(): void {
|
||||
if (installed) return;
|
||||
installed = true;
|
||||
|
||||
window.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => {
|
||||
const reason: unknown = e.reason;
|
||||
console.error(`${PREFIX} Необработанная ошибка: ${describeReason(reason)}`);
|
||||
// Стек и объект — отдельными аргументами: так консоль браузера даёт
|
||||
// раскрыть объект и перейти по стеку, а не показывает «[object Object]».
|
||||
if (reason instanceof Error && reason.stack) console.error(`${PREFIX} Стек:`, reason.stack);
|
||||
console.error(`${PREFIX} Причина целиком:`, reason);
|
||||
logError('Необработанная ошибка', e.reason);
|
||||
});
|
||||
|
||||
// Синхронные ошибки страницы. Раньше их ловил ErrorOverlay ради углового
|
||||
// уведомления; уведомление убрано (чужая ошибка со страницы SharePoint
|
||||
// выглядела как поломка портала), а разбор нужен — поэтому он переехал сюда.
|
||||
window.addEventListener('error', (e: ErrorEvent) => {
|
||||
logError('Ошибка на странице', e.error || e.message);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,73 +83,3 @@
|
||||
border-color: $gold-400;
|
||||
}
|
||||
}
|
||||
|
||||
// Уведомление об АСИНХРОННОЙ ошибке: в углу, поверх содержимого, но ничего не
|
||||
// перекрывает и не блокирует — портал остаётся рабочим (см. ErrorOverlay.tsx о
|
||||
// том, почему такие ошибки больше не схлопывают вебпарт).
|
||||
.toast {
|
||||
position: fixed;
|
||||
right: $sp-4;
|
||||
bottom: $sp-4;
|
||||
z-index: 999998;
|
||||
box-sizing: border-box;
|
||||
width: 360px;
|
||||
max-width: calc(100vw - #{$sp-4} * 2);
|
||||
padding: $sp-3 $sp-4;
|
||||
border-radius: $r-md;
|
||||
border-left: 4px solid $red-500;
|
||||
background: $white;
|
||||
box-shadow: $sh-modal;
|
||||
font-family: $font-sans;
|
||||
}
|
||||
|
||||
.toastHead {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: $sp-2;
|
||||
}
|
||||
|
||||
.toastTitle {
|
||||
font-size: $fs-sm;
|
||||
font-weight: 600;
|
||||
color: $navy-950;
|
||||
}
|
||||
|
||||
.toastClose {
|
||||
padding: 0 4px;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: $gray-500;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $navy-800;
|
||||
}
|
||||
}
|
||||
|
||||
.toastText {
|
||||
margin-top: $sp-1;
|
||||
max-height: 96px;
|
||||
overflow: auto;
|
||||
font-size: $fs-caption;
|
||||
color: $gray-900;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.toastCopy {
|
||||
margin-top: $sp-2;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: $r-sm;
|
||||
background: $white;
|
||||
color: $navy-800;
|
||||
font-size: $fs-caption;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: $gold-400;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ import styles from './ErrorOverlay.module.scss';
|
||||
interface ICaughtError {
|
||||
message: string;
|
||||
stack: string;
|
||||
source: string; // откуда поймана: React-рендер, window.onerror, unhandledrejection
|
||||
source: string;
|
||||
}
|
||||
|
||||
interface IErrorOverlayState {
|
||||
/** Падение самого рендера — дерево сломано, показывать нечего. */
|
||||
fatal: ICaughtError | undefined;
|
||||
/** Асинхронная ошибка со страницы — портал работает, это просто уведомление. */
|
||||
notice: ICaughtError | undefined;
|
||||
}
|
||||
|
||||
function formatError(err: unknown, source: string): ICaughtError {
|
||||
@@ -24,7 +22,7 @@ function formatError(err: unknown, source: string): ICaughtError {
|
||||
// at vp (request-portal-web-part_<hash>.js:9:397946)
|
||||
// at t.onUnhandledRejection (...)
|
||||
//
|
||||
// Место, где промис реально отклонился, при этом терялось полностью.
|
||||
// Место, где ошибка реально возникла, при этом терялось полностью.
|
||||
if (err instanceof Error) {
|
||||
return {
|
||||
message: err.message || describeReason(err),
|
||||
@@ -41,62 +39,30 @@ function copyToClipboard(text: string): void {
|
||||
navigator.clipboard.writeText(text).catch(() => undefined);
|
||||
}
|
||||
|
||||
function fullTextOf(error: ICaughtError): string {
|
||||
return `[${error.source}] ${error.message}\n\n${error.stack}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ловит падения, которые иначе схлопывают вебпарт в SPFx workbench.
|
||||
* Граница ошибок React: ловит падение РЕНДЕРА, из-за которого вебпарт иначе
|
||||
* схлопывается в пустое место, и показывает вместо него текст ошибки со стеком.
|
||||
*
|
||||
* Разделение на fatal и notice — не косметика. Слушатели `error` и
|
||||
* `unhandledrejection` вешаются на window, то есть срабатывают на ВСЮ страницу
|
||||
* SharePoint: на другие вебпарты, на скрипты самого SharePoint, на любые
|
||||
* фоновые промисы. Раньше любое такое отклонение — даже постороннее и
|
||||
* безобидное — заменяло собой весь портал: вместо открытой заявки пользователь
|
||||
* получал экран «Необработанное исключение». Именно так это и проявлялось при
|
||||
* переходе по ссылке на заявку.
|
||||
*
|
||||
* Теперь портал заменяется только при падении РЕНДЕРА (там показывать
|
||||
* действительно нечего), а асинхронные ошибки — угловое уведомление, которое
|
||||
* можно закрыть; работать оно не мешает. Полный разбор причины при этом всё
|
||||
* равно уходит в консоль — см. shared/diagnostics.ts.
|
||||
* Асинхронные ошибки страницы (`unhandledrejection`, `window.onerror`) здесь
|
||||
* НЕ ловятся намеренно. Слушатели на window срабатывают на всю страницу
|
||||
* SharePoint — на другие вебпарты, на скрипты самого SharePoint, на любые
|
||||
* фоновые промисы, — и показывать по ним что-то пользователю смысла нет: портал
|
||||
* при этом работает, а чужая ошибка выглядит как поломка портала. Разбор таких
|
||||
* ошибок уходит в консоль, см. shared/diagnostics.ts.
|
||||
*/
|
||||
export class ErrorOverlay extends React.Component<React.PropsWithChildren<unknown>, IErrorOverlayState> {
|
||||
public state: IErrorOverlayState = { fatal: undefined, notice: undefined };
|
||||
public state: IErrorOverlayState = { fatal: undefined };
|
||||
|
||||
private onWindowError = (event: ErrorEvent): void => {
|
||||
this.showNotice(formatError(event.error || event.message, 'window.onerror'));
|
||||
};
|
||||
|
||||
private onUnhandledRejection = (event: PromiseRejectionEvent): void => {
|
||||
this.showNotice(formatError(event.reason, 'unhandledrejection'));
|
||||
};
|
||||
|
||||
private showNotice(notice: ICaughtError): void {
|
||||
// Первое уведомление не затираем: за одной ошибкой обычно летит несколько
|
||||
// производных, а разбираться нужно с исходной.
|
||||
this.setState((prev) => (prev.notice ? prev : { ...prev, notice }));
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
window.addEventListener('error', this.onWindowError);
|
||||
window.addEventListener('unhandledrejection', this.onUnhandledRejection);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
window.removeEventListener('error', this.onWindowError);
|
||||
window.removeEventListener('unhandledrejection', this.onUnhandledRejection);
|
||||
}
|
||||
|
||||
public static getDerivedStateFromError(err: unknown): Partial<IErrorOverlayState> {
|
||||
public static getDerivedStateFromError(err: unknown): IErrorOverlayState {
|
||||
return { fatal: formatError(err, 'React render') };
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const { fatal, notice } = this.state;
|
||||
const { fatal } = this.state;
|
||||
if (!fatal) return this.props.children;
|
||||
|
||||
const fullText = `[${fatal.source}] ${fatal.message}\n\n${fatal.stack}`;
|
||||
|
||||
if (fatal) {
|
||||
const fullText = fullTextOf(fatal);
|
||||
return (
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.card}>
|
||||
@@ -107,11 +73,7 @@ export class ErrorOverlay extends React.Component<React.PropsWithChildren<unknow
|
||||
<button type="button" className={styles.copyBtn} onClick={() => copyToClipboard(fullText)}>
|
||||
Скопировать
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.reloadBtn}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
<button type="button" className={styles.reloadBtn} onClick={() => window.location.reload()}>
|
||||
Перезагрузить страницу
|
||||
</button>
|
||||
</div>
|
||||
@@ -119,34 +81,4 @@ export class ErrorOverlay extends React.Component<React.PropsWithChildren<unknow
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.props.children}
|
||||
{notice && (
|
||||
<div className={styles.toast} role="alert">
|
||||
<div className={styles.toastHead}>
|
||||
<span className={styles.toastTitle}>Что-то пошло не так</span>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.toastClose}
|
||||
aria-label="Закрыть уведомление"
|
||||
onClick={() => this.setState({ notice: undefined })}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.toastText}>{notice.message}</div>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.toastCopy}
|
||||
onClick={() => copyToClipboard(fullTextOf(notice))}
|
||||
>
|
||||
Скопировать детали
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user