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