|
| 1 | +const fs = require("fs"); |
| 2 | +const path = require("path"); |
| 3 | +const { execSync } = require("child_process"); |
| 4 | + |
| 5 | +// Get the app package name |
| 6 | +function getPackageName() { |
| 7 | + try { |
| 8 | + const appJson = JSON.parse(fs.readFileSync("./app.json", "utf8")); |
| 9 | + return ( |
| 10 | + appJson.expo.android?.package || "com.codebuilderinc.codebuilderapps" |
| 11 | + ); |
| 12 | + } catch (error) { |
| 13 | + console.error("Error getting package name:", error); |
| 14 | + return "com.codebuilderinc.codebuilderapps"; // Fallback |
| 15 | + } |
| 16 | +} |
| 17 | + |
| 18 | +// Setup the TS global handler that works with your actual error service |
| 19 | +function setupTSErrorHandler() { |
| 20 | + console.log("📱 Setting up TypeScript global error handler..."); |
| 21 | + |
| 22 | + const utilsDir = path.join("src", "utils"); |
| 23 | + const handlerPath = path.join(utilsDir, "errorHandler.ts"); |
| 24 | + |
| 25 | + // Create utils directory if it doesn't exist |
| 26 | + if (!fs.existsSync(utilsDir)) { |
| 27 | + fs.mkdirSync(utilsDir, { recursive: true }); |
| 28 | + } |
| 29 | + |
| 30 | + // Content for the error handler that uses your actual error reporting service |
| 31 | + const handlerContent = ` |
| 32 | +import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler'; |
| 33 | +import { Alert } from 'react-native'; |
| 34 | +import { errorReportingService } from '../services/errorReporting.service'; |
| 35 | +
|
| 36 | +// JavaScript Error Handler |
| 37 | +export const setupGlobalErrorHandlers = () => { |
| 38 | + setJSExceptionHandler((error, isFatal) => { |
| 39 | + // Log the error to your existing error reporting service |
| 40 | + errorReportingService.submitError?.(error) || |
| 41 | + errorReportingService.reportError?.(error) || |
| 42 | + console.error('Caught JS Exception:', error); |
| 43 | + |
| 44 | + // Show a friendly message instead of crashing |
| 45 | + Alert.alert( |
| 46 | + 'Unexpected Error Occurred', |
| 47 | + 'We encountered an issue. The app will continue running, and our team has been notified.', |
| 48 | + [{ text: 'OK' }] |
| 49 | + ); |
| 50 | + }, true); |
| 51 | +
|
| 52 | + // Native Exception Handler |
| 53 | + setNativeExceptionHandler( |
| 54 | + (exceptionString) => { |
| 55 | + // Handle native exceptions |
| 56 | + const error = new Error(\`Native Exception: \${exceptionString}\`); |
| 57 | + errorReportingService.submitError?.(error) || |
| 58 | + errorReportingService.reportError?.(error) || |
| 59 | + console.error('Caught Native Exception:', exceptionString); |
| 60 | + }, |
| 61 | + false, // don't force app to quit |
| 62 | + true // should catch all exceptions |
| 63 | + ); |
| 64 | +}; |
| 65 | +`; |
| 66 | + |
| 67 | + // Write the file |
| 68 | + fs.writeFileSync(handlerPath, handlerContent); |
| 69 | + console.log(`✅ Created error handler at ${handlerPath}`); |
| 70 | +} |
| 71 | + |
| 72 | +// Setup the Android native exception handler |
| 73 | +function setupNativeExceptionHandler() { |
| 74 | + console.log("📱 Setting up Android native exception handler..."); |
| 75 | + |
| 76 | + // Get package name and calculate path |
| 77 | + const packageName = getPackageName(); |
| 78 | + const packagePath = packageName.replace(/\./g, "/"); |
| 79 | + |
| 80 | + // Path to MainActivity.java |
| 81 | + const mainActivityPath = path.join( |
| 82 | + "android", |
| 83 | + "app", |
| 84 | + "src", |
| 85 | + "main", |
| 86 | + "java", |
| 87 | + packagePath, |
| 88 | + "MainActivity.java" |
| 89 | + ); |
| 90 | + |
| 91 | + // Check if file exists |
| 92 | + if (!fs.existsSync(mainActivityPath)) { |
| 93 | + console.log( |
| 94 | + `❌ MainActivity.java not found at ${mainActivityPath}. Run 'npx expo prebuild' first.` |
| 95 | + ); |
| 96 | + return; |
| 97 | + } |
| 98 | + |
| 99 | + // Read the file |
| 100 | + let mainActivityContent = fs.readFileSync(mainActivityPath, "utf8"); |
| 101 | + |
| 102 | + // Check if we've already modified this file |
| 103 | + if (mainActivityContent.includes("// CUSTOM EXCEPTION HANDLER")) { |
| 104 | + console.log("✅ Exception handler already set up in MainActivity.java"); |
| 105 | + return; |
| 106 | + } |
| 107 | + |
| 108 | + // Find the onCreate method or prepare to add it |
| 109 | + const onCreateRegex = |
| 110 | + /protected void onCreate\s*\(\s*Bundle savedInstanceState\s*\)\s*\{/; |
| 111 | + const hasOnCreate = onCreateRegex.test(mainActivityContent); |
| 112 | + |
| 113 | + let exceptionHandlerCode; |
| 114 | + |
| 115 | + if (hasOnCreate) { |
| 116 | + // Append to existing onCreate method |
| 117 | + exceptionHandlerCode = ` |
| 118 | + // CUSTOM EXCEPTION HANDLER |
| 119 | + final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); |
| 120 | + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { |
| 121 | + @Override |
| 122 | + public void uncaughtException(Thread thread, Throwable throwable) { |
| 123 | + // Log the exception but don't crash the app |
| 124 | + System.err.println("Caught unhandled exception: " + throwable.getMessage()); |
| 125 | + throwable.printStackTrace(); |
| 126 | + |
| 127 | + // Don't call the default handler to prevent crash screen |
| 128 | + // defaultHandler.uncaughtException(thread, throwable); |
| 129 | + } |
| 130 | + });`; |
| 131 | + |
| 132 | + // Insert the code after the opening brace of onCreate |
| 133 | + mainActivityContent = mainActivityContent.replace( |
| 134 | + onCreateRegex, |
| 135 | + `protected void onCreate(Bundle savedInstanceState) { |
| 136 | + super.onCreate(savedInstanceState);${exceptionHandlerCode}` |
| 137 | + ); |
| 138 | + } else { |
| 139 | + // Need to add the entire onCreate method |
| 140 | + exceptionHandlerCode = ` |
| 141 | + // CUSTOM EXCEPTION HANDLER |
| 142 | + @Override |
| 143 | + protected void onCreate(Bundle savedInstanceState) { |
| 144 | + super.onCreate(savedInstanceState); |
| 145 | + |
| 146 | + final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); |
| 147 | + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { |
| 148 | + @Override |
| 149 | + public void uncaughtException(Thread thread, Throwable throwable) { |
| 150 | + // Log the exception but don't crash the app |
| 151 | + System.err.println("Caught unhandled exception: " + throwable.getMessage()); |
| 152 | + throwable.printStackTrace(); |
| 153 | + |
| 154 | + // Don't call the default handler to prevent crash screen |
| 155 | + // defaultHandler.uncaughtException(thread, throwable); |
| 156 | + } |
| 157 | + }); |
| 158 | + } |
| 159 | +`; |
| 160 | + |
| 161 | + // Add the import for Bundle if needed |
| 162 | + if (!mainActivityContent.includes("import android.os.Bundle;")) { |
| 163 | + mainActivityContent = mainActivityContent.replace( |
| 164 | + "import com.facebook.react.ReactActivity;", |
| 165 | + "import com.facebook.react.ReactActivity;\nimport android.os.Bundle;" |
| 166 | + ); |
| 167 | + } |
| 168 | + |
| 169 | + // Find the closing brace of the class and insert before it |
| 170 | + const lastBraceIndex = mainActivityContent.lastIndexOf("}"); |
| 171 | + mainActivityContent = |
| 172 | + mainActivityContent.substring(0, lastBraceIndex) + |
| 173 | + exceptionHandlerCode + |
| 174 | + mainActivityContent.substring(lastBraceIndex); |
| 175 | + } |
| 176 | + |
| 177 | + // Write the modified file |
| 178 | + fs.writeFileSync(mainActivityPath, mainActivityContent); |
| 179 | + console.log("✅ Exception handler successfully added to MainActivity.java"); |
| 180 | +} |
| 181 | + |
| 182 | +// Run both setup functions |
| 183 | +function setupExceptionHandlers() { |
| 184 | + // Setup the JS error handler utility |
| 185 | + setupTSErrorHandler(); |
| 186 | + |
| 187 | + // Setup the native Android exception handler |
| 188 | + setupNativeExceptionHandler(); |
| 189 | + |
| 190 | + // Reminder for necessary package |
| 191 | + console.log(` |
| 192 | +🔔 Next steps: |
| 193 | +1. Add 'react-native-exception-handler' to your dependencies: |
| 194 | + npm install react-native-exception-handler |
| 195 | + |
| 196 | +2. Import and use the handler in your _layout.tsx: |
| 197 | + import { setupGlobalErrorHandlers } from '../src/utils/errorHandler'; |
| 198 | + |
| 199 | + // Add inside a useEffect: |
| 200 | + useEffect(() => { |
| 201 | + setupGlobalErrorHandlers(); |
| 202 | + }, []); |
| 203 | + `); |
| 204 | +} |
| 205 | + |
| 206 | +// Run the function if this is the main module |
| 207 | +if (require.main === module) { |
| 208 | + setupExceptionHandlers(); |
| 209 | +} |
| 210 | + |
| 211 | +module.exports = setupExceptionHandlers; |
0 commit comments