diff --git a/packages/backend/src/misc/is-error.ts b/packages/backend/src/misc/is-error.ts new file mode 100644 index 0000000000..1e1f00e48c --- /dev/null +++ b/packages/backend/src/misc/is-error.ts @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { types } from 'node:util'; + +const hasDOMException = Reflect.has(globalThis, 'DOMException'); +const hasErrorIsError = Reflect.has(globalThis.Error, 'isError'); + +/** + * Polyfill for https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/isError + */ +export const isError: IsErrorFunc = hasErrorIsError ? Error.isError : isErrorPolyfill; + +/** + * Returns true if error is an instance of Error, false otherwise. + * More robust than instanceof. + */ +export type IsErrorFunc = (error: unknown) => error is Error; + +export function isErrorPolyfill(error: unknown): error is Error { + // These are the fastest checks, so run them first + if (isErrorByInstance(error)) { + return true; + } + + // Errors must be a non-null object + if (typeof(error) !== 'object' || error == null) { + return false; + } + + // jest, and maybe a few other edge cases + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species + if ('constructor' in error && isErrorByInstance(error.constructor[Symbol.species])) { + return true; + } + + // If it looks like a duck and quacks like a duck... + if ('name' in error && typeof(error.name) === 'string') { + if ('message' in error && typeof(error.message) === 'string') { + if (!('stack' in error) || typeof(error.stack) === 'string' || typeof(error.stack) === 'undefined') { + return true; + } + } + } + + // Guess it's not :( + return false; +} + +function isErrorByInstance(error: unknown): boolean { + // It must be a non-null object + if (typeof(error) !== 'object' || error == null) { + return false; + } + + // An actual instance, nice + if (error instanceof Error) { + return true; + } + + // DOMException is an Error, just without the prototype chain + if (hasDOMException && error instanceof DOMException) { + return true; + } + + // Realm fuckery + if (types.isNativeError(error)) { + return true; + } + + return false; +} +