react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
95 lines (83 loc) • 2.95 kB
text/typescript
/* eslint-disable reanimated/use-reanimated-error */
;
import type { WorkletStackDetails } from './commonTypes';
type ReanimatedError = Error & 'ReanimatedError'; // signed type
interface ReanimatedErrorConstructor extends Error {
new (message?: string): ReanimatedError;
(message?: string): ReanimatedError;
readonly prototype: ReanimatedError;
}
const ReanimatedErrorConstructor: ReanimatedErrorConstructor =
function ReanimatedError(message?: string) {
'worklet';
const prefix = '[Reanimated]';
const errorInstance = new Error(message ? `${prefix} ${message}` : prefix);
errorInstance.name = 'ReanimatedError';
return errorInstance;
} as ReanimatedErrorConstructor;
export { ReanimatedErrorConstructor as ReanimatedError };
/**
* Registers `ReanimatedError` in global scope. Use it only for Worklet
* runtimes.
*/
export function registerReanimatedError() {
'worklet';
if (!_WORKLET) {
throw new Error(
'[Reanimated] registerReanimatedError() must be called on Worklet runtime'
);
}
(global as Record<string, unknown>).ReanimatedError =
ReanimatedErrorConstructor;
}
const _workletStackDetails = new Map<number, WorkletStackDetails>();
export function registerWorkletStackDetails(
hash: number,
stackDetails: WorkletStackDetails
) {
_workletStackDetails.set(hash, stackDetails);
}
function getBundleOffset(error: Error): [string, number, number] {
const frame = error.stack?.split('\n')?.[0];
if (frame) {
const parsedFrame = /@([^@]+):(\d+):(\d+)/.exec(frame);
if (parsedFrame) {
const [, file, line, col] = parsedFrame;
return [file, Number(line), Number(col)];
}
}
return ['unknown', 0, 0];
}
function processStack(stack: string): string {
const workletStackEntries = stack.match(/worklet_(\d+):(\d+):(\d+)/g);
let result = stack;
workletStackEntries?.forEach((match) => {
const [, hash, origLine, origCol] = match.split(/:|_/).map(Number);
const errorDetails = _workletStackDetails.get(hash);
if (!errorDetails) {
return;
}
const [error, lineOffset, colOffset] = errorDetails;
const [bundleFile, bundleLine, bundleCol] = getBundleOffset(error);
const line = origLine + bundleLine + lineOffset;
const col = origCol + bundleCol + colOffset;
result = result.replace(match, `${bundleFile}:${line}:${col}`);
});
return result;
}
export function reportFatalErrorOnJS({
message,
stack,
}: {
message: string;
stack?: string;
}) {
const error = new Error();
error.message = message;
error.stack = stack ? processStack(stack) : undefined;
error.name = 'ReanimatedError';
// @ts-ignore React Native's ErrorUtils implementation extends the Error type with jsEngine field
error.jsEngine = 'reanimated';
// @ts-ignore the reportFatalError method is an internal method of ErrorUtils not exposed in the type definitions
global.ErrorUtils.reportFatalError(error);
}