284 lines
8.3 KiB
JavaScript
284 lines
8.3 KiB
JavaScript
import React, { useEffect } from "react";
|
|
import * as Linking from "expo-linking";
|
|
import ErrorBoundary from "react-native-error-boundary";
|
|
import * as Sentry from "@sentry/react-native";
|
|
import { ErrorUtils } from "react-native";
|
|
import { createLogger } from "~/lib/logger";
|
|
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
|
|
|
import { authActions, permissionWizardActions } from "~/stores";
|
|
|
|
import "~/lib/mapbox";
|
|
import "~/i18n";
|
|
|
|
import { useFcm } from "~/notifications";
|
|
|
|
import storeSubscriptions from "./storeSubscriptions";
|
|
import AppRoot from "./AppRoot";
|
|
import useNetworkListener from "./useNetworkListener";
|
|
import handleDeepLink, { handleInitialURL } from "./handleDeepLink";
|
|
import AppLifecycleListener from "~/containers/AppLifecycleListener";
|
|
|
|
import { useUpdates } from "~/updates";
|
|
import Error from "~/components/Error";
|
|
|
|
import useTrackLocation from "~/hooks/useTrackLocation";
|
|
|
|
const appLogger = createLogger({
|
|
module: SYSTEM_SCOPES.APP,
|
|
feature: "core",
|
|
});
|
|
|
|
const lifecycleLogger = createLogger({
|
|
module: SYSTEM_SCOPES.LIFECYCLE,
|
|
feature: "error-handling",
|
|
});
|
|
|
|
// Initialize stores with error handling
|
|
const initializeStores = () => {
|
|
try {
|
|
appLogger.info("Initializing core stores and subscriptions");
|
|
|
|
// Initialize each store with error handling
|
|
const initializeStore = async (name, initFn) => {
|
|
try {
|
|
await Promise.resolve(initFn());
|
|
appLogger.debug(`${name} initialized successfully`);
|
|
} catch (error) {
|
|
lifecycleLogger.error(`Failed to initialize ${name}`, {
|
|
error: error?.message,
|
|
store: name,
|
|
});
|
|
errorHandler(error);
|
|
}
|
|
};
|
|
|
|
// Initialize stores sequentially to maintain order
|
|
initializeStore("authActions", authActions.init);
|
|
initializeStore("permissionWizard", permissionWizardActions.init);
|
|
initializeStore("storeSubscriptions", storeSubscriptions.init);
|
|
|
|
appLogger.info("Core initialization complete");
|
|
} catch (error) {
|
|
lifecycleLogger.error("Critical: Store initialization failed", {
|
|
error: error?.message,
|
|
});
|
|
errorHandler(error);
|
|
}
|
|
};
|
|
|
|
// Initialize stores immediately
|
|
initializeStores();
|
|
|
|
// Enhanced error handler with comprehensive error normalization and handling
|
|
const errorHandler = (error, stackTrace) => {
|
|
try {
|
|
// Normalize various error types
|
|
const normalizedError = (() => {
|
|
if (error instanceof Error) return error;
|
|
if (typeof error === "string") return new Error(error);
|
|
if (typeof error === "object" && error !== null) {
|
|
return new Error(error.message || JSON.stringify(error));
|
|
}
|
|
return new Error(String(error || "Unknown error"));
|
|
})();
|
|
|
|
// Enhance error with additional context
|
|
normalizedError.originalError = error;
|
|
normalizedError.timestamp = new Date().toISOString();
|
|
if (stackTrace) {
|
|
normalizedError.nativeStackTrace = stackTrace;
|
|
}
|
|
|
|
// Log error with comprehensive details
|
|
lifecycleLogger.error("Application error occurred", {
|
|
error: normalizedError.message,
|
|
type: error?.constructor?.name,
|
|
hasStackTrace: !!stackTrace,
|
|
timestamp: normalizedError.timestamp,
|
|
originalErrorType: typeof error,
|
|
});
|
|
|
|
// Capture to Sentry with enhanced context
|
|
Sentry.withScope((scope) => {
|
|
scope.setExtra("nativeStackTrace", stackTrace);
|
|
scope.setExtra("errorType", error?.constructor?.name);
|
|
scope.setExtra("isNativeError", !(error instanceof Error));
|
|
scope.setExtra("timestamp", normalizedError.timestamp);
|
|
scope.setExtra("originalErrorType", typeof error);
|
|
scope.setExtra("originalError", error);
|
|
Sentry.captureException(normalizedError);
|
|
});
|
|
|
|
lifecycleLogger.debug("Error details captured and sent to Sentry", {
|
|
errorType: error?.constructor?.name,
|
|
hasStackTrace: !!stackTrace,
|
|
timestamp: normalizedError.timestamp,
|
|
});
|
|
} catch (handlerError) {
|
|
// Fallback error handling if the main error handler fails
|
|
lifecycleLogger.error("Error handler failed", {
|
|
originalError: error?.message,
|
|
handlerError: handlerError?.message,
|
|
});
|
|
Sentry.captureException(handlerError);
|
|
}
|
|
};
|
|
|
|
// Set up global error handlers for both development and production
|
|
const setupGlobalErrorHandlers = () => {
|
|
try {
|
|
lifecycleLogger.info("Setting up global error handlers");
|
|
|
|
// Enhanced global error handler with fatal error detection
|
|
const globalErrorHandler = (error, isFatal) => {
|
|
try {
|
|
lifecycleLogger.error("Global error occurred", {
|
|
error: error?.message,
|
|
isFatal,
|
|
isDev: __DEV__,
|
|
});
|
|
|
|
// Add additional context for fatal errors
|
|
if (isFatal) {
|
|
Sentry.withScope((scope) => {
|
|
scope.setLevel("fatal");
|
|
scope.setExtra("isFatalError", true);
|
|
Sentry.captureException(error);
|
|
});
|
|
}
|
|
|
|
errorHandler(error, null);
|
|
} catch (handlerError) {
|
|
// Fallback if error handler fails
|
|
console.error("Fatal: Error handler failed", handlerError);
|
|
Sentry.captureException(handlerError);
|
|
}
|
|
};
|
|
|
|
// Set up global error handler
|
|
global.ErrorUtils.setGlobalHandler(globalErrorHandler);
|
|
lifecycleLogger.debug("Global error handler configured");
|
|
|
|
// Enhanced promise rejection handling
|
|
const rejectionTrackingOptions = {
|
|
onUnhandled: (id, error) => {
|
|
try {
|
|
lifecycleLogger.error("Unhandled promise rejection", {
|
|
error: error?.message,
|
|
id,
|
|
isDev: __DEV__,
|
|
});
|
|
|
|
// Add rejection specific context
|
|
Sentry.withScope((scope) => {
|
|
scope.setExtra("rejectionId", id);
|
|
scope.setTag("error_type", "unhandled_promise_rejection");
|
|
Sentry.captureException(error);
|
|
});
|
|
|
|
errorHandler(error, null);
|
|
} catch (handlerError) {
|
|
console.error("Failed to handle promise rejection", handlerError);
|
|
Sentry.captureException(handlerError);
|
|
}
|
|
},
|
|
onHandled: (id) => {
|
|
lifecycleLogger.debug("Previously unhandled promise now handled", {
|
|
id,
|
|
});
|
|
},
|
|
};
|
|
|
|
require("promise/setimmediate/rejection-tracking").enable(
|
|
rejectionTrackingOptions,
|
|
);
|
|
lifecycleLogger.debug("Promise rejection tracking enabled");
|
|
} catch (error) {
|
|
lifecycleLogger.error("Failed to setup global error handlers", {
|
|
error: error?.message,
|
|
});
|
|
Sentry.captureException(error);
|
|
}
|
|
};
|
|
|
|
// Initialize error handlers immediately
|
|
setupGlobalErrorHandlers();
|
|
|
|
function AppContent() {
|
|
appLogger.info("Initializing app features");
|
|
useFcm();
|
|
useUpdates();
|
|
useNetworkListener();
|
|
useTrackLocation();
|
|
|
|
// Handle deep links after app is initialized with error handling
|
|
useEffect(() => {
|
|
let subscription;
|
|
|
|
const setupDeepLinks = async () => {
|
|
try {
|
|
appLogger.info("Setting up deep link handlers");
|
|
await handleInitialURL().catch((error) => {
|
|
lifecycleLogger.error("Failed to handle initial URL", {
|
|
error: error?.message,
|
|
});
|
|
errorHandler(error);
|
|
});
|
|
|
|
subscription = Linking.addEventListener("url", (event) => {
|
|
try {
|
|
handleDeepLink(event.url);
|
|
} catch (error) {
|
|
lifecycleLogger.error("Deep link handling failed", {
|
|
url: event.url,
|
|
error: error?.message,
|
|
});
|
|
errorHandler(error);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
lifecycleLogger.error("Deep link setup failed", {
|
|
error: error?.message,
|
|
});
|
|
errorHandler(error);
|
|
}
|
|
};
|
|
|
|
setupDeepLinks();
|
|
|
|
return () => {
|
|
try {
|
|
appLogger.debug("Cleaning up deep link handlers");
|
|
if (subscription?.remove) {
|
|
subscription.remove();
|
|
}
|
|
} catch (error) {
|
|
lifecycleLogger.error("Failed to cleanup deep link handlers", {
|
|
error: error?.message,
|
|
});
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
appLogger.info("App initialization complete");
|
|
return (
|
|
<>
|
|
<AppLifecycleListener />
|
|
<AppRoot />
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<ErrorBoundary
|
|
onError={errorHandler}
|
|
FallbackComponent={Error}
|
|
// Add a buffer time before allowing reset to prevent immediate re-crashes
|
|
minTimeBetweenResets={1000}
|
|
>
|
|
<AppContent />
|
|
</ErrorBoundary>
|
|
);
|
|
}
|