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_0RCTRedBox.h"
9
10#import <ABI47_0_0FBReactNativeSpec/ABI47_0_0FBReactNativeSpec.h>
11#import <ABI47_0_0React/ABI47_0_0RCTBridge.h>
12#import <ABI47_0_0React/ABI47_0_0RCTConvert.h>
13#import <ABI47_0_0React/ABI47_0_0RCTDefines.h>
14#import <ABI47_0_0React/ABI47_0_0RCTErrorInfo.h>
15#import <ABI47_0_0React/ABI47_0_0RCTEventDispatcherProtocol.h>
16#import <ABI47_0_0React/ABI47_0_0RCTJSStackFrame.h>
17#import <ABI47_0_0React/ABI47_0_0RCTRedBoxExtraDataViewController.h>
18#import <ABI47_0_0React/ABI47_0_0RCTRedBoxSetEnabled.h>
19#import <ABI47_0_0React/ABI47_0_0RCTReloadCommand.h>
20#import <ABI47_0_0React/ABI47_0_0RCTUtils.h>
21
22#import <objc/runtime.h>
23
24#import "ABI47_0_0CoreModulesPlugins.h"
25
26#if ABI47_0_0RCT_DEV_MENU
27
28@class ABI47_0_0RCTRedBoxWindow;
29
30@interface UIButton (ABI47_0_0RCTRedBox)
31
32@property (nonatomic) ABI47_0_0RCTRedBoxButtonPressHandler rct_handler;
33
34- (void)rct_addBlock:(ABI47_0_0RCTRedBoxButtonPressHandler)handler forControlEvents:(UIControlEvents)controlEvents;
35
36@end
37
38@implementation UIButton (ABI47_0_0RCTRedBox)
39
40- (ABI47_0_0RCTRedBoxButtonPressHandler)rct_handler
41{
42  return objc_getAssociatedObject(self, @selector(rct_handler));
43}
44
45- (void)setRct_handler:(ABI47_0_0RCTRedBoxButtonPressHandler)rct_handler
46{
47  objc_setAssociatedObject(self, @selector(rct_handler), rct_handler, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
48}
49
50- (void)rct_callBlock
51{
52  if (self.rct_handler) {
53    self.rct_handler();
54  }
55}
56
57- (void)rct_addBlock:(ABI47_0_0RCTRedBoxButtonPressHandler)handler forControlEvents:(UIControlEvents)controlEvents
58{
59  self.rct_handler = handler;
60  [self addTarget:self action:@selector(rct_callBlock) forControlEvents:controlEvents];
61}
62
63@end
64
65@protocol ABI47_0_0RCTRedBoxWindowActionDelegate <NSObject>
66
67- (void)redBoxWindow:(ABI47_0_0RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(ABI47_0_0RCTJSStackFrame *)stackFrame;
68- (void)reloadFromRedBoxWindow:(ABI47_0_0RCTRedBoxWindow *)redBoxWindow;
69- (void)loadExtraDataViewController;
70
71@end
72
73@interface ABI47_0_0RCTRedBoxWindow : NSObject <UITableViewDelegate, UITableViewDataSource>
74@property (nonatomic, strong) UIViewController *rootViewController;
75@property (nonatomic, weak) id<ABI47_0_0RCTRedBoxWindowActionDelegate> actionDelegate;
76@end
77
78@implementation ABI47_0_0RCTRedBoxWindow {
79  UITableView *_stackTraceTableView;
80  NSString *_lastErrorMessage;
81  NSArray<ABI47_0_0RCTJSStackFrame *> *_lastStackTrace;
82  int _lastErrorCookie;
83}
84
85- (instancetype)initWithFrame:(CGRect)frame
86           customButtonTitles:(NSArray<NSString *> *)customButtonTitles
87         customButtonHandlers:(NSArray<ABI47_0_0RCTRedBoxButtonPressHandler> *)customButtonHandlers
88{
89  if (self = [super init]) {
90    _lastErrorCookie = -1;
91
92    _rootViewController = [UIViewController new];
93    UIView *rootView = _rootViewController.view;
94    rootView.frame = frame;
95    rootView.backgroundColor = [UIColor blackColor];
96
97    const CGFloat buttonHeight = 60;
98
99    CGRect detailsFrame = rootView.bounds;
100    detailsFrame.size.height -= buttonHeight + (double)[self bottomSafeViewHeight];
101
102    _stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain];
103    _stackTraceTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
104    _stackTraceTableView.delegate = self;
105    _stackTraceTableView.dataSource = self;
106    _stackTraceTableView.backgroundColor = [UIColor clearColor];
107    _stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3];
108    _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
109    _stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
110    [rootView addSubview:_stackTraceTableView];
111
112#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST
113    NSString *reloadText = @"Reload\n(\u2318R)";
114    NSString *dismissText = @"Dismiss\n(ESC)";
115    NSString *copyText = @"Copy\n(\u2325\u2318C)";
116    NSString *extraText = @"Extra Info\n(\u2318E)";
117#else
118    NSString *reloadText = @"Reload JS";
119    NSString *dismissText = @"Dismiss";
120    NSString *copyText = @"Copy";
121    NSString *extraText = @"Extra Info";
122#endif
123
124    UIButton *dismissButton = [self redBoxButton:dismissText
125                         accessibilityIdentifier:@"redbox-dismiss"
126                                        selector:@selector(dismiss)
127                                           block:nil];
128    UIButton *reloadButton = [self redBoxButton:reloadText
129                        accessibilityIdentifier:@"redbox-reload"
130                                       selector:@selector(reload)
131                                          block:nil];
132    UIButton *copyButton = [self redBoxButton:copyText
133                      accessibilityIdentifier:@"redbox-copy"
134                                     selector:@selector(copyStack)
135                                        block:nil];
136    UIButton *extraButton = [self redBoxButton:extraText
137                       accessibilityIdentifier:@"redbox-extra"
138                                      selector:@selector(showExtraDataViewController)
139                                         block:nil];
140
141    CGFloat buttonWidth = frame.size.width / (CGFloat)(4 + [customButtonTitles count]);
142    CGFloat bottomButtonHeight = frame.size.height - buttonHeight - (CGFloat)[self bottomSafeViewHeight];
143    dismissButton.frame = CGRectMake(0, bottomButtonHeight, buttonWidth, buttonHeight);
144    reloadButton.frame = CGRectMake(buttonWidth, bottomButtonHeight, buttonWidth, buttonHeight);
145    copyButton.frame = CGRectMake(buttonWidth * 2, bottomButtonHeight, buttonWidth, buttonHeight);
146    extraButton.frame = CGRectMake(buttonWidth * 3, bottomButtonHeight, buttonWidth, buttonHeight);
147
148    [rootView addSubview:dismissButton];
149    [rootView addSubview:reloadButton];
150    [rootView addSubview:copyButton];
151    [rootView addSubview:extraButton];
152
153    for (NSUInteger i = 0; i < [customButtonTitles count]; i++) {
154      UIButton *button = [self redBoxButton:customButtonTitles[i]
155                    accessibilityIdentifier:@""
156                                   selector:nil
157                                      block:customButtonHandlers[i]];
158      button.frame = CGRectMake(buttonWidth * (double)(4 + i), bottomButtonHeight, buttonWidth, buttonHeight);
159      [rootView addSubview:button];
160    }
161
162    UIView *topBorder =
163        [[UIView alloc] initWithFrame:CGRectMake(0, bottomButtonHeight + 1, rootView.frame.size.width, 1)];
164    topBorder.backgroundColor = [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0];
165
166    [rootView addSubview:topBorder];
167
168    UIView *bottomSafeView = [UIView new];
169    bottomSafeView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1];
170    bottomSafeView.frame = CGRectMake(
171        0,
172        frame.size.height - (CGFloat)[self bottomSafeViewHeight],
173        frame.size.width,
174        (CGFloat)[self bottomSafeViewHeight]);
175
176    [rootView addSubview:bottomSafeView];
177  }
178  return self;
179}
180
181- (UIButton *)redBoxButton:(NSString *)title
182    accessibilityIdentifier:(NSString *)accessibilityIdentifier
183                   selector:(SEL)selector
184                      block:(ABI47_0_0RCTRedBoxButtonPressHandler)block
185{
186  UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
187  button.autoresizingMask =
188      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin;
189  button.accessibilityIdentifier = accessibilityIdentifier;
190  button.titleLabel.font = [UIFont systemFontOfSize:13];
191  button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
192  button.titleLabel.textAlignment = NSTextAlignmentCenter;
193  button.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1];
194  [button setTitle:title forState:UIControlStateNormal];
195  [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
196  [button setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateHighlighted];
197  if (selector) {
198    [button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
199  } else if (block) {
200    [button rct_addBlock:block forControlEvents:UIControlEventTouchUpInside];
201  }
202  return button;
203}
204
205- (NSInteger)bottomSafeViewHeight
206{
207  return ABI47_0_0RCTSharedApplication().delegate.window.safeAreaInsets.bottom;
208}
209
210ABI47_0_0RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
211
212- (NSString *)stripAnsi:(NSString *)text
213{
214  NSError *error = nil;
215  NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\x1b\\[[0-9;]*m"
216                                                                         options:NSRegularExpressionCaseInsensitive
217                                                                           error:&error];
218  return [regex stringByReplacingMatchesInString:text options:0 range:NSMakeRange(0, [text length]) withTemplate:@""];
219}
220
221- (void)showErrorMessage:(NSString *)message
222               withStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
223                isUpdate:(BOOL)isUpdate
224             errorCookie:(int)errorCookie
225{
226  // Remove ANSI color codes from the message
227  NSString *messageWithoutAnsi = [self stripAnsi:message];
228
229  BOOL isRootViewControllerPresented = self.rootViewController.presentingViewController != nil;
230  // Show if this is a new message, or if we're updating the previous message
231  BOOL isNew = !isRootViewControllerPresented && !isUpdate;
232  BOOL isUpdateForSameMessage = !isNew &&
233      (isRootViewControllerPresented && isUpdate &&
234       ((errorCookie == -1 && [_lastErrorMessage isEqualToString:messageWithoutAnsi]) ||
235        (errorCookie == _lastErrorCookie)));
236  if (isNew || isUpdateForSameMessage) {
237    _lastStackTrace = stack;
238    // message is displayed using UILabel, which is unable to render text of
239    // unlimited length, so we truncate it
240    _lastErrorMessage = [messageWithoutAnsi substringToIndex:MIN((NSUInteger)10000, messageWithoutAnsi.length)];
241    _lastErrorCookie = errorCookie;
242
243    [_stackTraceTableView reloadData];
244
245    if (!isRootViewControllerPresented) {
246      [_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
247                                  atScrollPosition:UITableViewScrollPositionTop
248                                          animated:NO];
249      [ABI47_0_0RCTKeyWindow().rootViewController presentViewController:self.rootViewController animated:YES completion:nil];
250    }
251  }
252}
253
254- (void)dismiss
255{
256  [self.rootViewController dismissViewControllerAnimated:YES completion:nil];
257}
258
259- (void)reload
260{
261  [_actionDelegate reloadFromRedBoxWindow:self];
262}
263
264- (void)showExtraDataViewController
265{
266  [_actionDelegate loadExtraDataViewController];
267}
268
269- (void)copyStack
270{
271  NSMutableString *fullStackTrace;
272
273  if (_lastErrorMessage != nil) {
274    fullStackTrace = [_lastErrorMessage mutableCopy];
275    [fullStackTrace appendString:@"\n\n"];
276  } else {
277    fullStackTrace = [NSMutableString string];
278  }
279
280  for (ABI47_0_0RCTJSStackFrame *stackFrame in _lastStackTrace) {
281    [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]];
282    if (stackFrame.file) {
283      [fullStackTrace appendFormat:@"    %@\n", [self formatFrameSource:stackFrame]];
284    }
285  }
286  UIPasteboard *pb = [UIPasteboard generalPasteboard];
287  [pb setString:fullStackTrace];
288}
289
290- (NSString *)formatFrameSource:(ABI47_0_0RCTJSStackFrame *)stackFrame
291{
292  NSString *fileName = ABI47_0_0RCTNilIfNull(stackFrame.file) ? [stackFrame.file lastPathComponent] : @"<unknown file>";
293  NSString *lineInfo = [NSString stringWithFormat:@"%@:%lld", fileName, (long long)stackFrame.lineNumber];
294
295  if (stackFrame.column != 0) {
296    lineInfo = [lineInfo stringByAppendingFormat:@":%lld", (long long)stackFrame.column];
297  }
298  return lineInfo;
299}
300
301#pragma mark - TableView
302
303- (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView
304{
305  return 2;
306}
307
308- (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section
309{
310  return section == 0 ? 1 : _lastStackTrace.count;
311}
312
313- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
314{
315  if (indexPath.section == 0) {
316    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"];
317    return [self reuseCell:cell forErrorMessage:_lastErrorMessage];
318  }
319  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
320  NSUInteger index = indexPath.row;
321  ABI47_0_0RCTJSStackFrame *stackFrame = _lastStackTrace[index];
322  return [self reuseCell:cell forStackFrame:stackFrame];
323}
324
325- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message
326{
327  if (!cell) {
328    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"msg-cell"];
329    cell.textLabel.accessibilityIdentifier = @"redbox-error";
330    cell.textLabel.textColor = [UIColor whiteColor];
331    cell.textLabel.font = [UIFont boldSystemFontOfSize:16];
332    cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
333    cell.textLabel.numberOfLines = 0;
334    cell.detailTextLabel.textColor = [UIColor whiteColor];
335    cell.backgroundColor = [UIColor colorWithRed:0.82 green:0.10 blue:0.15 alpha:1.0];
336    cell.selectionStyle = UITableViewCellSelectionStyleNone;
337  }
338
339  cell.textLabel.text = message;
340
341  return cell;
342}
343
344- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(ABI47_0_0RCTJSStackFrame *)stackFrame
345{
346  if (!cell) {
347    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
348    cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14];
349    cell.textLabel.lineBreakMode = NSLineBreakByCharWrapping;
350    cell.textLabel.numberOfLines = 2;
351    cell.detailTextLabel.textColor = [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0];
352    cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11];
353    cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
354    cell.backgroundColor = [UIColor clearColor];
355    cell.selectedBackgroundView = [UIView new];
356    cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
357  }
358
359  cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)";
360  if (stackFrame.file) {
361    cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
362  } else {
363    cell.detailTextLabel.text = @"";
364  }
365  cell.textLabel.textColor = stackFrame.collapse ? [UIColor lightGrayColor] : [UIColor whiteColor];
366  cell.detailTextLabel.textColor = stackFrame.collapse ? [UIColor colorWithRed:0.50 green:0.50 blue:0.50 alpha:1.0]
367                                                       : [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0];
368  return cell;
369}
370
371- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
372{
373  if (indexPath.section == 0) {
374    NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
375    paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
376
377    NSDictionary *attributes =
378        @{NSFontAttributeName : [UIFont boldSystemFontOfSize:16], NSParagraphStyleAttributeName : paragraphStyle};
379    CGRect boundingRect =
380        [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX)
381                                        options:NSStringDrawingUsesLineFragmentOrigin
382                                     attributes:attributes
383                                        context:nil];
384    return ceil(boundingRect.size.height) + 40;
385  } else {
386    return 50;
387  }
388}
389
390- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
391{
392  if (indexPath.section == 1) {
393    NSUInteger row = indexPath.row;
394    ABI47_0_0RCTJSStackFrame *stackFrame = _lastStackTrace[row];
395    [_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame];
396  }
397  [tableView deselectRowAtIndexPath:indexPath animated:YES];
398}
399
400#pragma mark - Key commands
401
402- (NSArray<UIKeyCommand *> *)keyCommands
403{
404  // NOTE: We could use ABI47_0_0RCTKeyCommands for this, but since
405  // we control this window, we can use the standard, non-hacky
406  // mechanism instead
407
408  return @[
409    // Dismiss red box
410    [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(dismiss)],
411
412    // Reload
413    [UIKeyCommand keyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:@selector(reload)],
414
415    // Copy = Cmd-Option C since Cmd-C in the simulator copies the pasteboard from
416    // the simulator to the desktop pasteboard.
417    [UIKeyCommand keyCommandWithInput:@"c"
418                        modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate
419                               action:@selector(copyStack)],
420
421    // Extra data
422    [UIKeyCommand keyCommandWithInput:@"e"
423                        modifierFlags:UIKeyModifierCommand
424                               action:@selector(showExtraDataViewController)]
425  ];
426}
427
428- (BOOL)canBecomeFirstResponder
429{
430  return YES;
431}
432
433@end
434
435@interface ABI47_0_0RCTRedBox () <
436    ABI47_0_0RCTInvalidating,
437    ABI47_0_0RCTRedBoxWindowActionDelegate,
438    ABI47_0_0RCTRedBoxExtraDataActionDelegate,
439    ABI47_0_0NativeRedBoxSpec>
440@end
441
442@implementation ABI47_0_0RCTRedBox {
443  ABI47_0_0RCTRedBoxWindow *_window;
444  NSMutableArray<id<ABI47_0_0RCTErrorCustomizer>> *_errorCustomizers;
445  ABI47_0_0RCTRedBoxExtraDataViewController *_extraDataViewController;
446  NSMutableArray<NSString *> *_customButtonTitles;
447  NSMutableArray<ABI47_0_0RCTRedBoxButtonPressHandler> *_customButtonHandlers;
448}
449
450@synthesize bridge = _bridge;
451@synthesize moduleRegistry = _moduleRegistry;
452@synthesize bundleManager = _bundleManager;
453
454ABI47_0_0RCT_EXPORT_MODULE()
455
456- (void)registerErrorCustomizer:(id<ABI47_0_0RCTErrorCustomizer>)errorCustomizer
457{
458  dispatch_async(dispatch_get_main_queue(), ^{
459    if (!self->_errorCustomizers) {
460      self->_errorCustomizers = [NSMutableArray array];
461    }
462    if (![self->_errorCustomizers containsObject:errorCustomizer]) {
463      [self->_errorCustomizers addObject:errorCustomizer];
464    }
465  });
466}
467
468// WARNING: Should only be called from the main thread/dispatch queue.
469- (ABI47_0_0RCTErrorInfo *)_customizeError:(ABI47_0_0RCTErrorInfo *)error
470{
471  ABI47_0_0RCTAssertMainQueue();
472  if (!self->_errorCustomizers) {
473    return error;
474  }
475  for (id<ABI47_0_0RCTErrorCustomizer> customizer in self->_errorCustomizers) {
476    ABI47_0_0RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error];
477    if (newInfo) {
478      error = newInfo;
479    }
480  }
481  return error;
482}
483
484- (void)showError:(NSError *)error
485{
486  [self showErrorMessage:error.localizedDescription
487             withDetails:error.localizedFailureReason
488                   stack:error.userInfo[ABI47_0_0RCTJSStackTraceKey]
489             errorCookie:-1];
490}
491
492- (void)showErrorMessage:(NSString *)message
493{
494  [self showErrorMessage:message withParsedStack:nil isUpdate:NO errorCookie:-1];
495}
496
497- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
498{
499  [self showErrorMessage:message withDetails:details stack:nil errorCookie:-1];
500}
501
502- (void)showErrorMessage:(NSString *)message
503             withDetails:(NSString *)details
504                   stack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
505             errorCookie:(int)errorCookie
506{
507  NSString *combinedMessage = message;
508  if (details) {
509    combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
510  }
511  [self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO errorCookie:errorCookie];
512}
513
514- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack
515{
516  [self showErrorMessage:message withRawStack:rawStack errorCookie:-1];
517}
518
519- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack errorCookie:(int)errorCookie
520{
521  NSArray<ABI47_0_0RCTJSStackFrame *> *stack = [ABI47_0_0RCTJSStackFrame stackFramesWithLines:rawStack];
522  [self showErrorMessage:message withParsedStack:stack isUpdate:NO errorCookie:errorCookie];
523}
524
525- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
526{
527  [self showErrorMessage:message withStack:stack errorCookie:-1];
528}
529
530- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
531{
532  [self updateErrorMessage:message withStack:stack errorCookie:-1];
533}
534
535- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack errorCookie:(int)errorCookie
536{
537  [self showErrorMessage:message
538         withParsedStack:[ABI47_0_0RCTJSStackFrame stackFramesWithDictionaries:stack]
539                isUpdate:NO
540             errorCookie:errorCookie];
541}
542
543- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack errorCookie:(int)errorCookie
544{
545  [self showErrorMessage:message
546         withParsedStack:[ABI47_0_0RCTJSStackFrame stackFramesWithDictionaries:stack]
547                isUpdate:YES
548             errorCookie:errorCookie];
549}
550
551- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
552{
553  [self showErrorMessage:message withParsedStack:stack errorCookie:-1];
554}
555
556- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
557{
558  [self updateErrorMessage:message withParsedStack:stack errorCookie:-1];
559}
560
561- (void)showErrorMessage:(NSString *)message
562         withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
563             errorCookie:(int)errorCookie
564{
565  [self showErrorMessage:message withParsedStack:stack isUpdate:NO errorCookie:errorCookie];
566}
567
568- (void)updateErrorMessage:(NSString *)message
569           withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
570               errorCookie:(int)errorCookie
571{
572  [self showErrorMessage:message withParsedStack:stack isUpdate:YES errorCookie:errorCookie];
573}
574
575- (void)showErrorMessage:(NSString *)message
576         withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
577                isUpdate:(BOOL)isUpdate
578             errorCookie:(int)errorCookie
579{
580  dispatch_async(dispatch_get_main_queue(), ^{
581    if (self->_extraDataViewController == nil) {
582      self->_extraDataViewController = [ABI47_0_0RCTRedBoxExtraDataViewController new];
583      self->_extraDataViewController.actionDelegate = self;
584    }
585
586#pragma clang diagnostic push
587#pragma clang diagnostic ignored "-Wdeprecated-declarations"
588    [[self->_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"collectRedBoxExtraData"
589                                                                                body:nil];
590#pragma clang diagnostic pop
591
592    if (!self->_window) {
593      self->_window = [[ABI47_0_0RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds
594                                          customButtonTitles:self->_customButtonTitles
595                                        customButtonHandlers:self->_customButtonHandlers];
596      self->_window.actionDelegate = self;
597    }
598
599    ABI47_0_0RCTErrorInfo *errorInfo = [[ABI47_0_0RCTErrorInfo alloc] initWithErrorMessage:message stack:stack];
600    errorInfo = [self _customizeError:errorInfo];
601    [self->_window showErrorMessage:errorInfo.errorMessage
602                          withStack:errorInfo.stack
603                           isUpdate:isUpdate
604                        errorCookie:errorCookie];
605  });
606}
607
608- (void)loadExtraDataViewController
609{
610  dispatch_async(dispatch_get_main_queue(), ^{
611    // Make sure the CMD+E shortcut doesn't call this twice
612    if (self->_extraDataViewController != nil && ![self->_window.rootViewController presentedViewController]) {
613      [self->_window.rootViewController presentViewController:self->_extraDataViewController
614                                                     animated:YES
615                                                   completion:nil];
616    }
617  });
618}
619
620ABI47_0_0RCT_EXPORT_METHOD(setExtraData : (NSDictionary *)extraData forIdentifier : (NSString *)identifier)
621{
622  [_extraDataViewController addExtraData:extraData forIdentifier:identifier];
623}
624
625ABI47_0_0RCT_EXPORT_METHOD(dismiss)
626{
627  dispatch_async(dispatch_get_main_queue(), ^{
628    [self->_window dismiss];
629  });
630}
631
632- (void)invalidate
633{
634  [self dismiss];
635}
636
637- (void)redBoxWindow:(__unused ABI47_0_0RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(ABI47_0_0RCTJSStackFrame *)stackFrame
638{
639  NSURL *const bundleURL = _overrideBundleURL ?: _bundleManager.bundleURL;
640  if (![bundleURL.scheme hasPrefix:@"http"]) {
641    ABI47_0_0RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager.");
642    return;
643  }
644
645  NSData *stackFrameJSON = [ABI47_0_0RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding];
646  NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
647  NSMutableURLRequest *request = [NSMutableURLRequest new];
648  request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL];
649  request.HTTPMethod = @"POST";
650  request.HTTPBody = stackFrameJSON;
651  [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
652  [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
653
654  [[[NSURLSession sharedSession] dataTaskWithRequest:request] resume];
655}
656
657- (void)reload
658{
659  // Window is not used and can be nil
660  [self reloadFromRedBoxWindow:nil];
661}
662
663- (void)reloadFromRedBoxWindow:(__unused ABI47_0_0RCTRedBoxWindow *)redBoxWindow
664{
665  if (_overrideReloadAction) {
666    _overrideReloadAction();
667  } else {
668    ABI47_0_0RCTTriggerReloadCommandListeners(@"Redbox");
669  }
670  [self dismiss];
671}
672
673- (void)addCustomButton:(NSString *)title onPressHandler:(ABI47_0_0RCTRedBoxButtonPressHandler)handler
674{
675  if (!_customButtonTitles) {
676    _customButtonTitles = [NSMutableArray new];
677    _customButtonHandlers = [NSMutableArray new];
678  }
679
680  [_customButtonTitles addObject:title];
681  [_customButtonHandlers addObject:handler];
682}
683
684- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule:
685    (const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params
686{
687  return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeRedBoxSpecJSI>(params);
688}
689
690@end
691
692@implementation ABI47_0_0RCTBridge (ABI47_0_0RCTRedBox)
693
694- (ABI47_0_0RCTRedBox *)redBox
695{
696  return ABI47_0_0RCTRedBoxGetEnabled() ? [self moduleForClass:[ABI47_0_0RCTRedBox class]] : nil;
697}
698
699@end
700
701#else // Disabled
702
703@interface ABI47_0_0RCTRedBox () <ABI47_0_0NativeRedBoxSpec>
704@end
705
706@implementation ABI47_0_0RCTRedBox
707
708+ (NSString *)moduleName
709{
710  return nil;
711}
712- (void)registerErrorCustomizer:(id<ABI47_0_0RCTErrorCustomizer>)errorCustomizer
713{
714}
715- (void)showError:(NSError *)error
716{
717}
718- (void)showErrorMessage:(NSString *)message
719{
720}
721- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
722{
723}
724- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack
725{
726}
727- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack errorCookie:(int)errorCookie
728{
729}
730- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
731{
732}
733- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
734{
735}
736- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack errorCookie:(int)errorCookie
737{
738}
739- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack errorCookie:(int)errorCookie
740{
741}
742- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
743{
744}
745- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
746{
747}
748- (void)showErrorMessage:(NSString *)message
749         withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
750             errorCookie:(int)errorCookie
751{
752}
753- (void)updateErrorMessage:(NSString *)message
754           withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack
755               errorCookie:(int)errorCookie
756{
757}
758- (void)setExtraData:(NSDictionary *)extraData forIdentifier:(NSString *)identifier
759{
760}
761
762- (void)dismiss
763{
764}
765
766- (void)addCustomButton:(NSString *)title onPressHandler:(ABI47_0_0RCTRedBoxButtonPressHandler)handler
767{
768}
769- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule:
770    (const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params
771{
772  return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeRedBoxSpecJSI>(params);
773}
774
775@end
776
777@implementation ABI47_0_0RCTBridge (ABI47_0_0RCTRedBox)
778
779- (ABI47_0_0RCTRedBox *)redBox
780{
781  return nil;
782}
783
784@end
785
786#endif
787
788Class ABI47_0_0RCTRedBoxCls(void)
789{
790  return ABI47_0_0RCTRedBox.class;
791}
792