@react-native-firebase/crashlytics 26.4.0 → 27.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/RNFBCrashlytics.podspec +7 -1
  3. package/android/build.gradle +8 -0
  4. package/android/proguard-rules.pro +20 -0
  5. package/android/src/main/java/io/invertase/firebase/crashlytics/Constants.kt +42 -0
  6. package/android/src/main/java/io/invertase/firebase/crashlytics/{JavaScriptError.java → JavaScriptError.kt} +4 -6
  7. package/android/src/main/java/io/invertase/firebase/crashlytics/NativeRNFBTurboCrashlytics.java +11 -4
  8. package/android/src/main/java/io/invertase/firebase/crashlytics/ReactNativeFirebaseCrashlyticsFatalExit.java +117 -0
  9. package/android/src/main/java/io/invertase/firebase/crashlytics/ReactNativeFirebaseCrashlyticsInternal.java +80 -0
  10. package/android/src/main/java/io/invertase/firebase/crashlytics/ReactNativeFirebaseCrashlyticsNativeHelper.kt +45 -0
  11. package/android/src/main/java/io/invertase/firebase/crashlytics/{UnhandledPromiseRejection.java → UnhandledPromiseRejection.kt} +4 -6
  12. package/android/src/test/java/io/invertase/firebase/crashlytics/ConstantsAndJsErrorTypesTest.java +84 -0
  13. package/android/src/test/java/io/invertase/firebase/crashlytics/ReactNativeFirebaseCrashlyticsFatalExitBarrierTest.java +122 -0
  14. package/android/src/test/java/io/invertase/firebase/crashlytics/ReactNativeFirebaseCrashlyticsFatalExitTest.java +189 -0
  15. package/android/src/test/java/io/invertase/firebase/crashlytics/ReactNativeFirebaseCrashlyticsInternalTest.java +199 -0
  16. package/android/src/test/java/io/invertase/firebase/crashlytics/ReactNativeFirebaseCrashlyticsNativeHelperTest.java +108 -0
  17. package/dist/module/version.js +1 -1
  18. package/dist/typescript/lib/index.d.ts +1 -1
  19. package/dist/typescript/lib/version.d.ts +1 -1
  20. package/ios/RNFBCrashlytics/RNFBCrashlyticsDebuggerProbe.swift +121 -0
  21. package/ios/RNFBCrashlytics/RNFBCrashlyticsHelper.h +98 -0
  22. package/ios/RNFBCrashlytics/RNFBCrashlyticsHelper.m +182 -0
  23. package/ios/RNFBCrashlytics/RNFBCrashlyticsModule.mm +57 -75
  24. package/ios/RNFBCrashlytics.xcodeproj/project.pbxproj +14 -0
  25. package/ios/RNFBCrashlyticsUnitTests/HostStubs/Firebase/FIRCrashlyticsHostStub.m +273 -0
  26. package/ios/RNFBCrashlyticsUnitTests/HostStubs/Firebase/Firebase.h +56 -0
  27. package/ios/RNFBCrashlyticsUnitTests/RNFBCrashlyticsDebuggerProbeTests.swift +112 -0
  28. package/ios/RNFBCrashlyticsUnitTests/RNFBCrashlyticsFatalExitTests.m +268 -0
  29. package/ios/RNFBCrashlyticsUnitTests/RNFBCrashlyticsHelperHostStubTests.m +119 -0
  30. package/ios/RNFBCrashlyticsUnitTests/RNFBCrashlyticsUnitTests.xcodeproj/project.pbxproj +265 -0
  31. package/ios/RNFBCrashlyticsUnitTests/RNFBCrashlyticsUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBCrashlyticsUnitTests.xcscheme +69 -0
  32. package/ios_config.sh +38 -5
  33. package/lib/version.ts +1 -1
  34. package/package.json +4 -4
  35. package/android/src/main/java/io/invertase/firebase/crashlytics/Constants.java +0 -29
  36. package/android/src/main/java/io/invertase/firebase/crashlytics/ReactNativeFirebaseCrashlyticsNativeHelper.java +0 -18
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ #import <Foundation/Foundation.h>
19
+
20
+ // Plain Objective-C helper (see docs/ios-spm.mdx and
21
+ // okf-bundle/ios-spm-native-imports.md) that owns every call touching
22
+ // `FIRCrashlytics` / exception models for RNFBCrashlyticsModule, plus the
23
+ // Swift debugger probe. This keeps RNFBCrashlyticsModule.mm free of Firebase
24
+ // Crashlytics imports and `*-Swift.h`: under SPM an Objective-C++ (.mm)
25
+ // TurboModule cannot `@import` Firebase (or parse a Swift-generated header
26
+ // that uses module imports) when C++ modules are disabled (required by React
27
+ // Native's JSI headers).
28
+ @interface RNFBCrashlyticsHelper : NSObject
29
+
30
+ /** Cached `sysctl` debugger-attach probe (Swift `RNFBCrashlyticsDebuggerProbe`). */
31
+ + (BOOL)isDebuggerAttached;
32
+
33
+ + (void)checkForUnsentReportsWithCompletion:(void (^)(BOOL unsentReports))completion;
34
+
35
+ + (void)deleteUnsentReports;
36
+
37
+ + (BOOL)didCrashDuringPreviousExecution;
38
+
39
+ + (void)log:(NSString *)message;
40
+
41
+ + (void)sendUnsentReports;
42
+
43
+ + (void)setCustomValue:(NSString *)value forKey:(NSString *)key;
44
+
45
+ /** Applies each entry via `setCustomValue:forKey:` (Module setAttributes path). */
46
+ + (void)setCustomKeysAndValues:(NSDictionary *)attributes;
47
+
48
+ + (void)setUserID:(NSString *)userId;
49
+
50
+ /**
51
+ * Records a JS exception model.
52
+ * Each frame dictionary uses keys: `fn` (symbol), `file`, `line` (number).
53
+ */
54
+ + (void)recordJavaScriptErrorWithMessage:(NSString *)message
55
+ frames:(NSArray<NSDictionary *> *)frames
56
+ isUnhandledPromiseRejection:(BOOL)isUnhandledPromiseRejection;
57
+
58
+ @end
59
+
60
+ /** Upper bound (seconds) a JS fatal waits for its record to save before the process exits. */
61
+ FOUNDATION_EXPORT const NSTimeInterval RNFBCrashlyticsFatalExitWatchdogTimeout;
62
+
63
+ /**
64
+ * Records a JS fatal, then terminates the process once the record has had a chance to save.
65
+ *
66
+ * `-[FIRCrashlytics recordExceptionModel:]` saves asynchronously: once Crashlytics' context-init
67
+ * promise is fulfilled, the save block is dispatched to the main queue. Terminating on the
68
+ * calling queue right after recording usually loses that save. Instead the terminator is
69
+ * enqueued on `terminateQueue` (the main queue in production), so it runs FIFO after the save
70
+ * block. A watchdog on `watchdogQueue` terminates after `watchdogTimeout` if `terminateQueue`
71
+ * is hung. The terminator runs at most once.
72
+ *
73
+ * Known limit: a JS fatal recorded before Crashlytics finished initialising queues its save as a
74
+ * promise observer instead of a main-queue block, so it can still be lost.
75
+ * The FIFO ordering also relies on FBLPromise's default dispatch queue being the main queue; if an
76
+ * app changes it, the save runs off the main queue and the terminator can run first, losing the
77
+ * report. The watchdog only covers a hung `terminateQueue`.
78
+ */
79
+ @interface RNFBCrashlyticsFatalExit : NSObject
80
+
81
+ /** Production instance: terminates with `exit(0)` on the main queue, 2 s watchdog. */
82
+ + (instancetype)shared;
83
+
84
+ - (instancetype)initWithTerminator:(dispatch_block_t)terminator
85
+ terminateQueue:(dispatch_queue_t)terminateQueue
86
+ watchdogQueue:(dispatch_queue_t)watchdogQueue
87
+ watchdogTimeout:(NSTimeInterval)watchdogTimeout NS_DESIGNATED_INITIALIZER;
88
+
89
+ - (instancetype)init NS_UNAVAILABLE;
90
+
91
+ /**
92
+ * Runs `record` synchronously, then schedules termination, even when `record` raises. Returns NO
93
+ * without running `record` when an earlier call already started terminating (a second fatal
94
+ * while waiting).
95
+ */
96
+ - (BOOL)recordThenTerminate:(dispatch_block_t)record;
97
+
98
+ @end
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ #if __has_include(<Firebase/Firebase.h>)
19
+ #import <Firebase/Firebase.h>
20
+ #elif __has_include(<FirebaseCrashlytics/FirebaseCrashlytics.h>)
21
+ #import <FirebaseCore/FirebaseCore.h>
22
+ #import <FirebaseCrashlytics/FirebaseCrashlytics.h>
23
+ #else
24
+ @import FirebaseCore;
25
+ @import FirebaseCrashlytics;
26
+ #endif
27
+
28
+ #if __has_include(<RNFBCrashlytics/RNFBCrashlytics-Swift.h>)
29
+ #import <RNFBCrashlytics/RNFBCrashlytics-Swift.h>
30
+ #elif __has_include("RNFBCrashlytics-Swift.h")
31
+ #import "RNFBCrashlytics-Swift.h"
32
+ #elif __has_include("RNFBCrashlyticsDebuggerProbe-Swift.inc")
33
+ #import "RNFBCrashlyticsDebuggerProbe-Swift.inc"
34
+ #else
35
+ #error "RNFBCrashlyticsDebuggerProbe Swift interface not found"
36
+ #endif
37
+
38
+ #import <stdatomic.h>
39
+
40
+ #import "RNFBCrashlyticsHelper.h"
41
+
42
+ @implementation RNFBCrashlyticsHelper
43
+
44
+ + (BOOL)isDebuggerAttached {
45
+ return [RNFBCrashlyticsDebuggerProbe isDebuggerAttached];
46
+ }
47
+
48
+ + (void)checkForUnsentReportsWithCompletion:(void (^)(BOOL unsentReports))completion {
49
+ [[FIRCrashlytics crashlytics] checkForUnsentReportsWithCompletion:^(BOOL unsentReports) {
50
+ if (completion != nil) {
51
+ completion(unsentReports);
52
+ }
53
+ }];
54
+ }
55
+
56
+ + (void)deleteUnsentReports {
57
+ [[FIRCrashlytics crashlytics] deleteUnsentReports];
58
+ }
59
+
60
+ + (BOOL)didCrashDuringPreviousExecution {
61
+ return [[FIRCrashlytics crashlytics] didCrashDuringPreviousExecution];
62
+ }
63
+
64
+ + (void)log:(NSString *)message {
65
+ [[FIRCrashlytics crashlytics] log:message];
66
+ }
67
+
68
+ + (void)sendUnsentReports {
69
+ [[FIRCrashlytics crashlytics] sendUnsentReports];
70
+ }
71
+
72
+ + (void)setCustomValue:(NSString *)value forKey:(NSString *)key {
73
+ [[FIRCrashlytics crashlytics] setCustomValue:value forKey:key];
74
+ }
75
+
76
+ + (void)setCustomKeysAndValues:(NSDictionary *)attributes {
77
+ NSArray *keys = [attributes allKeys];
78
+ for (NSString *key in keys) {
79
+ [[FIRCrashlytics crashlytics] setCustomValue:attributes[key] forKey:key];
80
+ }
81
+ }
82
+
83
+ + (void)setUserID:(NSString *)userId {
84
+ [[FIRCrashlytics crashlytics] setUserID:userId];
85
+ }
86
+
87
+ + (void)recordJavaScriptErrorWithMessage:(NSString *)message
88
+ frames:(NSArray<NSDictionary *> *)frames
89
+ isUnhandledPromiseRejection:(BOOL)isUnhandledPromiseRejection {
90
+ NSMutableArray *stackTrace = [[NSMutableArray alloc] init];
91
+ for (NSDictionary *stackFrame in frames) {
92
+ NSString *symbol = stackFrame[@"fn"];
93
+ NSString *file = stackFrame[@"file"];
94
+ uint32_t line = (uint32_t)[stackFrame[@"line"] unsignedIntValue];
95
+ FIRStackFrame *customFrame = [FIRStackFrame stackFrameWithSymbol:symbol file:file line:line];
96
+ [stackTrace addObject:customFrame];
97
+ }
98
+
99
+ NSString *name = @"JavaScriptError";
100
+ if (isUnhandledPromiseRejection) {
101
+ name = @"UnhandledPromiseRejection";
102
+ }
103
+
104
+ FIRExceptionModel *exceptionModel = [FIRExceptionModel exceptionModelWithName:name
105
+ reason:message];
106
+ exceptionModel.stackTrace = stackTrace;
107
+
108
+ [[FIRCrashlytics crashlytics] recordExceptionModel:exceptionModel];
109
+ }
110
+
111
+ @end
112
+
113
+ const NSTimeInterval RNFBCrashlyticsFatalExitWatchdogTimeout = 2.0;
114
+
115
+ @implementation RNFBCrashlyticsFatalExit {
116
+ dispatch_block_t _terminator;
117
+ dispatch_queue_t _terminateQueue;
118
+ dispatch_queue_t _watchdogQueue;
119
+ NSTimeInterval _watchdogTimeout;
120
+ atomic_bool _exiting;
121
+ atomic_bool _terminated;
122
+ }
123
+
124
+ + (instancetype)shared {
125
+ static RNFBCrashlyticsFatalExit *shared;
126
+ static dispatch_once_t once;
127
+ dispatch_once(&once, ^{
128
+ shared = [[RNFBCrashlyticsFatalExit alloc]
129
+ initWithTerminator:^{
130
+ // Coverage exception: ends the process, so no test can run it.
131
+ exit(0);
132
+ }
133
+ terminateQueue:dispatch_get_main_queue()
134
+ watchdogQueue:dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
135
+ watchdogTimeout:RNFBCrashlyticsFatalExitWatchdogTimeout];
136
+ });
137
+ return shared;
138
+ }
139
+
140
+ - (instancetype)initWithTerminator:(dispatch_block_t)terminator
141
+ terminateQueue:(dispatch_queue_t)terminateQueue
142
+ watchdogQueue:(dispatch_queue_t)watchdogQueue
143
+ watchdogTimeout:(NSTimeInterval)watchdogTimeout {
144
+ if (self = [super init]) {
145
+ _terminator = [terminator copy];
146
+ _terminateQueue = terminateQueue;
147
+ _watchdogQueue = watchdogQueue;
148
+ _watchdogTimeout = watchdogTimeout;
149
+ atomic_init(&_exiting, false);
150
+ atomic_init(&_terminated, false);
151
+ }
152
+ return self;
153
+ }
154
+
155
+ - (BOOL)recordThenTerminate:(dispatch_block_t)record {
156
+ if (atomic_exchange(&_exiting, true)) {
157
+ return NO;
158
+ }
159
+ @try {
160
+ record();
161
+ } @catch (NSException *exception) {
162
+ // The guard is already set, so terminate anyway rather than leave the app running with every
163
+ // later fatal ignored.
164
+ NSLog(@"RNFBCrashlytics: recording JS fatal failed: %@", exception);
165
+ }
166
+ dispatch_block_t terminateOnce = ^{
167
+ [self terminateOnce];
168
+ };
169
+ // FIFO behind the Crashlytics save block that recordExceptionModel: enqueued on main.
170
+ dispatch_async(_terminateQueue, terminateOnce);
171
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_watchdogTimeout * NSEC_PER_SEC)),
172
+ _watchdogQueue, terminateOnce);
173
+ return YES;
174
+ }
175
+
176
+ - (void)terminateOnce {
177
+ if (!atomic_exchange(&_terminated, true)) {
178
+ _terminator();
179
+ }
180
+ }
181
+
182
+ @end
@@ -15,23 +15,17 @@
15
15
  *
16
16
  */
17
17
 
18
- #include <Foundation/Foundation.h>
19
- #include <sys/sysctl.h>
20
-
18
+ // This module intentionally has no Firebase imports and no `*-Swift.h` —
19
+ // see RNFBCrashlyticsHelper.h. Every Firebase Crashlytics SDK call (and the
20
+ // Swift debugger probe) is routed through the plain Objective-C
21
+ // RNFBCrashlyticsHelper class instead, which can safely `@import`
22
+ // FirebaseCrashlytics / import the generated Swift interface because it
23
+ // compiles as ObjC, not ObjC++.
21
24
  #import <React/RCTConvert.h>
22
25
  #import <React/RCTLog.h>
23
- #import <React/RCTUtils.h>
24
-
25
- #if __has_include(<Firebase/Firebase.h>)
26
- #import <Firebase/Firebase.h>
27
- #elif __has_include(<FirebaseCrashlytics/FirebaseCrashlytics.h>)
28
- #import <FirebaseCore/FirebaseCore.h>
29
- #import <FirebaseCrashlytics/FirebaseCrashlytics.h>
30
- #else
31
- @import FirebaseCore;
32
- @import FirebaseCrashlytics;
33
- #endif
26
+
34
27
  #import "RNFBApp/RNFBSharedUtils.h"
28
+ #import "RNFBCrashlyticsHelper.h"
35
29
  #import "RNFBCrashlyticsInitProvider.h"
36
30
  #import "RNFBCrashlyticsModule.h"
37
31
  #import "RNFBPreferences.h"
@@ -58,7 +52,7 @@ RCT_EXPORT_MODULE(NativeRNFBTurboCrashlytics)
58
52
  constants[@"isCrashlyticsJavascriptExceptionHandlerChainingEnabled"] =
59
53
  @([RCTConvert BOOL:@([RNFBCrashlyticsInitProvider
60
54
  isCrashlyticsJavascriptExceptionHandlerChainingEnabled])]);
61
- if ([self isDebuggerAttached]) {
55
+ if ([RNFBCrashlyticsHelper isDebuggerAttached]) {
62
56
  RCTLog(
63
57
  @"Crashlytics - WARNING: Debugger detected. Crashlytics will not receive crash reports.");
64
58
  }
@@ -74,14 +68,15 @@ RCT_EXPORT_MODULE(NativeRNFBTurboCrashlytics)
74
68
  }
75
69
 
76
70
  - (void)checkForUnsentReports:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
77
- [[FIRCrashlytics crashlytics] checkForUnsentReportsWithCompletion:^(BOOL unsentReports) {
71
+ (void)reject;
72
+ [RNFBCrashlyticsHelper checkForUnsentReportsWithCompletion:^(BOOL unsentReports) {
78
73
  resolve([NSNumber numberWithBool:unsentReports]);
79
74
  }];
80
75
  }
81
76
 
82
77
  - (void)crash {
83
78
  if ([RNFBCrashlyticsInitProvider isCrashlyticsCollectionEnabled]) {
84
- if ([self isDebuggerAttached]) {
79
+ if ([RNFBCrashlyticsHelper isDebuggerAttached]) {
85
80
  RCTLog(
86
81
  @"Crashlytics - WARNING: Debugger detected. Crashlytics will not receive crash reports.");
87
82
  }
@@ -96,15 +91,21 @@ RCT_EXPORT_MODULE(NativeRNFBTurboCrashlytics)
96
91
  - (void)crashWithStackPromise:(JS::NativeRNFBTurboCrashlytics::JavaScriptErrorObject &)jsErrorDict
97
92
  resolve:(RCTPromiseResolveBlock)resolve
98
93
  reject:(RCTPromiseRejectBlock)reject {
94
+ (void)reject;
99
95
  if ([RNFBCrashlyticsInitProvider isCrashlyticsCollectionEnabled]) {
100
- if ([self isDebuggerAttached]) {
96
+ if ([RNFBCrashlyticsHelper isDebuggerAttached]) {
101
97
  RCTLog(
102
98
  @"Crashlytics - WARNING: Debugger detected. Crashlytics will not receive crash reports.");
103
99
  }
104
- [self recordJavaScriptError:jsErrorDict];
105
-
106
- ELog(@"Crashlytics - Crash logged. Terminating app.");
107
- exit(0);
100
+ // Coverage exception: this branch ends the process, so neither XCTest nor e2e can observe it.
101
+ // Keep it a thin delegate; ordering, watchdog and the second-fatal guard live in
102
+ // RNFBCrashlyticsFatalExit (unit-tested). The promise is intentionally never resolved: that
103
+ // keeps the JS global handler suspended (no chaining to the previous handler, no
104
+ // fall-through) until termination.
105
+ ELog(@"Crashlytics - Logging crash and terminating app.");
106
+ [[RNFBCrashlyticsFatalExit shared]
107
+ recordThenTerminate:[self javaScriptErrorRecorder:jsErrorDict]];
108
+ return;
108
109
  } else {
109
110
  RCTLog(@"Crashlytics - INFO: crashlytics collection is not enabled, not crashing.");
110
111
  }
@@ -112,37 +113,41 @@ RCT_EXPORT_MODULE(NativeRNFBTurboCrashlytics)
112
113
  }
113
114
 
114
115
  - (void)deleteUnsentReports:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
115
- [[FIRCrashlytics crashlytics] deleteUnsentReports];
116
+ (void)reject;
117
+ [RNFBCrashlyticsHelper deleteUnsentReports];
116
118
  resolve([NSNull null]);
117
119
  }
118
120
 
119
121
  - (void)didCrashOnPreviousExecution:(RCTPromiseResolveBlock)resolve
120
122
  reject:(RCTPromiseRejectBlock)reject {
121
- BOOL didCrash = [[FIRCrashlytics crashlytics] didCrashDuringPreviousExecution];
123
+ (void)reject;
124
+ BOOL didCrash = [RNFBCrashlyticsHelper didCrashDuringPreviousExecution];
122
125
  resolve([NSNumber numberWithBool:didCrash]);
123
126
  }
124
127
 
125
128
  - (void)log:(NSString *)message {
126
- [[FIRCrashlytics crashlytics] log:message];
129
+ [RNFBCrashlyticsHelper log:message];
127
130
  }
128
131
 
129
132
  - (void)logPromise:(NSString *)message
130
133
  resolve:(RCTPromiseResolveBlock)resolve
131
134
  reject:(RCTPromiseRejectBlock)reject {
132
- [[FIRCrashlytics crashlytics] log:message];
135
+ (void)reject;
136
+ [RNFBCrashlyticsHelper log:message];
133
137
  resolve([NSNull null]);
134
138
  }
135
139
 
136
140
  - (void)sendUnsentReports {
137
- [[FIRCrashlytics crashlytics] sendUnsentReports];
141
+ [RNFBCrashlyticsHelper sendUnsentReports];
138
142
  }
139
143
 
140
144
  - (void)setAttribute:(NSString *)key
141
145
  value:(NSString *)value
142
146
  resolve:(RCTPromiseResolveBlock)resolve
143
147
  reject:(RCTPromiseRejectBlock)reject {
148
+ (void)reject;
144
149
  if ([RNFBCrashlyticsInitProvider isCrashlyticsCollectionEnabled]) {
145
- [[FIRCrashlytics crashlytics] setCustomValue:value forKey:key];
150
+ [RNFBCrashlyticsHelper setCustomValue:value forKey:key];
146
151
  }
147
152
  resolve([NSNull null]);
148
153
  }
@@ -150,12 +155,9 @@ RCT_EXPORT_MODULE(NativeRNFBTurboCrashlytics)
150
155
  - (void)setAttributes:(NSDictionary *)attributes
151
156
  resolve:(RCTPromiseResolveBlock)resolve
152
157
  reject:(RCTPromiseRejectBlock)reject {
158
+ (void)reject;
153
159
  if ([RNFBCrashlyticsInitProvider isCrashlyticsCollectionEnabled]) {
154
- NSArray *keys = [attributes allKeys];
155
-
156
- for (NSString *key in keys) {
157
- [[FIRCrashlytics crashlytics] setCustomValue:attributes[key] forKey:key];
158
- }
160
+ [RNFBCrashlyticsHelper setCustomKeysAndValues:attributes];
159
161
  }
160
162
  resolve([NSNull null]);
161
163
  }
@@ -163,8 +165,9 @@ RCT_EXPORT_MODULE(NativeRNFBTurboCrashlytics)
163
165
  - (void)setUserId:(NSString *)userId
164
166
  resolve:(RCTPromiseResolveBlock)resolve
165
167
  reject:(RCTPromiseRejectBlock)reject {
168
+ (void)reject;
166
169
  if ([RNFBCrashlyticsInitProvider isCrashlyticsCollectionEnabled]) {
167
- [[FIRCrashlytics crashlytics] setUserID:userId];
170
+ [RNFBCrashlyticsHelper setUserID:userId];
168
171
  }
169
172
  resolve([NSNull null]);
170
173
  }
@@ -178,6 +181,7 @@ RCT_EXPORT_MODULE(NativeRNFBTurboCrashlytics)
178
181
  - (void)recordErrorPromise:(JS::NativeRNFBTurboCrashlytics::JavaScriptErrorObject &)jsErrorDict
179
182
  resolve:(RCTPromiseResolveBlock)resolve
180
183
  reject:(RCTPromiseRejectBlock)reject {
184
+ (void)reject;
181
185
  if ([RNFBCrashlyticsInitProvider isCrashlyticsCollectionEnabled]) {
182
186
  [self recordJavaScriptError:jsErrorDict];
183
187
  }
@@ -187,60 +191,38 @@ RCT_EXPORT_MODULE(NativeRNFBTurboCrashlytics)
187
191
  - (void)setCrashlyticsCollectionEnabled:(BOOL)enabled
188
192
  resolve:(RCTPromiseResolveBlock)resolve
189
193
  reject:(RCTPromiseRejectBlock)reject {
194
+ (void)reject;
190
195
  [[RNFBPreferences shared] setBooleanValue:@"crashlytics_auto_collection_enabled"
191
196
  boolValue:enabled];
192
197
  resolve([NSNull null]);
193
198
  }
194
199
 
195
200
  - (void)recordJavaScriptError:(JS::NativeRNFBTurboCrashlytics::JavaScriptErrorObject &)jsErrorDict {
201
+ [self javaScriptErrorRecorder:jsErrorDict]();
202
+ }
203
+
204
+ // Converts the codegen struct to Foundation values up front, so the returned block captures no
205
+ // C++ references and can be handed to RNFBCrashlyticsFatalExit.
206
+ - (dispatch_block_t)javaScriptErrorRecorder:
207
+ (JS::NativeRNFBTurboCrashlytics::JavaScriptErrorObject &)jsErrorDict {
196
208
  NSString *message = jsErrorDict.message();
197
209
  auto stackFrames = jsErrorDict.frames();
198
- NSMutableArray *stackTrace = [[NSMutableArray alloc] init];
210
+ NSMutableArray *frames = [[NSMutableArray alloc] init];
199
211
  BOOL isUnhandledPromiseRejection = jsErrorDict.isUnhandledRejection();
200
212
 
201
213
  for (const auto &stackFrame : stackFrames) {
202
- FIRStackFrame *customFrame = [FIRStackFrame stackFrameWithSymbol:stackFrame.fn()
203
- file:stackFrame.file()
204
- line:(uint32_t)stackFrame.line()];
205
- [stackTrace addObject:customFrame];
206
- }
207
-
208
- NSString *name = @"JavaScriptError";
209
- if (isUnhandledPromiseRejection) {
210
- name = @"UnhandledPromiseRejection";
214
+ [frames addObject:@{
215
+ @"fn" : stackFrame.fn() ?: @"",
216
+ @"file" : stackFrame.file() ?: @"",
217
+ @"line" : @((NSUInteger)stackFrame.line()),
218
+ }];
211
219
  }
212
220
 
213
- FIRExceptionModel *exceptionModel = [FIRExceptionModel exceptionModelWithName:name
214
- reason:message];
215
- exceptionModel.stackTrace = stackTrace;
216
-
217
- [[FIRCrashlytics crashlytics] recordExceptionModel:exceptionModel];
218
- }
219
-
220
- - (BOOL)isDebuggerAttached {
221
- static BOOL debuggerIsAttached = NO;
222
-
223
- static dispatch_once_t debuggerPredicate;
224
- dispatch_once(&debuggerPredicate, ^{
225
- struct kinfo_proc info;
226
- size_t info_size = sizeof(info);
227
- int name[4];
228
-
229
- name[0] = CTL_KERN;
230
- name[1] = KERN_PROC;
231
- name[2] = KERN_PROC_PID;
232
- name[3] = getpid();
233
-
234
- if (sysctl(name, 4, &info, &info_size, NULL, 0) == -1) {
235
- ELog(@"Crashlytics ERROR: Checking for a running debugger via sysctl() failed: %s",
236
- strerror(errno));
237
- debuggerIsAttached = false;
238
- }
239
-
240
- if (!debuggerIsAttached && (info.kp_proc.p_flag & P_TRACED) != 0) debuggerIsAttached = true;
241
- });
242
-
243
- return debuggerIsAttached;
221
+ return ^{
222
+ [RNFBCrashlyticsHelper recordJavaScriptErrorWithMessage:message
223
+ frames:frames
224
+ isUnhandledPromiseRejection:isUnhandledPromiseRejection];
225
+ };
244
226
  }
245
227
 
246
228
  @end
@@ -10,6 +10,8 @@
10
10
  2744B98621F45429004F8E3F /* RNFBCrashlyticsModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2744B98521F45429004F8E3F /* RNFBCrashlyticsModule.mm */; };
11
11
  2748D82422371C1C00FC8DC8 /* RNFBCrashlyticsInitProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2748D82322371C1C00FC8DC8 /* RNFBCrashlyticsInitProvider.m */; };
12
12
  C66637BB25FA560700DCAA51 /* RNFBCrashlyticsNativeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = C66637BA25FA560700DCAA51 /* RNFBCrashlyticsNativeHelper.m */; };
13
+ RNFBCL000000000000000001 /* RNFBCrashlyticsDebuggerProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = RNFBCL000000000000000002 /* RNFBCrashlyticsDebuggerProbe.swift */; };
14
+ RNFBCL000000000000000003 /* RNFBCrashlyticsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = RNFBCL000000000000000005 /* RNFBCrashlyticsHelper.m */; };
13
15
  /* End PBXBuildFile section */
14
16
 
15
17
  /* Begin PBXCopyFilesBuildPhase section */
@@ -32,6 +34,9 @@
32
34
  2748D82322371C1C00FC8DC8 /* RNFBCrashlyticsInitProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFBCrashlyticsInitProvider.m; sourceTree = "<group>"; };
33
35
  C66637BA25FA560700DCAA51 /* RNFBCrashlyticsNativeHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFBCrashlyticsNativeHelper.m; sourceTree = "<group>"; };
34
36
  C66637BD25FA561D00DCAA51 /* RNFBCrashlyticsNativeHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFBCrashlyticsNativeHelper.h; sourceTree = "<group>"; };
37
+ RNFBCL000000000000000002 /* RNFBCrashlyticsDebuggerProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RNFBCrashlyticsDebuggerProbe.swift; path = RNFBCrashlytics/RNFBCrashlyticsDebuggerProbe.swift; sourceTree = SOURCE_ROOT; };
38
+ RNFBCL000000000000000004 /* RNFBCrashlyticsHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNFBCrashlyticsHelper.h; path = RNFBCrashlytics/RNFBCrashlyticsHelper.h; sourceTree = SOURCE_ROOT; };
39
+ RNFBCL000000000000000005 /* RNFBCrashlyticsHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNFBCrashlyticsHelper.m; path = RNFBCrashlytics/RNFBCrashlyticsHelper.m; sourceTree = SOURCE_ROOT; };
35
40
  /* End PBXFileReference section */
36
41
 
37
42
  /* Begin PBXFrameworksBuildPhase section */
@@ -58,6 +63,9 @@
58
63
  children = (
59
64
  2744B98421F45429004F8E3F /* RNFBCrashlyticsModule.h */,
60
65
  2744B98521F45429004F8E3F /* RNFBCrashlyticsModule.mm */,
66
+ RNFBCL000000000000000004 /* RNFBCrashlyticsHelper.h */,
67
+ RNFBCL000000000000000005 /* RNFBCrashlyticsHelper.m */,
68
+ RNFBCL000000000000000002 /* RNFBCrashlyticsDebuggerProbe.swift */,
61
69
  2748D82222371C0900FC8DC8 /* RNFBCrashlyticsInitProvider.h */,
62
70
  2748D82322371C1C00FC8DC8 /* RNFBCrashlyticsInitProvider.m */,
63
71
  C66637BD25FA561D00DCAA51 /* RNFBCrashlyticsNativeHelper.h */,
@@ -133,8 +141,10 @@
133
141
  buildActionMask = 2147483647;
134
142
  files = (
135
143
  C66637BB25FA560700DCAA51 /* RNFBCrashlyticsNativeHelper.m in Sources */,
144
+ RNFBCL000000000000000003 /* RNFBCrashlyticsHelper.m in Sources */,
136
145
  2748D82422371C1C00FC8DC8 /* RNFBCrashlyticsInitProvider.m in Sources */,
137
146
  2744B98621F45429004F8E3F /* RNFBCrashlyticsModule.mm in Sources */,
147
+ RNFBCL000000000000000001 /* RNFBCrashlyticsDebuggerProbe.swift in Sources */,
138
148
  );
139
149
  runOnlyForDeploymentPostprocessing = 0;
140
150
  };
@@ -160,6 +170,7 @@
160
170
  CODE_SIGN_STYLE = Automatic;
161
171
  COPY_PHASE_STRIP = NO;
162
172
  DEBUG_INFORMATION_FORMAT = dwarf;
173
+ DEFINES_MODULE = YES;
163
174
  GCC_C_LANGUAGE_STANDARD = gnu11;
164
175
  GCC_DYNAMIC_NO_PIC = NO;
165
176
  GCC_OPTIMIZATION_LEVEL = 0;
@@ -176,6 +187,7 @@
176
187
  PRODUCT_NAME = "$(TARGET_NAME)";
177
188
  SDKROOT = iphoneos;
178
189
  SKIP_INSTALL = YES;
190
+ SWIFT_VERSION = 5.10;
179
191
  TARGETED_DEVICE_FAMILY = "1,2";
180
192
  };
181
193
  name = Debug;
@@ -199,6 +211,7 @@
199
211
  CODE_SIGN_STYLE = Automatic;
200
212
  COPY_PHASE_STRIP = NO;
201
213
  DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
214
+ DEFINES_MODULE = YES;
202
215
  ENABLE_NS_ASSERTIONS = NO;
203
216
  GCC_C_LANGUAGE_STANDARD = gnu11;
204
217
  GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -210,6 +223,7 @@
210
223
  PRODUCT_NAME = "$(TARGET_NAME)";
211
224
  SDKROOT = iphoneos;
212
225
  SKIP_INSTALL = YES;
226
+ SWIFT_VERSION = 5.10;
213
227
  TARGETED_DEVICE_FAMILY = "1,2";
214
228
  VALIDATE_PRODUCT = YES;
215
229
  };