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 <Crashlytics/Crashlytics.h> 12#import <React/RCTBridge.h> 13#import <React/RCTRedBox.h> 14 15RCTFatalHandler handleFatalReactError = ^(NSError *error) { 16 [EXUtil performSynchronouslyOnMainThread:^{ 17 EXKernelAppRecord *record = [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager appRecordForError:error]; 18 if (!record) { 19 // show the error on Home or on the main standalone app if we can't figure out who this error belongs to 20 if ([EXKernel sharedInstance].appRegistry.homeAppRecord) { 21 record = [EXKernel sharedInstance].appRegistry.homeAppRecord; 22 } else if ([EXKernel sharedInstance].appRegistry.standaloneAppRecord) { 23 record = [EXKernel sharedInstance].appRegistry.standaloneAppRecord; 24 } 25 } 26 if (record) { 27 [record.viewController maybeShowError:error]; 28 } 29 }]; 30}; 31 32NS_ASSUME_NONNULL_BEGIN 33 34@interface EXReactAppExceptionHandler () 35 36@property (nonatomic, weak) EXKernelAppRecord *appRecord; 37 38@end 39 40@implementation EXReactAppExceptionHandler 41 42- (instancetype)initWithAppRecord:(EXKernelAppRecord *)appRecord 43{ 44 if (self = [super init]) { 45 _appRecord = appRecord; 46 } 47 return self; 48} 49 50RCT_NOT_IMPLEMENTED(- (instancetype)init) 51 52- (void)handleSoftJSExceptionWithMessage:(nullable NSString *)message 53 stack:(nullable NSArray<NSDictionary<NSString *, id> *> *)stack 54 exceptionId:(NSNumber *)exceptionId 55{ 56 [[self _bridgeForRecord].redBox showErrorMessage:message withStack:stack]; 57} 58 59- (void)handleFatalJSExceptionWithMessage:(nullable NSString *)message 60 stack:(nullable NSArray<NSDictionary<NSString *, id> *> *)stack 61 exceptionId:(NSNumber *)exceptionId 62{ 63 [[self _bridgeForRecord].redBox showErrorMessage:message withStack:stack]; 64 65 NSString *description = [@"Unhandled JS Exception: " stringByAppendingString:message]; 66 NSDictionary *errorInfo = @{ NSLocalizedDescriptionKey: description, RCTJSStackTraceKey: stack }; 67 NSError *error = [NSError errorWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; 68 69 [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager setError:error forExperienceId:_appRecord.experienceId]; 70 71 if ([self _isProdHome]) { 72 [self _recordCrashlyticsExceptionFromMessage:message stack:stack]; 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- (void)_recordCrashlyticsExceptionFromMessage:(NSString *)message stack:(NSArray *)stack 100{ 101 NSError *error; 102 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^([a-z$_][a-z\\d$_]*):\\s*" 103 options:NSRegularExpressionCaseInsensitive 104 error:&error]; 105 if (!regex) { 106 DDLogError(@"Error creating regular expression: %@", error.localizedDescription); 107 return; 108 } 109 110 NSString *errorName; 111 NSString *errorReason; 112 NSTextCheckingResult *match = [regex firstMatchInString:message options:0 range:NSMakeRange(0, message.length)]; 113 if (match) { 114 errorName = [message substringWithRange:[match rangeAtIndex:1]]; 115 errorName = [message substringFromIndex:match.range.location + match.range.length]; 116 } else { 117 errorName = @"UnknownError"; 118 errorReason = message; 119 } 120 121 NSMutableArray *frameArray = [NSMutableArray arrayWithCapacity:stack.count]; 122 for (NSDictionary *frameInfo in stack) { 123 CLSStackFrame *frame = [CLSStackFrame stackFrame]; 124 if ([frameInfo[@"file"] isKindOfClass:[NSString class]]) { 125 frame.fileName = frameInfo[@"file"]; 126 } 127 if ([frameInfo[@"methodName"] isKindOfClass:[NSString class]]) { 128 frame.symbol = frameInfo[@"methodName"]; 129 } 130 if ([frameInfo[@"lineNumber"] isKindOfClass:[NSNumber class]]) { 131 frame.lineNumber = (uint32_t)MAX([frameInfo[@"lineNumber"] integerValue], 0); 132 } 133 [frameArray addObject:frame]; 134 } 135 136 [[Crashlytics sharedInstance] recordCustomExceptionName:errorName reason:errorReason frameArray:frameArray]; 137} 138 139@end 140 141NS_ASSUME_NONNULL_END 142