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