@@ -102,6 +102,17 @@ static RCTNullability RCTParseNullabilityPostfix(const char **input)
102102 return RCTNullabilityUnspecified;
103103}
104104
105+ // returns YES if execution is safe to proceed (enqueue callback invocation), NO if callback has already been invoked
106+ static BOOL RCTCheckCallbackMultipleInvocations (BOOL *didInvoke) {
107+ if (*didInvoke) {
108+ RCTFatal (RCTErrorWithMessage (@" Illegal callback invocation from native module. This callback type only permits a single invocation from native code." ));
109+ return NO ;
110+ } else {
111+ *didInvoke = YES ;
112+ return YES ;
113+ }
114+ }
115+
105116SEL RCTParseMethodSignature (NSString *, NSArray <RCTMethodArgument *> **);
106117SEL RCTParseMethodSignature (NSString *methodSignature, NSArray <RCTMethodArgument *> **arguments)
107118{
@@ -205,8 +216,11 @@ - (void)processMethodSignature
205216 return NO ;
206217 }
207218
219+ __block BOOL didInvoke = NO ;
208220 RCT_BLOCK_ARGUMENT (^(NSArray *args) {
209- [bridge enqueueCallback: json args: args];
221+ if (RCTCheckCallbackMultipleInvocations (&didInvoke)) {
222+ [bridge enqueueCallback: json args: args];
223+ }
210224 });
211225 )
212226 };
@@ -302,8 +316,11 @@ - (void)processMethodSignature
302316 return NO ;
303317 }
304318
319+ __block BOOL didInvoke = NO ;
305320 RCT_BLOCK_ARGUMENT (^(NSError *error) {
306- [bridge enqueueCallback: json args: @[RCTJSErrorFromNSError (error)]];
321+ if (RCTCheckCallbackMultipleInvocations (&didInvoke)) {
322+ [bridge enqueueCallback: json args: @[RCTJSErrorFromNSError (error)]];
323+ }
307324 });
308325 )
309326 } else if ([typeName isEqualToString: @" RCTPromiseResolveBlock" ]) {
@@ -316,8 +333,11 @@ - (void)processMethodSignature
316333 return NO ;
317334 }
318335
336+ __block BOOL didInvoke = NO ;
319337 RCT_BLOCK_ARGUMENT (^(id result) {
320- [bridge enqueueCallback: json args: result ? @[result] : @[]];
338+ if (RCTCheckCallbackMultipleInvocations (&didInvoke)) {
339+ [bridge enqueueCallback: json args: result ? @[result] : @[]];
340+ }
321341 });
322342 )
323343 } else if ([typeName isEqualToString: @" RCTPromiseRejectBlock" ]) {
@@ -330,9 +350,12 @@ - (void)processMethodSignature
330350 return NO ;
331351 }
332352
353+ __block BOOL didInvoke = NO ;
333354 RCT_BLOCK_ARGUMENT (^(NSString *code, NSString *message, NSError *error) {
334- NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError (code, message, error);
335- [bridge enqueueCallback: json args: @[errorJSON]];
355+ if (RCTCheckCallbackMultipleInvocations (&didInvoke)) {
356+ NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError (code, message, error);
357+ [bridge enqueueCallback: json args: @[errorJSON]];
358+ }
336359 });
337360 )
338361 } else {
0 commit comments