11package io .sentry .react ;
22
3+ import static io .sentry .android .core .internal .util .ScreenshotUtils .takeScreenshot ;
4+
35import android .app .Activity ;
46import android .content .Context ;
57import android .content .pm .PackageInfo ;
1719import com .facebook .react .bridge .ReadableArray ;
1820import com .facebook .react .bridge .ReadableMap ;
1921import com .facebook .react .bridge .ReadableMapKeySetIterator ;
22+ import com .facebook .react .bridge .WritableArray ;
2023import com .facebook .react .bridge .WritableMap ;
24+ import com .facebook .react .bridge .WritableNativeArray ;
25+ import com .facebook .react .bridge .WritableNativeMap ;
2126import com .facebook .react .module .annotations .ReactModule ;
2227
2328import java .io .BufferedInputStream ;
3136import java .util .List ;
3237import java .util .Map ;
3338import java .util .UUID ;
34- import java .util .logging .Level ;
35- import java .util .logging .Logger ;
3639
3740import io .sentry .Breadcrumb ;
3841import io .sentry .HubAdapter ;
42+ import io .sentry .ILogger ;
3943import io .sentry .Integration ;
4044import io .sentry .Sentry ;
4145import io .sentry .SentryEvent ;
4246import io .sentry .SentryLevel ;
4347import io .sentry .UncaughtExceptionHandlerIntegration ;
4448import io .sentry .android .core .AnrIntegration ;
4549import io .sentry .android .core .AppStartState ;
50+ import io .sentry .android .core .BuildInfoProvider ;
51+ import io .sentry .android .core .CurrentActivityHolder ;
4652import io .sentry .android .core .NdkIntegration ;
53+ import io .sentry .android .core .ScreenshotEventProcessor ;
4754import io .sentry .android .core .SentryAndroid ;
55+ import io .sentry .android .core .AndroidLogger ;
4856import io .sentry .protocol .SdkVersion ;
4957import io .sentry .protocol .SentryException ;
5058import io .sentry .protocol .SentryPackage ;
@@ -55,13 +63,15 @@ public class RNSentryModule extends ReactContextBaseJavaModule {
5563
5664 public static final String NAME = "RNSentry" ;
5765
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 );
5968 private static final String modulesPath = "modules.json" ;
6069 private static final Charset UTF_8 = Charset .forName ("UTF-8" );
6170
6271 private final PackageInfo packageInfo ;
6372 private FrameMetricsAggregator frameMetricsAggregator = null ;
6473 private boolean androidXAvailable ;
74+ private ScreenshotEventProcessor screenshotEventProcessor ;
6575
6676 private static boolean didFetchAppStart ;
6777
@@ -86,11 +96,10 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
8696 SentryAndroid .init (this .getReactApplicationContext (), options -> {
8797 if (rnOptions .hasKey ("debug" ) && rnOptions .getBoolean ("debug" )) {
8898 options .setDebug (true );
89- logger .setLevel (Level .INFO );
9099 }
91100 if (rnOptions .hasKey ("dsn" ) && rnOptions .getString ("dsn" ) != null ) {
92101 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 ));
94103 options .setDsn (dsn );
95104 } else {
96105 // SentryAndroid needs an empty string fallback for the dsn.
@@ -134,6 +143,9 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
134143 // by default we hide.
135144 options .setAttachThreads (rnOptions .getBoolean ("attachThreads" ));
136145 }
146+ if (rnOptions .hasKey ("attachScreenshot" )) {
147+ options .setAttachScreenshot (rnOptions .getBoolean ("attachScreenshot" ));
148+ }
137149 if (rnOptions .hasKey ("sendDefaultPii" )) {
138150 options .setSendDefaultPii (rnOptions .getBoolean ("sendDefaultPii" ));
139151 }
@@ -169,8 +181,13 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
169181 }
170182 }
171183 }
184+ logger .log (SentryLevel .INFO , String .format ("Native Integrations '%s'" , options .getIntegrations ()));
172185
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+ }
174191 });
175192
176193 promise .resolve (true );
@@ -195,7 +212,7 @@ public void fetchModules(Promise promise) {
195212 } catch (FileNotFoundException e ) {
196213 promise .resolve (null );
197214 } catch (Throwable e ) {
198- logger .warning ( "Fetching JS Modules failed." );
215+ logger .log ( SentryLevel . WARNING , "Fetching JS Modules failed." );
199216 promise .resolve (null );
200217 }
201218 }
@@ -216,10 +233,10 @@ public void fetchNativeAppStart(Promise promise) {
216233 final Boolean isColdStart = appStartInstance .isColdStart ();
217234
218235 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." );
220237 promise .resolve (null );
221238 } 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." );
223240 promise .resolve (null );
224241 } else {
225242 final double appStartTimestamp = (double ) appStartTime .getTime ();
@@ -285,7 +302,7 @@ public void fetchNativeFrames(Promise promise) {
285302
286303 promise .resolve (map );
287304 } catch (Throwable ignored ) {
288- logger .warning ( "Error fetching native frames." );
305+ logger .log ( SentryLevel . WARNING , "Error fetching native frames." );
289306 promise .resolve (null );
290307 }
291308 }
@@ -302,7 +319,7 @@ public void captureEnvelope(ReadableArray rawBytes, ReadableMap options, Promise
302319 final String outboxPath = HubAdapter .getInstance ().getOptions ().getOutboxPath ();
303320
304321 if (outboxPath == null ) {
305- logger .severe (
322+ logger .log ( SentryLevel . ERROR ,
306323 "Error retrieving outboxPath. Envelope will not be sent. Is the Android SDK initialized?" );
307324 } else {
308325 File installation = new File (outboxPath , UUID .randomUUID ().toString ());
@@ -311,16 +328,47 @@ public void captureEnvelope(ReadableArray rawBytes, ReadableMap options, Promise
311328 }
312329 }
313330 } catch (Throwable ignored ) {
314- logger .severe ( "Error while writing envelope to outbox." );
331+ logger .log ( SentryLevel . ERROR , "Error while writing envelope to outbox." );
315332 }
316333 promise .resolve (true );
317334 }
318335
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+
319367 private static PackageInfo getPackageInfo (Context ctx ) {
320368 try {
321369 return ctx .getPackageManager ().getPackageInfo (ctx .getPackageName (), 0 );
322370 } catch (PackageManager .NameNotFoundException e ) {
323- logger .warning ( "Error getting package info." );
371+ logger .log ( SentryLevel . WARNING , "Error getting package info." );
324372 return null ;
325373 }
326374 }
@@ -483,17 +531,17 @@ public void enableNativeFramesTracking() {
483531 try {
484532 frameMetricsAggregator .add (currentActivity );
485533
486- logger .info ( "FrameMetricsAggregator installed." );
534+ logger .log ( SentryLevel . INFO , "FrameMetricsAggregator installed." );
487535 } catch (Throwable ignored ) {
488536 // throws ConcurrentModification when calling addOnFrameMetricsAvailableListener
489537 // 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." );
491539 }
492540 } else {
493- logger .info ( "currentActivity isn't available." );
541+ logger .log ( SentryLevel . INFO , "currentActivity isn't available." );
494542 }
495543 } 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." );
497545 }
498546 }
499547
0 commit comments