diff --git a/React/Base/RCTErrorCustomizer.h b/React/Base/RCTErrorCustomizer.h new file mode 100644 index 00000000000..23c8b24d36e --- /dev/null +++ b/React/Base/RCTErrorCustomizer.h @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class RCTErrorInfo; + +/** + * Provides an interface to customize React Native error messages and stack + * traces from exceptions. + */ +@protocol RCTErrorCustomizer + +/** + * Customizes the given error, returning the passed info argument if no + * customization is required. + */ +- (nonnull RCTErrorInfo *)customizeErrorInfo:(nonnull RCTErrorInfo *)info; +@end diff --git a/React/Base/RCTErrorInfo.h b/React/Base/RCTErrorInfo.h new file mode 100644 index 00000000000..aaa42221410 --- /dev/null +++ b/React/Base/RCTErrorInfo.h @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class RCTJSStackFrame; + +/** + * An ObjC wrapper for React Native errors. + */ +@interface RCTErrorInfo : NSObject +@property (nonatomic, copy, readonly) NSString *errorMessage; +@property (nonatomic, copy, readonly) NSArray *stack; + + +- (instancetype)initWithErrorMessage:(NSString *)errorMessage + stack:(NSArray *)stack; + +@end diff --git a/React/Base/RCTErrorInfo.m b/React/Base/RCTErrorInfo.m new file mode 100644 index 00000000000..37e055fd564 --- /dev/null +++ b/React/Base/RCTErrorInfo.m @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTErrorInfo.h" + +#import "RCTJSStackFrame.h" + +@implementation RCTErrorInfo + +- (instancetype)initWithErrorMessage:(NSString *)errorMessage + stack:(NSArray *)stack { + self = [super init]; + if (self) { + _errorMessage = [errorMessage copy]; + _stack = [stack copy]; + } + return self; +} + +@end diff --git a/React/Modules/RCTRedBox.h b/React/Modules/RCTRedBox.h index c586091a760..40f5c62d274 100644 --- a/React/Modules/RCTRedBox.h +++ b/React/Modules/RCTRedBox.h @@ -11,9 +11,11 @@ #import "RCTBridge.h" #import "RCTBridgeModule.h" +#import "RCTErrorCustomizer.h" @interface RCTRedBox : NSObject +- (void)registerErrorCustomizer:(id)errorCustomizer; - (void)showError:(NSError *)error; - (void)showErrorMessage:(NSString *)message; - (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details; diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m index 34b47b90781..400042099d1 100644 --- a/React/Modules/RCTRedBox.m +++ b/React/Modules/RCTRedBox.m @@ -12,6 +12,7 @@ #import "RCTBridge.h" #import "RCTConvert.h" #import "RCTDefines.h" +#import "RCTErrorInfo.h" #import "RCTUtils.h" #import "RCTJSStackFrame.h" @@ -314,12 +315,42 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) @implementation RCTRedBox { RCTRedBoxWindow *_window; + NSMutableArray> *_errorCustomizers; } @synthesize bridge = _bridge; RCT_EXPORT_MODULE() +- (void)registerErrorCustomizer:(id)errorCustomizer +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (!self->_errorCustomizers) { + self->_errorCustomizers = [NSMutableArray array]; + } + if (![self->_errorCustomizers containsObject:errorCustomizer]) { + [self->_errorCustomizers addObject:errorCustomizer]; + } + }); +} + +// WARNING: Should only be called from the main thread/dispatch queue. +- (RCTErrorInfo *)_customizeError:(RCTErrorInfo *)error +{ + RCTAssertMainQueue(); + + if (!self->_errorCustomizers) { + return error; + } + for (id customizer in self->_errorCustomizers) { + RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error]; + if (newInfo) { + error = newInfo; + } + } + return error; +} + - (void)showError:(NSError *)error { [self showErrorMessage:error.localizedDescription withDetails:error.localizedFailureReason]; @@ -367,7 +398,12 @@ RCT_EXPORT_MODULE() self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self->_window.actionDelegate = self; } - [self->_window showErrorMessage:message withStack:stack isUpdate:isUpdate]; + RCTErrorInfo *errorInfo = [[RCTErrorInfo alloc] initWithErrorMessage:message + stack:stack]; + errorInfo = [self _customizeError:errorInfo]; + [self->_window showErrorMessage:errorInfo.errorMessage + withStack:errorInfo.stack + isUpdate:isUpdate]; }); } @@ -422,6 +458,7 @@ RCT_EXPORT_METHOD(dismiss) @implementation RCTRedBox + (NSString *)moduleName { return nil; } +- (void)registerErrorCustomizer:(id)errorCustomizer {} - (void)showError:(NSError *)message {} - (void)showErrorMessage:(NSString *)message {} - (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details {} diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index eb93edf6c37..5c19887e0fc 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 352DCFF01D19F4C20056D623 /* RCTI18nUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DCFEF1D19F4C20056D623 /* RCTI18nUtil.m */; }; 391E86A41C623EC800009732 /* RCTTouchEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 391E86A21C623EC800009732 /* RCTTouchEvent.m */; }; 3D1E68DB1CABD13900DD7465 /* RCTDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D1E68D91CABD13900DD7465 /* RCTDisplayLink.m */; }; + 3EDCA8A51D3591E700450C31 /* RCTErrorInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */; }; 58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; }; 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; }; 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; @@ -274,6 +275,9 @@ 3D1E68D91CABD13900DD7465 /* RCTDisplayLink.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDisplayLink.m; sourceTree = ""; }; 3DB910701C74B21600838BBE /* RCTWebSocketProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxy.h; sourceTree = ""; }; 3DB910711C74B21600838BBE /* RCTWebSocketProxyDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxyDelegate.h; sourceTree = ""; }; + 3EDCA8A21D3591E700450C31 /* RCTErrorCustomizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTErrorCustomizer.h; sourceTree = ""; }; + 3EDCA8A31D3591E700450C31 /* RCTErrorInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTErrorInfo.h; sourceTree = ""; }; + 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTErrorInfo.m; sourceTree = ""; }; 58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = ""; }; 58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = ""; }; 58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = ""; }; @@ -554,6 +558,9 @@ 83CBBA491A601E3B00E9B192 /* Base */ = { isa = PBXGroup; children = ( + 3EDCA8A21D3591E700450C31 /* RCTErrorCustomizer.h */, + 3EDCA8A31D3591E700450C31 /* RCTErrorInfo.h */, + 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */, 68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */, 68EFE4EC1CF6EB3000A1DE13 /* RCTBundleURLProvider.h */, 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */, @@ -755,6 +762,7 @@ 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */, 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */, 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, + 3EDCA8A51D3591E700450C31 /* RCTErrorInfo.m in Sources */, 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */, 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */, 13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */,