1
1
package io .sentry .react ;
2
2
3
+ import static io .sentry .android .core .internal .util .ScreenshotUtils .takeScreenshot ;
4
+
3
5
import android .app .Activity ;
4
6
import android .content .Context ;
5
7
import android .content .pm .PackageInfo ;
16
18
import com .facebook .react .bridge .ReadableArray ;
17
19
import com .facebook .react .bridge .ReadableMap ;
18
20
import com .facebook .react .bridge .ReadableMapKeySetIterator ;
21
+ import com .facebook .react .bridge .WritableArray ;
19
22
import com .facebook .react .bridge .WritableMap ;
23
+ import com .facebook .react .bridge .WritableNativeArray ;
24
+ import com .facebook .react .bridge .WritableNativeMap ;
25
+ import com .facebook .react .module .annotations .ReactModule ;
20
26
21
27
import java .io .BufferedInputStream ;
22
28
import java .io .File ;
29
35
import java .util .List ;
30
36
import java .util .Map ;
31
37
import java .util .UUID ;
32
- import java .util .logging .Level ;
33
- import java .util .logging .Logger ;
34
38
35
39
import io .sentry .Breadcrumb ;
36
40
import io .sentry .HubAdapter ;
41
+ import io .sentry .ILogger ;
37
42
import io .sentry .Integration ;
38
43
import io .sentry .Sentry ;
39
44
import io .sentry .SentryEvent ;
40
45
import io .sentry .SentryLevel ;
41
46
import io .sentry .UncaughtExceptionHandlerIntegration ;
42
47
import io .sentry .android .core .AnrIntegration ;
43
48
import io .sentry .android .core .AppStartState ;
49
+ import io .sentry .android .core .BuildInfoProvider ;
50
+ import io .sentry .android .core .CurrentActivityHolder ;
44
51
import io .sentry .android .core .NdkIntegration ;
52
+ import io .sentry .android .core .ScreenshotEventProcessor ;
45
53
import io .sentry .android .core .SentryAndroid ;
54
+ import io .sentry .android .core .AndroidLogger ;
46
55
import io .sentry .protocol .SdkVersion ;
47
56
import io .sentry .protocol .SentryException ;
48
57
import io .sentry .protocol .SentryPackage ;
@@ -52,14 +61,16 @@ public class RNSentryModuleImpl {
52
61
53
62
public static final String NAME = "RNSentry" ;
54
63
55
- private static final Logger logger = Logger .getLogger ("react-native-sentry" );
64
+ private static final ILogger logger = new AndroidLogger (NAME );
65
+ private static final BuildInfoProvider buildInfo = new BuildInfoProvider (logger );
56
66
private static final String modulesPath = "modules.json" ;
57
67
private static final Charset UTF_8 = Charset .forName ("UTF-8" );
58
68
59
69
private final ReactApplicationContext reactApplicationContext ;
60
70
private final PackageInfo packageInfo ;
61
71
private FrameMetricsAggregator frameMetricsAggregator = null ;
62
72
private boolean androidXAvailable ;
73
+ private ScreenshotEventProcessor screenshotEventProcessor ;
63
74
64
75
private static boolean didFetchAppStart ;
65
76
@@ -86,11 +97,10 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
86
97
SentryAndroid .init (this .getReactApplicationContext (), options -> {
87
98
if (rnOptions .hasKey ("debug" ) && rnOptions .getBoolean ("debug" )) {
88
99
options .setDebug (true );
89
- logger .setLevel (Level .INFO );
90
100
}
91
101
if (rnOptions .hasKey ("dsn" ) && rnOptions .getString ("dsn" ) != null ) {
92
102
String dsn = rnOptions .getString ("dsn" );
93
- logger .info ( String .format ("Starting with DSN: '%s'" , dsn ));
103
+ logger .log ( SentryLevel . INFO , String .format ("Starting with DSN: '%s'" , dsn ));
94
104
options .setDsn (dsn );
95
105
} else {
96
106
// SentryAndroid needs an empty string fallback for the dsn.
@@ -134,6 +144,9 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
134
144
// by default we hide.
135
145
options .setAttachThreads (rnOptions .getBoolean ("attachThreads" ));
136
146
}
147
+ if (rnOptions .hasKey ("attachScreenshot" )) {
148
+ options .setAttachScreenshot (rnOptions .getBoolean ("attachScreenshot" ));
149
+ }
137
150
if (rnOptions .hasKey ("sendDefaultPii" )) {
138
151
options .setSendDefaultPii (rnOptions .getBoolean ("sendDefaultPii" ));
139
152
}
@@ -169,8 +182,13 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
169
182
}
170
183
}
171
184
}
185
+ logger .log (SentryLevel .INFO , String .format ("Native Integrations '%s'" , options .getIntegrations ()));
172
186
173
- logger .info (String .format ("Native Integrations '%s'" , options .getIntegrations ()));
187
+ final CurrentActivityHolder currentActivityHolder = CurrentActivityHolder .getInstance ();
188
+ final Activity currentActivity = getCurrentActivity ();
189
+ if (currentActivity != null ) {
190
+ currentActivityHolder .setActivity (currentActivity );
191
+ }
174
192
});
175
193
176
194
promise .resolve (true );
@@ -193,7 +211,7 @@ public void fetchModules(Promise promise) {
193
211
} catch (FileNotFoundException e ) {
194
212
promise .resolve (null );
195
213
} catch (Throwable e ) {
196
- logger .warning ( "Fetching JS Modules failed." );
214
+ logger .log ( SentryLevel . WARNING , "Fetching JS Modules failed." );
197
215
promise .resolve (null );
198
216
}
199
217
}
@@ -212,10 +230,10 @@ public void fetchNativeAppStart(Promise promise) {
212
230
final Boolean isColdStart = appStartInstance .isColdStart ();
213
231
214
232
if (appStartTime == null ) {
215
- logger .warning ( "App start won't be sent due to missing appStartTime." );
233
+ logger .log ( SentryLevel . WARNING , "App start won't be sent due to missing appStartTime." );
216
234
promise .resolve (null );
217
235
} else if (isColdStart == null ) {
218
- logger .warning ( "App start won't be sent due to missing isColdStart." );
236
+ logger .log ( SentryLevel . WARNING , "App start won't be sent due to missing isColdStart." );
219
237
promise .resolve (null );
220
238
} else {
221
239
final double appStartTimestamp = (double ) appStartTime .getTime ();
@@ -280,7 +298,7 @@ public void fetchNativeFrames(Promise promise) {
280
298
281
299
promise .resolve (map );
282
300
} catch (Throwable ignored ) {
283
- logger .warning ( "Error fetching native frames." );
301
+ logger .log ( SentryLevel . WARNING , "Error fetching native frames." );
284
302
promise .resolve (null );
285
303
}
286
304
}
@@ -296,7 +314,7 @@ public void captureEnvelope(ReadableArray rawBytes, ReadableMap options, Promise
296
314
final String outboxPath = HubAdapter .getInstance ().getOptions ().getOutboxPath ();
297
315
298
316
if (outboxPath == null ) {
299
- logger .severe (
317
+ logger .log ( SentryLevel . ERROR ,
300
318
"Error retrieving outboxPath. Envelope will not be sent. Is the Android SDK initialized?" );
301
319
} else {
302
320
File installation = new File (outboxPath , UUID .randomUUID ().toString ());
@@ -305,16 +323,46 @@ public void captureEnvelope(ReadableArray rawBytes, ReadableMap options, Promise
305
323
}
306
324
}
307
325
} catch (Throwable ignored ) {
308
- logger .severe ( "Error while writing envelope to outbox." );
326
+ logger .log ( SentryLevel . ERROR , "Error while writing envelope to outbox." );
309
327
}
310
328
promise .resolve (true );
311
329
}
312
330
331
+ public void captureScreenshot (Promise promise ) {
332
+
333
+ final Activity activity = getCurrentActivity ();
334
+ if (activity == null ) {
335
+ logger .log (SentryLevel .WARNING , "CurrentActivity is null, can't capture screenshot." );
336
+ promise .resolve (null );
337
+ return ;
338
+ }
339
+
340
+ final byte [] raw = takeScreenshot (activity , logger , buildInfo );
341
+ if (raw == null ) {
342
+ logger .log (SentryLevel .WARNING , "Screenshot is null, screen was not captured." );
343
+ promise .resolve (null );
344
+ return ;
345
+ }
346
+
347
+ final WritableNativeArray data = new WritableNativeArray ();
348
+ for (final byte b : raw ) {
349
+ data .pushInt (b );
350
+ }
351
+ final WritableMap screenshot = new WritableNativeMap ();
352
+ screenshot .putString ("contentType" , "image/png" );
353
+ screenshot .putArray ("data" , data );
354
+ screenshot .putString ("filename" , "screenshot.png" );
355
+
356
+ final WritableArray screenshotsArray = new WritableNativeArray ();
357
+ screenshotsArray .pushMap (screenshot );
358
+ promise .resolve (screenshotsArray );
359
+ }
360
+
313
361
private static PackageInfo getPackageInfo (Context ctx ) {
314
362
try {
315
363
return ctx .getPackageManager ().getPackageInfo (ctx .getPackageName (), 0 );
316
364
} catch (PackageManager .NameNotFoundException e ) {
317
- logger .warning ( "Error getting package info." );
365
+ logger .log ( SentryLevel . WARNING , "Error getting package info." );
318
366
return null ;
319
367
}
320
368
}
@@ -469,17 +517,17 @@ public void enableNativeFramesTracking() {
469
517
try {
470
518
frameMetricsAggregator .add (currentActivity );
471
519
472
- logger .info ( "FrameMetricsAggregator installed." );
520
+ logger .log ( SentryLevel . INFO , "FrameMetricsAggregator installed." );
473
521
} catch (Throwable ignored ) {
474
522
// throws ConcurrentModification when calling addOnFrameMetricsAvailableListener
475
523
// this is a best effort since we can't reproduce it
476
- logger .severe ( "Error adding Activity to frameMetricsAggregator." );
524
+ logger .log ( SentryLevel . ERROR , "Error adding Activity to frameMetricsAggregator." );
477
525
}
478
526
} else {
479
- logger .info ( "currentActivity isn't available." );
527
+ logger .log ( SentryLevel . INFO , "currentActivity isn't available." );
480
528
}
481
529
} else {
482
- logger .warning ( "androidx.core' isn't available as a dependency." );
530
+ logger .log ( SentryLevel . WARNING , "androidx.core' isn't available as a dependency." );
483
531
}
484
532
}
485
533
0 commit comments