1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "EXAppViewController.h" 4#import "EXErrorRecoveryManager.h" 5#import "EXKernel.h" 6#import "EXKernelAppRecord.h" 7#import "EXReactAppManager.h" 8#import "EXReactAppExceptionHandler.h" 9#import "EXUtil.h" 10 11#import <React/RCTBridge.h> 12#import <React/RCTRedBox.h> 13 14RCTFatalHandler handleFatalReactError = ^(NSError *error) { 15 [EXUtil performSynchronouslyOnMainThread:^{ 16 EXKernelAppRecord *record = [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager appRecordForError:error]; 17 if (!record) { 18 // show the error on Home or on the main standalone app if we can't figure out who this error belongs to 19 if ([EXKernel sharedInstance].appRegistry.homeAppRecord) { 20 record = [EXKernel sharedInstance].appRegistry.homeAppRecord; 21 } else if ([EXKernel sharedInstance].appRegistry.standaloneAppRecord) { 22 record = [EXKernel sharedInstance].appRegistry.standaloneAppRecord; 23 } 24 } 25 if (record) { 26 [record.viewController maybeShowError:error]; 27 } 28 }]; 29}; 30 31NS_ASSUME_NONNULL_BEGIN 32 33@interface EXReactAppExceptionHandler () 34 35@property (nonatomic, weak) EXKernelAppRecord *appRecord; 36 37@end 38 39@implementation EXReactAppExceptionHandler 40 41- (instancetype)initWithAppRecord:(EXKernelAppRecord *)appRecord 42{ 43 if (self = [super init]) { 44 _appRecord = appRecord; 45 } 46 return self; 47} 48 49RCT_NOT_IMPLEMENTED(- (instancetype)init) 50 51- (void)handleSoftJSExceptionWithMessage:(nullable NSString *)message 52 stack:(nullable NSArray<NSDictionary<NSString *, id> *> *)stack 53 exceptionId:(NSNumber *)exceptionId 54 extraDataAsJSON:(nullable NSString *)extraDataAsJSON 55{ 56 // In RN 0.8 this was used to invoke the native red box errors (via `showErrorMessage`). 57 // The invocation has since been moved into the method that invokes this delegate method. 58 // https://github.com/facebook/react-native/commit/31b5b0ac010d44afe3e742e85c75a9ef9e72a9e0#diff-35e5d8a9e7e9ea80b2ccfd41b905b703 59} 60 61- (void)handleFatalJSExceptionWithMessage:(nullable NSString *)message 62 stack:(nullable NSArray<NSDictionary<NSString *, id> *> *)stack 63 exceptionId:(NSNumber *)exceptionId 64 extraDataAsJSON:(nullable NSString *)extraDataAsJSON 65{ 66 NSString *description = [@"Unhandled JS Exception: " stringByAppendingString:message]; 67 NSDictionary *errorInfo = @{ NSLocalizedDescriptionKey: description, RCTJSStackTraceKey: stack }; 68 NSError *error = [NSError errorWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; 69 70 [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager setError:error forScopeKey:_appRecord.scopeKey]; 71 72 if ([self _isProdHome]) { 73 RCTFatal(error); 74 } 75} 76 77- (void)updateJSExceptionWithMessage:(nullable NSString *)message 78 stack:(nullable NSArray *)stack 79 exceptionId:(NSNumber *)exceptionId 80{ 81 [[self _bridgeForRecord].redBox updateErrorMessage:message withStack:stack]; 82} 83 84#pragma mark - internal 85 86- (RCTBridge *)_bridgeForRecord 87{ 88 return _appRecord.appManager.reactBridge; 89} 90 91- (BOOL)_isProdHome 92{ 93 if (RCT_DEBUG) { 94 return NO; 95 } 96 return (_appRecord && _appRecord == [EXKernel sharedInstance].appRegistry.homeAppRecord); 97} 98 99@end 100 101NS_ASSUME_NONNULL_END 102