1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import "ABI47_0_0RCTExceptionsManager.h"
9
10#import <ABI47_0_0FBReactNativeSpec/ABI47_0_0FBReactNativeSpec.h>
11#import <ABI47_0_0React/ABI47_0_0RCTConvert.h>
12#import <ABI47_0_0React/ABI47_0_0RCTDefines.h>
13#import <ABI47_0_0React/ABI47_0_0RCTLog.h>
14#import <ABI47_0_0React/ABI47_0_0RCTRedBox.h>
15#import <ABI47_0_0React/ABI47_0_0RCTReloadCommand.h>
16#import <ABI47_0_0React/ABI47_0_0RCTRootView.h>
17
18#import "ABI47_0_0CoreModulesPlugins.h"
19
20@interface ABI47_0_0RCTExceptionsManager () <ABI47_0_0NativeExceptionsManagerSpec>
21
22@end
23
24@implementation ABI47_0_0RCTExceptionsManager
25
26@synthesize moduleRegistry = _moduleRegistry;
27
28ABI47_0_0RCT_EXPORT_MODULE()
29
30- (instancetype)initWithDelegate:(id<ABI47_0_0RCTExceptionsManagerDelegate>)delegate
31{
32  if ((self = [self init])) {
33    _delegate = delegate;
34  }
35  return self;
36}
37
38- (void)reportSoft:(NSString *)message
39              stack:(NSArray<NSDictionary *> *)stack
40        exceptionId:(double)exceptionId
41    extraDataAsJSON:(nullable NSString *)extraDataAsJSON
42{
43  ABI47_0_0RCTRedBox *redbox = [_moduleRegistry moduleForName:"RedBox"];
44  [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId];
45
46  if (_delegate) {
47    [_delegate handleSoftJSExceptionWithMessage:message
48                                          stack:stack
49                                    exceptionId:[NSNumber numberWithDouble:exceptionId]
50                                extraDataAsJSON:extraDataAsJSON];
51  }
52}
53
54- (void)reportFatal:(NSString *)message
55              stack:(NSArray<NSDictionary *> *)stack
56        exceptionId:(double)exceptionId
57    extraDataAsJSON:(nullable NSString *)extraDataAsJSON
58{
59  ABI47_0_0RCTRedBox *redbox = [_moduleRegistry moduleForName:"RedBox"];
60  [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId];
61
62  if (_delegate) {
63    [_delegate handleFatalJSExceptionWithMessage:message
64                                           stack:stack
65                                     exceptionId:[NSNumber numberWithDouble:exceptionId]
66                                 extraDataAsJSON:extraDataAsJSON];
67  }
68
69  static NSUInteger reloadRetries = 0;
70  if (!ABI47_0_0RCT_DEBUG && reloadRetries < _maxReloadAttempts) {
71    reloadRetries++;
72    ABI47_0_0RCTTriggerReloadCommandListeners(@"JS Crash Reload");
73  } else if (!ABI47_0_0RCT_DEV) {
74    NSString *description = [@"Unhandled JS Exception: " stringByAppendingString:message];
75    NSDictionary *errorInfo =
76        @{NSLocalizedDescriptionKey : description, ABI47_0_0RCTJSStackTraceKey : stack, ABI47_0_0RCTJSExtraDataKey : extraDataAsJSON};
77    ABI47_0_0RCTFatal([NSError errorWithDomain:ABI47_0_0RCTErrorDomain code:0 userInfo:errorInfo]);
78  }
79}
80
81ABI47_0_0RCT_EXPORT_METHOD(reportSoftException
82                  : (NSString *)message stack
83                  : (NSArray<NSDictionary *> *)stack exceptionId
84                  : (double)exceptionId)
85{
86  [self reportSoft:message stack:stack exceptionId:exceptionId extraDataAsJSON:nil];
87}
88
89ABI47_0_0RCT_EXPORT_METHOD(reportFatalException
90                  : (NSString *)message stack
91                  : (NSArray<NSDictionary *> *)stack exceptionId
92                  : (double)exceptionId)
93{
94  [self reportFatal:message stack:stack exceptionId:exceptionId extraDataAsJSON:nil];
95}
96
97ABI47_0_0RCT_EXPORT_METHOD(updateExceptionMessage
98                  : (NSString *)message stack
99                  : (NSArray<NSDictionary *> *)stack exceptionId
100                  : (double)exceptionId)
101{
102  ABI47_0_0RCTRedBox *redbox = [_moduleRegistry moduleForName:"RedBox"];
103  [redbox updateErrorMessage:message withStack:stack errorCookie:(int)exceptionId];
104
105  if (_delegate && [_delegate respondsToSelector:@selector(updateJSExceptionWithMessage:stack:exceptionId:)]) {
106    [_delegate updateJSExceptionWithMessage:message stack:stack exceptionId:[NSNumber numberWithDouble:exceptionId]];
107  }
108}
109
110// Deprecated.  Use reportFatalException directly instead.
111ABI47_0_0RCT_EXPORT_METHOD(reportUnhandledException : (NSString *)message stack : (NSArray<NSDictionary *> *)stack)
112{
113  [self reportFatalException:message stack:stack exceptionId:-1];
114}
115
116ABI47_0_0RCT_EXPORT_METHOD(dismissRedbox) {}
117
118ABI47_0_0RCT_EXPORT_METHOD(reportException : (ABI47_0_0JS::NativeExceptionsManager::ExceptionData &)data)
119{
120  NSString *message = data.message();
121  double exceptionId = data.id_();
122
123  // Reserialize data.stack() into an array of untyped dictionaries.
124  // TODO: (moti) T53588496 Replace `(NSArray<NSDictionary *> *)stack` in
125  // reportFatalException etc with a typed interface.
126  NSMutableArray<NSDictionary *> *stackArray = [NSMutableArray<NSDictionary *> new];
127  for (auto frame : data.stack()) {
128    NSMutableDictionary *frameDict = [NSMutableDictionary new];
129    if (frame.column().has_value()) {
130      frameDict[@"column"] = @(frame.column().value());
131    }
132    frameDict[@"file"] = frame.file();
133    if (frame.lineNumber().has_value()) {
134      frameDict[@"lineNumber"] = @(frame.lineNumber().value());
135    }
136    frameDict[@"methodName"] = frame.methodName();
137    if (frame.collapse().has_value()) {
138      frameDict[@"collapse"] = @(frame.collapse().value());
139    }
140    [stackArray addObject:frameDict];
141  }
142
143  NSDictionary *extraData = (NSDictionary *)data.extraData();
144  NSString *extraDataAsJSON = ABI47_0_0RCTJSONStringify(extraData, NULL);
145
146  if (data.isFatal()) {
147    [self reportFatal:message stack:stackArray exceptionId:exceptionId extraDataAsJSON:extraDataAsJSON];
148  } else {
149    [self reportSoft:message stack:stackArray exceptionId:exceptionId extraDataAsJSON:extraDataAsJSON];
150  }
151}
152
153- (void)reportJsException:(NSString *)errorStr
154{
155  NSData *jsonData = [errorStr dataUsingEncoding:NSUTF8StringEncoding];
156  NSError *jsonError;
157  NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData
158                                                       options:NSJSONWritingPrettyPrinted
159                                                         error:&jsonError];
160
161  NSString *message = [dict objectForKey:@"message"];
162  double exceptionId = [[dict objectForKey:@"id"] doubleValue];
163  NSArray *stack = [dict objectForKey:@"stack"];
164  BOOL isFatal = [[dict objectForKey:@"isFatal"] boolValue];
165
166  if (isFatal) {
167    [self reportFatalException:message stack:stack exceptionId:exceptionId];
168  } else {
169    [self reportSoftException:message stack:stack exceptionId:exceptionId];
170  }
171}
172
173- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule:
174    (const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params
175{
176  return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeExceptionsManagerSpecJSI>(params);
177}
178
179@end
180
181Class ABI47_0_0RCTExceptionsManagerCls(void)
182{
183  return ABI47_0_0RCTExceptionsManager.class;
184}
185