Redo error handling on iOS
Reviewed By: danzimm Differential Revision: D5969343 fbshipit-source-id: 376984a6e959349260c54884c0b0b719f4c353d6
This commit is contained in:
parent
1e3a8e2ed4
commit
e87904cea5
|
@ -18,7 +18,7 @@ static NSRegularExpression *RCTJSStackFrameRegex()
|
|||
static NSRegularExpression *_regex;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSError *regexError;
|
||||
_regex = [NSRegularExpression regularExpressionWithPattern:@"^([^@]+)@(.*):(\\d+):(\\d+)$" options:0 error:®exError];
|
||||
_regex = [NSRegularExpression regularExpressionWithPattern:@"^(?:([^@]+)@)?(.*):(\\d+):(\\d+)$" options:0 error:®exError];
|
||||
if (regexError) {
|
||||
RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
|
||||
}
|
||||
|
@ -56,7 +56,9 @@ static NSRegularExpression *RCTJSStackFrameRegex()
|
|||
return nil;
|
||||
}
|
||||
|
||||
NSString *methodName = [line substringWithRange:[match rangeAtIndex:1]];
|
||||
// methodName may not be present for e.g. anonymous functions
|
||||
const NSRange methodNameRange = [match rangeAtIndex:1];
|
||||
NSString *methodName = methodNameRange.location == NSNotFound ? nil : [line substringWithRange:methodNameRange];
|
||||
NSString *file = [line substringWithRange:[match rangeAtIndex:2]];
|
||||
NSString *lineNumber = [line substringWithRange:[match rangeAtIndex:3]];
|
||||
NSString *column = [line substringWithRange:[match rangeAtIndex:4]];
|
||||
|
@ -69,7 +71,7 @@ static NSRegularExpression *RCTJSStackFrameRegex()
|
|||
|
||||
+ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict
|
||||
{
|
||||
return [[self alloc] initWithMethodName:dict[@"methodName"]
|
||||
return [[self alloc] initWithMethodName:RCTNilIfNull(dict[@"methodName"])
|
||||
file:dict[@"file"]
|
||||
lineNumber:[RCTNilIfNull(dict[@"lineNumber"]) integerValue]
|
||||
column:[RCTNilIfNull(dict[@"column"]) integerValue]];
|
||||
|
|
|
@ -17,3 +17,6 @@
|
|||
|
||||
/** Registers a weakly-held observer of the Command+R reload key command. */
|
||||
RCT_EXTERN void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener);
|
||||
|
||||
/** Triggers a reload for all current listeners. You shouldn't need to use this directly in most cases. */
|
||||
RCT_EXTERN void RCTTriggerReloadCommandListeners(void);
|
||||
|
|
|
@ -9,11 +9,15 @@
|
|||
|
||||
#import "RCTReloadCommand.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
|
||||
/** main queue only */
|
||||
static NSHashTable<id<RCTReloadListener>> *listeners;
|
||||
|
||||
void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
|
||||
{
|
||||
static NSHashTable<id<RCTReloadListener>> *listeners;
|
||||
RCTAssertMainQueue(); // because registerKeyCommandWithInput: must be called on the main thread
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
listeners = [NSHashTable weakObjectsHashTable];
|
||||
|
@ -21,17 +25,19 @@ void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
|
|||
modifierFlags:UIKeyModifierCommand
|
||||
action:
|
||||
^(__unused UIKeyCommand *command) {
|
||||
NSArray<id<RCTReloadListener>> *copiedListeners;
|
||||
@synchronized (listeners) { // avoid mutation-while-enumerating
|
||||
copiedListeners = [listeners allObjects];
|
||||
}
|
||||
for (id<RCTReloadListener> l in copiedListeners) {
|
||||
[l didReceiveReloadCommand];
|
||||
}
|
||||
RCTTriggerReloadCommandListeners();
|
||||
}];
|
||||
});
|
||||
[listeners addObject:listener];
|
||||
}
|
||||
|
||||
@synchronized (listeners) {
|
||||
[listeners addObject:listener];
|
||||
void RCTTriggerReloadCommandListeners(void)
|
||||
{
|
||||
RCTAssertMainQueue();
|
||||
// Copy to protect against mutation-during-enumeration.
|
||||
// If listeners hasn't been initialized yet we get nil, which works just fine.
|
||||
NSArray<id<RCTReloadListener>> *copiedListeners = [listeners allObjects];
|
||||
for (id<RCTReloadListener> l in copiedListeners) {
|
||||
[l didReceiveReloadCommand];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTErrorCustomizer.h>
|
||||
|
||||
@class RCTJSStackFrame;
|
||||
|
||||
@interface RCTRedBox : NSObject <RCTBridgeModule>
|
||||
|
||||
- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer;
|
||||
|
@ -22,9 +24,17 @@
|
|||
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack;
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack;
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack;
|
||||
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack;
|
||||
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack;
|
||||
|
||||
- (void)dismiss;
|
||||
|
||||
/** Overrides bridge.bundleURL. Modify on main thread only. You shouldn't need to use this. */
|
||||
@property (nonatomic, strong) NSURL *overrideBundleURL;
|
||||
|
||||
/** Overrides the default behavior of calling [bridge reload] on reload. You shouldn't need to use this. */
|
||||
@property (nonatomic, strong) dispatch_block_t overrideReloadAction;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
|
|
|
@ -255,7 +255,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
|
||||
}
|
||||
|
||||
cell.textLabel.text = stackFrame.methodName;
|
||||
cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)";
|
||||
if (stackFrame.file) {
|
||||
cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
|
||||
} else {
|
||||
|
@ -375,7 +375,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (void)showErrorMessage:(NSString *)message
|
||||
{
|
||||
[self showErrorMessage:message withStack:nil isUpdate:NO];
|
||||
[self showErrorMessage:message withParsedStack:nil isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
|
||||
|
@ -383,36 +383,42 @@ RCT_EXPORT_MODULE()
|
|||
[self showErrorMessage:message withDetails:details stack:nil];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details stack:(NSArray<id> *)stack {
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details stack:(NSArray<RCTJSStackFrame *> *)stack {
|
||||
NSString *combinedMessage = message;
|
||||
if (details) {
|
||||
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
|
||||
}
|
||||
[self showErrorMessage:combinedMessage withStack:stack isUpdate:NO];
|
||||
[self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack
|
||||
{
|
||||
NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack];
|
||||
[self showErrorMessage:message withStack:stack isUpdate:NO];
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack isUpdate:NO];
|
||||
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack isUpdate:YES];
|
||||
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:YES];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<id> *)stack isUpdate:(BOOL)isUpdate
|
||||
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
|
||||
{
|
||||
if (![[stack firstObject] isKindOfClass:[RCTJSStackFrame class]]) {
|
||||
stack = [RCTJSStackFrame stackFramesWithDictionaries:stack];
|
||||
}
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
|
||||
{
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:YES];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack isUpdate:(BOOL)isUpdate
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!self->_window) {
|
||||
self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
|
@ -441,7 +447,8 @@ RCT_EXPORT_METHOD(dismiss)
|
|||
|
||||
- (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame
|
||||
{
|
||||
if (![_bridge.bundleURL.scheme hasPrefix:@"http"]) {
|
||||
NSURL *const bundleURL = _overrideBundleURL ?: _bridge.bundleURL;
|
||||
if (![bundleURL.scheme hasPrefix:@"http"]) {
|
||||
RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager.");
|
||||
return;
|
||||
}
|
||||
|
@ -449,7 +456,7 @@ RCT_EXPORT_METHOD(dismiss)
|
|||
NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest new];
|
||||
request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:_bridge.bundleURL];
|
||||
request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL];
|
||||
request.HTTPMethod = @"POST";
|
||||
request.HTTPBody = stackFrameJSON;
|
||||
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
|
||||
|
@ -460,7 +467,11 @@ RCT_EXPORT_METHOD(dismiss)
|
|||
|
||||
- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow
|
||||
{
|
||||
[_bridge reload];
|
||||
if (_overrideReloadAction) {
|
||||
_overrideReloadAction();
|
||||
} else {
|
||||
[_bridge reload];
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
|
@ -487,6 +498,8 @@ RCT_EXPORT_METHOD(dismiss)
|
|||
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack {}
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {}
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {}
|
||||
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack {}
|
||||
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack {}
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate {}
|
||||
- (void)dismiss {}
|
||||
|
||||
|
|
Loading…
Reference in New Issue