mirror of
https://github.com/status-im/react-native.git
synced 2025-01-16 04:24:15 +00:00
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;
|
static NSRegularExpression *_regex;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
NSError *regexError;
|
NSError *regexError;
|
||||||
_regex = [NSRegularExpression regularExpressionWithPattern:@"^([^@]+)@(.*):(\\d+):(\\d+)$" options:0 error:®exError];
|
_regex = [NSRegularExpression regularExpressionWithPattern:@"^(?:([^@]+)@)?(.*):(\\d+):(\\d+)$" options:0 error:®exError];
|
||||||
if (regexError) {
|
if (regexError) {
|
||||||
RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
|
RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
|
||||||
}
|
}
|
||||||
@ -56,7 +56,9 @@ static NSRegularExpression *RCTJSStackFrameRegex()
|
|||||||
return nil;
|
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 *file = [line substringWithRange:[match rangeAtIndex:2]];
|
||||||
NSString *lineNumber = [line substringWithRange:[match rangeAtIndex:3]];
|
NSString *lineNumber = [line substringWithRange:[match rangeAtIndex:3]];
|
||||||
NSString *column = [line substringWithRange:[match rangeAtIndex:4]];
|
NSString *column = [line substringWithRange:[match rangeAtIndex:4]];
|
||||||
@ -69,7 +71,7 @@ static NSRegularExpression *RCTJSStackFrameRegex()
|
|||||||
|
|
||||||
+ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict
|
+ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict
|
||||||
{
|
{
|
||||||
return [[self alloc] initWithMethodName:dict[@"methodName"]
|
return [[self alloc] initWithMethodName:RCTNilIfNull(dict[@"methodName"])
|
||||||
file:dict[@"file"]
|
file:dict[@"file"]
|
||||||
lineNumber:[RCTNilIfNull(dict[@"lineNumber"]) integerValue]
|
lineNumber:[RCTNilIfNull(dict[@"lineNumber"]) integerValue]
|
||||||
column:[RCTNilIfNull(dict[@"column"]) integerValue]];
|
column:[RCTNilIfNull(dict[@"column"]) integerValue]];
|
||||||
|
@ -17,3 +17,6 @@
|
|||||||
|
|
||||||
/** Registers a weakly-held observer of the Command+R reload key command. */
|
/** Registers a weakly-held observer of the Command+R reload key command. */
|
||||||
RCT_EXTERN void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener);
|
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 "RCTReloadCommand.h"
|
||||||
|
|
||||||
|
#import "RCTAssert.h"
|
||||||
#import "RCTKeyCommands.h"
|
#import "RCTKeyCommands.h"
|
||||||
|
|
||||||
|
/** main queue only */
|
||||||
|
static NSHashTable<id<RCTReloadListener>> *listeners;
|
||||||
|
|
||||||
void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
|
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;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
listeners = [NSHashTable weakObjectsHashTable];
|
listeners = [NSHashTable weakObjectsHashTable];
|
||||||
@ -21,17 +25,19 @@ void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
|
|||||||
modifierFlags:UIKeyModifierCommand
|
modifierFlags:UIKeyModifierCommand
|
||||||
action:
|
action:
|
||||||
^(__unused UIKeyCommand *command) {
|
^(__unused UIKeyCommand *command) {
|
||||||
NSArray<id<RCTReloadListener>> *copiedListeners;
|
RCTTriggerReloadCommandListeners();
|
||||||
@synchronized (listeners) { // avoid mutation-while-enumerating
|
}];
|
||||||
copiedListeners = [listeners allObjects];
|
});
|
||||||
|
[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) {
|
for (id<RCTReloadListener> l in copiedListeners) {
|
||||||
[l didReceiveReloadCommand];
|
[l didReceiveReloadCommand];
|
||||||
}
|
}
|
||||||
}];
|
|
||||||
});
|
|
||||||
|
|
||||||
@synchronized (listeners) {
|
|
||||||
[listeners addObject:listener];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
#import <React/RCTBridgeModule.h>
|
#import <React/RCTBridgeModule.h>
|
||||||
#import <React/RCTErrorCustomizer.h>
|
#import <React/RCTErrorCustomizer.h>
|
||||||
|
|
||||||
|
@class RCTJSStackFrame;
|
||||||
|
|
||||||
@interface RCTRedBox : NSObject <RCTBridgeModule>
|
@interface RCTRedBox : NSObject <RCTBridgeModule>
|
||||||
|
|
||||||
- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer;
|
- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer;
|
||||||
@ -22,9 +24,17 @@
|
|||||||
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack;
|
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack;
|
||||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack;
|
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack;
|
||||||
- (void)updateErrorMessage:(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;
|
- (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
|
@end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -255,7 +255,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||||||
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
|
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.textLabel.text = stackFrame.methodName;
|
cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)";
|
||||||
if (stackFrame.file) {
|
if (stackFrame.file) {
|
||||||
cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
|
cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
|
||||||
} else {
|
} else {
|
||||||
@ -375,7 +375,7 @@ RCT_EXPORT_MODULE()
|
|||||||
|
|
||||||
- (void)showErrorMessage:(NSString *)message
|
- (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
|
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
|
||||||
@ -383,36 +383,42 @@ RCT_EXPORT_MODULE()
|
|||||||
[self showErrorMessage:message withDetails:details stack:nil];
|
[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;
|
NSString *combinedMessage = message;
|
||||||
if (details) {
|
if (details) {
|
||||||
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, 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
|
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack
|
||||||
{
|
{
|
||||||
NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines: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]]) {
|
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
|
||||||
stack = [RCTJSStackFrame stackFramesWithDictionaries:stack];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (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(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
if (!self->_window) {
|
if (!self->_window) {
|
||||||
self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||||
@ -441,7 +447,8 @@ RCT_EXPORT_METHOD(dismiss)
|
|||||||
|
|
||||||
- (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame
|
- (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.");
|
RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -449,7 +456,7 @@ RCT_EXPORT_METHOD(dismiss)
|
|||||||
NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding];
|
NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
|
NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
|
||||||
NSMutableURLRequest *request = [NSMutableURLRequest new];
|
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.HTTPMethod = @"POST";
|
||||||
request.HTTPBody = stackFrameJSON;
|
request.HTTPBody = stackFrameJSON;
|
||||||
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
|
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
|
||||||
@ -460,7 +467,11 @@ RCT_EXPORT_METHOD(dismiss)
|
|||||||
|
|
||||||
- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow
|
- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow
|
||||||
{
|
{
|
||||||
|
if (_overrideReloadAction) {
|
||||||
|
_overrideReloadAction();
|
||||||
|
} else {
|
||||||
[_bridge reload];
|
[_bridge reload];
|
||||||
|
}
|
||||||
[self dismiss];
|
[self dismiss];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,6 +498,8 @@ RCT_EXPORT_METHOD(dismiss)
|
|||||||
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack {}
|
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack {}
|
||||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {}
|
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {}
|
||||||
- (void)updateErrorMessage:(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)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate {}
|
||||||
- (void)dismiss {}
|
- (void)dismiss {}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user