File size: 3,879 Bytes
b91e262 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | import { useEffect } from 'react'
import { isNextRouterError } from '../../../../client/components/is-next-router-error'
import {
formatConsoleArgs,
parseConsoleArgs,
} from '../../../../client/lib/console'
import isError from '../../../../lib/is-error'
import { createConsoleError } from '../../../shared/console-error'
import { coerceError, setOwnerStackIfAvailable } from './stitched-error'
import { forwardUnhandledError, logUnhandledRejection } from '../forward-logs'
const queueMicroTask =
globalThis.queueMicrotask || ((cb: () => void) => Promise.resolve().then(cb))
type ErrorHandler = (error: Error) => void
const errorQueue: Array<Error> = []
const errorHandlers: Array<ErrorHandler> = []
const rejectionQueue: Array<Error> = []
const rejectionHandlers: Array<ErrorHandler> = []
export function handleConsoleError(
originError: unknown,
consoleErrorArgs: any[]
) {
let error: Error
const { environmentName } = parseConsoleArgs(consoleErrorArgs)
if (isError(originError)) {
error = createConsoleError(originError, environmentName)
} else {
error = createConsoleError(
formatConsoleArgs(consoleErrorArgs),
environmentName
)
}
setOwnerStackIfAvailable(error)
errorQueue.push(error)
for (const handler of errorHandlers) {
// Delayed the error being passed to React Dev Overlay,
// avoid the state being synchronously updated in the component.
queueMicroTask(() => {
handler(error)
})
}
}
export function handleClientError(error: Error) {
errorQueue.push(error)
for (const handler of errorHandlers) {
// Delayed the error being passed to React Dev Overlay,
// avoid the state being synchronously updated in the component.
queueMicroTask(() => {
handler(error)
})
}
}
export function useErrorHandler(
handleOnUnhandledError: ErrorHandler,
handleOnUnhandledRejection: ErrorHandler
) {
useEffect(() => {
// Handle queued errors.
errorQueue.forEach(handleOnUnhandledError)
rejectionQueue.forEach(handleOnUnhandledRejection)
// Listen to new errors.
errorHandlers.push(handleOnUnhandledError)
rejectionHandlers.push(handleOnUnhandledRejection)
return () => {
// Remove listeners.
errorHandlers.splice(errorHandlers.indexOf(handleOnUnhandledError), 1)
rejectionHandlers.splice(
rejectionHandlers.indexOf(handleOnUnhandledRejection),
1
)
// Reset error queues.
errorQueue.splice(0, errorQueue.length)
rejectionQueue.splice(0, rejectionQueue.length)
}
}, [handleOnUnhandledError, handleOnUnhandledRejection])
}
function onUnhandledError(event: WindowEventMap['error']): void | boolean {
const thrownValue: unknown = event.error
if (isNextRouterError(thrownValue)) {
event.preventDefault()
return false
}
// When there's an error property present, we log the error to error overlay.
// Otherwise we don't do anything as it's not logging in the console either.
if (thrownValue) {
const error = coerceError(thrownValue)
setOwnerStackIfAvailable(error)
handleClientError(error)
forwardUnhandledError(error)
}
}
function onUnhandledRejection(ev: WindowEventMap['unhandledrejection']): void {
const reason: unknown = ev?.reason
if (isNextRouterError(reason)) {
ev.preventDefault()
return
}
const error = coerceError(reason)
setOwnerStackIfAvailable(error)
rejectionQueue.push(error)
for (const handler of rejectionHandlers) {
handler(error)
}
logUnhandledRejection(reason)
}
export function handleGlobalErrors() {
if (typeof window !== 'undefined') {
try {
// Increase the number of stack frames on the client
Error.stackTraceLimit = 50
} catch {}
window.addEventListener('error', onUnhandledError)
window.addEventListener('unhandledrejection', onUnhandledRejection)
}
}
|