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 ;
17
19
import com .facebook .react .bridge .ReadableArray ;
18
20
import com .facebook .react .bridge .ReadableMap ;
19
21
import com .facebook .react .bridge .ReadableMapKeySetIterator ;
22
+ import com .facebook .react .bridge .WritableArray ;
20
23
import com .facebook .react .bridge .WritableMap ;
24
+ import com .facebook .react .bridge .WritableNativeArray ;
25
+ import com .facebook .react .bridge .WritableNativeMap ;
21
26
import com .facebook .react .module .annotations .ReactModule ;
22
27
23
28
import java .io .BufferedInputStream ;
31
36
import java .util .List ;
32
37
import java .util .Map ;
33
38
import java .util .UUID ;
34
- import java .util .logging .Level ;
35
- import java .util .logging .Logger ;
36
39
37
40
import io .sentry .Breadcrumb ;
38
41
import io .sentry .HubAdapter ;
42
+ import io .sentry .ILogger ;
39
43
import io .sentry .Integration ;
40
44
import io .sentry .Sentry ;
41
45
import io .sentry .SentryEvent ;
42
46
import io .sentry .SentryLevel ;
43
47
import io .sentry .UncaughtExceptionHandlerIntegration ;
44
48
import io .sentry .android .core .AnrIntegration ;
45
49
import io .sentry .android .core .AppStartState ;
50
+ import io .sentry .android .core .BuildInfoProvider ;
51
+ import io .sentry .android .core .CurrentActivityHolder ;
46
52
import io .sentry .android .core .NdkIntegration ;
53
+ import io .sentry .android .core .ScreenshotEventProcessor ;
47
54
import io .sentry .android .core .SentryAndroid ;
55
+ import io .sentry .android .core .AndroidLogger ;
48
56
import io .sentry .protocol .SdkVersion ;
49
57
import io .sentry .protocol .SentryException ;
50
58
import io .sentry .protocol .SentryPackage ;
@@ -55,13 +63,15 @@ public class RNSentryModule extends ReactContextBaseJavaModule {
55
63
56
64
public static final String NAME = "RNSentry" ;
57
65
58
- private static final Logger logger = Logger .getLogger ("react-native-sentry" );
66
+ private static final ILogger logger = new AndroidLogger (NAME );
67
+ private static final BuildInfoProvider buildInfo = new BuildInfoProvider (logger );
59
68
private static final String modulesPath = "modules.json" ;
60
69
private static final Charset UTF_8 = Charset .forName ("UTF-8" );
61
70
62
71
private final PackageInfo packageInfo ;
63
72
private FrameMetricsAggregator frameMetricsAggregator = null ;
64
73
private boolean androidXAvailable ;
74
+ private ScreenshotEventProcessor screenshotEventProcessor ;
65
75
66
76
private static boolean didFetchAppStart ;
67
77
@@ -86,11 +96,10 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
86
96
SentryAndroid .init (this .getReactApplicationContext (), options -> {
87
97
if (rnOptions .hasKey ("debug" ) && rnOptions .getBoolean ("debug" )) {
88
98
options .setDebug (true );
89
- logger .setLevel (Level .INFO );
90
99
}
91
100
if (rnOptions .hasKey ("dsn" ) && rnOptions .getString ("dsn" ) != null ) {
92
101
String dsn = rnOptions .getString ("dsn" );
93
- logger .info ( String .format ("Starting with DSN: '%s'" , dsn ));
102
+ logger .log ( SentryLevel . INFO , String .format ("Starting with DSN: '%s'" , dsn ));
94
103
options .setDsn (dsn );
95
104
} else {
96
105
// SentryAndroid needs an empty string fallback for the dsn.
@@ -134,6 +143,9 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
134
143
// by default we hide.
135
144
options .setAttachThreads (rnOptions .getBoolean ("attachThreads" ));
136
145
}
146
+ if (rnOptions .hasKey ("attachScreenshot" )) {
147
+ options .setAttachScreenshot (rnOptions .getBoolean ("attachScreenshot" ));
148
+ }
137
149
if (rnOptions .hasKey ("sendDefaultPii" )) {
138
150
options .setSendDefaultPii (rnOptions .getBoolean ("sendDefaultPii" ));
139
151
}
@@ -169,8 +181,13 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
169
181
}
170
182
}
171
183
}
184
+ logger .log (SentryLevel .INFO , String .format ("Native Integrations '%s'" , options .getIntegrations ()));
172
185
173
- logger .info (String .format ("Native Integrations '%s'" , options .getIntegrations ()));
186
+ final CurrentActivityHolder currentActivityHolder = CurrentActivityHolder .getInstance ();
187
+ final Activity currentActivity = getCurrentActivity ();
188
+ if (currentActivity != null ) {
189
+ currentActivityHolder .setActivity (currentActivity );
190
+ }
174
191
});
175
192
176
193
promise .resolve (true );
@@ -195,7 +212,7 @@ public void fetchModules(Promise promise) {
195
212
} catch (FileNotFoundException e ) {
196
213
promise .resolve (null );
197
214
} catch (Throwable e ) {
198
- logger .warning ( "Fetching JS Modules failed." );
215
+ logger .log ( SentryLevel . WARNING , "Fetching JS Modules failed." );
199
216
promise .resolve (null );
200
217
}
201
218
}
@@ -216,10 +233,10 @@ public void fetchNativeAppStart(Promise promise) {
216
233
final Boolean isColdStart = appStartInstance .isColdStart ();
217
234
218
235
if (appStartTime == null ) {
219
- logger .warning ( "App start won't be sent due to missing appStartTime." );
236
+ logger .log ( SentryLevel . WARNING , "App start won't be sent due to missing appStartTime." );
220
237
promise .resolve (null );
221
238
} else if (isColdStart == null ) {
222
- logger .warning ( "App start won't be sent due to missing isColdStart." );
239
+ logger .log ( SentryLevel . WARNING , "App start won't be sent due to missing isColdStart." );
223
240
promise .resolve (null );
224
241
} else {
225
242
final double appStartTimestamp = (double ) appStartTime .getTime ();
@@ -285,7 +302,7 @@ public void fetchNativeFrames(Promise promise) {
285
302
286
303
promise .resolve (map );
287
304
} catch (Throwable ignored ) {
288
- logger .warning ( "Error fetching native frames." );
305
+ logger .log ( SentryLevel . WARNING , "Error fetching native frames." );
289
306
promise .resolve (null );
290
307
}
291
308
}
@@ -302,7 +319,7 @@ public void captureEnvelope(ReadableArray rawBytes, ReadableMap options, Promise
302
319
final String outboxPath = HubAdapter .getInstance ().getOptions ().getOutboxPath ();
303
320
304
321
if (outboxPath == null ) {
305
- logger .severe (
322
+ logger .log ( SentryLevel . ERROR ,
306
323
"Error retrieving outboxPath. Envelope will not be sent. Is the Android SDK initialized?" );
307
324
} else {
308
325
File installation = new File (outboxPath , UUID .randomUUID ().toString ());
@@ -311,16 +328,47 @@ public void captureEnvelope(ReadableArray rawBytes, ReadableMap options, Promise
311
328
}
312
329
}
313
330
} catch (Throwable ignored ) {
314
- logger .severe ( "Error while writing envelope to outbox." );
331
+ logger .log ( SentryLevel . ERROR , "Error while writing envelope to outbox." );
315
332
}
316
333
promise .resolve (true );
317
334
}
318
335
336
+ @ ReactMethod
337
+ public void captureScreenshot (Promise promise ) {
338
+
339
+ final Activity activity = getCurrentActivity ();
340
+ if (activity == null ) {
341
+ logger .log (SentryLevel .WARNING , "CurrentActivity is null, can't capture screenshot." );
342
+ promise .resolve (null );
343
+ return ;
344
+ }
345
+
346
+ final byte [] raw = takeScreenshot (activity , logger , buildInfo );
347
+ if (raw == null ) {
348
+ logger .log (SentryLevel .WARNING , "Screenshot is null, screen was not captured." );
349
+ promise .resolve (null );
350
+ return ;
351
+ }
352
+
353
+ final WritableNativeArray data = new WritableNativeArray ();
354
+ for (final byte b : raw ) {
355
+ data .pushInt (b );
356
+ }
357
+ final WritableMap screenshot = new WritableNativeMap ();
358
+ screenshot .putString ("contentType" , "image/png" );
359
+ screenshot .putArray ("data" , data );
360
+ screenshot .putString ("filename" , "screenshot.png" );
361
+
362
+ final WritableArray screenshotsArray = new WritableNativeArray ();
363
+ screenshotsArray .pushMap (screenshot );
364
+ promise .resolve (screenshotsArray );
365
+ }
366
+
319
367
private static PackageInfo getPackageInfo (Context ctx ) {
320
368
try {
321
369
return ctx .getPackageManager ().getPackageInfo (ctx .getPackageName (), 0 );
322
370
} catch (PackageManager .NameNotFoundException e ) {
323
- logger .warning ( "Error getting package info." );
371
+ logger .log ( SentryLevel . WARNING , "Error getting package info." );
324
372
return null ;
325
373
}
326
374
}
@@ -483,17 +531,17 @@ public void enableNativeFramesTracking() {
483
531
try {
484
532
frameMetricsAggregator .add (currentActivity );
485
533
486
- logger .info ( "FrameMetricsAggregator installed." );
534
+ logger .log ( SentryLevel . INFO , "FrameMetricsAggregator installed." );
487
535
} catch (Throwable ignored ) {
488
536
// throws ConcurrentModification when calling addOnFrameMetricsAvailableListener
489
537
// this is a best effort since we can't reproduce it
490
- logger .severe ( "Error adding Activity to frameMetricsAggregator." );
538
+ logger .log ( SentryLevel . ERROR , "Error adding Activity to frameMetricsAggregator." );
491
539
}
492
540
} else {
493
- logger .info ( "currentActivity isn't available." );
541
+ logger .log ( SentryLevel . INFO , "currentActivity isn't available." );
494
542
}
495
543
} else {
496
- logger .warning ( "androidx.core' isn't available as a dependency." );
544
+ logger .log ( SentryLevel . WARNING , "androidx.core' isn't available as a dependency." );
497
545
}
498
546
}
499
547
0 commit comments