Skip to content

Commit 3e7761c

Browse files
committed
feat(spotlight): Extract SpotlightIntegration to separate module
1 parent c48e22d commit 3e7761c

16 files changed

Lines changed: 263 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@
55
### Features
66

77
- Update Android targetSdk to API 36 (Android 16) ([#5016](https://github.com/getsentry/sentry-java/pull/5016))
8+
- Add AndroidManifest support for Spotlight configuration via `io.sentry.spotlight.enabled` and `io.sentry.spotlight.url` ([#5064](https://github.com/getsentry/sentry-java/pull/5064))
9+
10+
### Fixes
11+
12+
- Extract `SpotlightIntegration` to separate `sentry-spotlight` module to prevent insecure HTTP URLs from appearing in release APKs ([#5064](https://github.com/getsentry/sentry-java/pull/5064))
13+
- **Breaking:** Users who enable Spotlight must now add the `io.sentry:sentry-spotlight` dependency:
14+
```kotlin
15+
dependencies {
16+
debugImplementation("io.sentry:sentry-spotlight:{{VERSION}}")
17+
}
18+
```
819

920
### Internal
1021

sentry-android-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ dependencies {
102102
testImplementation(libs.mockito.kotlin)
103103
testImplementation(libs.mockito.inline)
104104
testImplementation(projects.sentryTestSupport)
105+
testImplementation(projects.sentrySpotlight)
105106
testImplementation(projects.sentryAndroidFragment)
106107
testImplementation(projects.sentryAndroidTimber)
107108
testImplementation(projects.sentryAndroidReplay)

sentry-android-core/proguard-rules.pro

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,8 @@
8383
-dontwarn io.sentry.android.distribution.DistributionIntegration
8484
-keepnames class io.sentry.android.distribution.DistributionIntegration
8585
##---------------End: proguard configuration for sentry-android-distribution ----------
86+
87+
##---------------Begin: proguard configuration for sentry-spotlight ----------
88+
-dontwarn io.sentry.spotlight.SpotlightIntegration
89+
-keepnames class io.sentry.spotlight.SpotlightIntegration
90+
##---------------End: proguard configuration for sentry-spotlight ----------

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ final class ManifestMetadataReader {
164164

165165
static final String FEEDBACK_SHOW_BRANDING = "io.sentry.feedback.show-branding";
166166

167+
static final String SPOTLIGHT_ENABLE = "io.sentry.spotlight.enabled";
168+
169+
static final String SPOTLIGHT_CONNECTION_URL = "io.sentry.spotlight.url";
170+
167171
/** ManifestMetadataReader ctor */
168172
private ManifestMetadataReader() {}
169173

@@ -642,6 +646,15 @@ static void applyMetadata(
642646
metadata, logger, FEEDBACK_USE_SENTRY_USER, feedbackOptions.isUseSentryUser()));
643647
feedbackOptions.setShowBranding(
644648
readBool(metadata, logger, FEEDBACK_SHOW_BRANDING, feedbackOptions.isShowBranding()));
649+
650+
options.setEnableSpotlight(
651+
readBool(metadata, logger, SPOTLIGHT_ENABLE, options.isEnableSpotlight()));
652+
653+
final @Nullable String spotlightUrl =
654+
readString(metadata, logger, SPOTLIGHT_CONNECTION_URL, null);
655+
if (spotlightUrl != null) {
656+
options.setSpotlightConnectionUrl(spotlightUrl);
657+
}
645658
}
646659
options
647660
.getLogger()

sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,4 +2217,76 @@ class ManifestMetadataReaderTest {
22172217
assertTrue(headers.contains("Authorization"))
22182218
assertTrue(headers.contains("X-Custom-Header"))
22192219
}
2220+
2221+
// Spotlight Configuration Tests
2222+
2223+
@Test
2224+
fun `applyMetadata reads spotlight enabled and keeps default value if not found`() {
2225+
// Arrange
2226+
val context = fixture.getContext()
2227+
2228+
// Act
2229+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
2230+
2231+
// Assert
2232+
assertFalse(fixture.options.isEnableSpotlight)
2233+
}
2234+
2235+
@Test
2236+
fun `applyMetadata reads spotlight enabled to options`() {
2237+
// Arrange
2238+
val bundle = bundleOf(ManifestMetadataReader.SPOTLIGHT_ENABLE to true)
2239+
val context = fixture.getContext(metaData = bundle)
2240+
2241+
// Act
2242+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
2243+
2244+
// Assert
2245+
assertTrue(fixture.options.isEnableSpotlight)
2246+
}
2247+
2248+
@Test
2249+
fun `applyMetadata reads spotlight url and keeps null if not found`() {
2250+
// Arrange
2251+
val context = fixture.getContext()
2252+
2253+
// Act
2254+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
2255+
2256+
// Assert
2257+
assertNull(fixture.options.spotlightConnectionUrl)
2258+
}
2259+
2260+
@Test
2261+
fun `applyMetadata reads spotlight url to options`() {
2262+
// Arrange
2263+
val expectedUrl = "http://10.0.2.2:8969/stream"
2264+
val bundle = bundleOf(ManifestMetadataReader.SPOTLIGHT_CONNECTION_URL to expectedUrl)
2265+
val context = fixture.getContext(metaData = bundle)
2266+
2267+
// Act
2268+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
2269+
2270+
// Assert
2271+
assertEquals(expectedUrl, fixture.options.spotlightConnectionUrl)
2272+
}
2273+
2274+
@Test
2275+
fun `applyMetadata reads both spotlight enabled and url to options`() {
2276+
// Arrange
2277+
val expectedUrl = "http://localhost:8969/stream"
2278+
val bundle =
2279+
bundleOf(
2280+
ManifestMetadataReader.SPOTLIGHT_ENABLE to true,
2281+
ManifestMetadataReader.SPOTLIGHT_CONNECTION_URL to expectedUrl,
2282+
)
2283+
val context = fixture.getContext(metaData = bundle)
2284+
2285+
// Act
2286+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
2287+
2288+
// Assert
2289+
assertTrue(fixture.options.isEnableSpotlight)
2290+
assertEquals(expectedUrl, fixture.options.spotlightConnectionUrl)
2291+
}
22202292
}

sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import io.sentry.SentryOptions
2626
import io.sentry.SentryOptions.BeforeSendCallback
2727
import io.sentry.Session
2828
import io.sentry.ShutdownHookIntegration
29-
import io.sentry.SpotlightIntegration
3029
import io.sentry.SystemOutLogger
3130
import io.sentry.UncaughtExceptionHandlerIntegration
3231
import io.sentry.android.core.cache.AndroidEnvelopeCache
@@ -46,6 +45,7 @@ import io.sentry.cache.PersistingScopeObserver.TRANSACTION_FILENAME
4645
import io.sentry.cache.tape.QueueFile
4746
import io.sentry.protocol.Contexts
4847
import io.sentry.protocol.SentryId
48+
import io.sentry.spotlight.SpotlightIntegration
4949
import io.sentry.test.applyTestOptions
5050
import io.sentry.transport.NoOpEnvelopeCache
5151
import io.sentry.util.StringUtils
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
public final class io/sentry/spotlight/BuildConfig {
2+
public static final field VERSION_NAME Ljava/lang/String;
3+
}
4+
5+
public final class io/sentry/spotlight/SpotlightIntegration : io/sentry/Integration, io/sentry/SentryOptions$BeforeEnvelopeCallback, java/io/Closeable {
6+
public fun <init> ()V
7+
public fun close ()V
8+
public fun execute (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
9+
public fun getSpotlightConnectionUrl ()Ljava/lang/String;
10+
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
11+
}
12+

sentry-spotlight/build.gradle.kts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import net.ltgt.gradle.errorprone.errorprone
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
4+
plugins {
5+
`java-library`
6+
id("io.sentry.javadoc")
7+
alias(libs.plugins.kotlin.jvm)
8+
jacoco
9+
alias(libs.plugins.errorprone)
10+
alias(libs.plugins.gradle.versions)
11+
alias(libs.plugins.animalsniffer)
12+
alias(libs.plugins.buildconfig)
13+
}
14+
15+
tasks.withType<KotlinCompile>().configureEach {
16+
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
17+
}
18+
19+
dependencies {
20+
api(projects.sentry)
21+
22+
errorprone(libs.errorprone.core)
23+
errorprone(libs.nopen.checker)
24+
errorprone(libs.nullaway)
25+
compileOnly(libs.jetbrains.annotations)
26+
compileOnly(libs.nopen.annotations)
27+
28+
// tests
29+
testImplementation(kotlin(Config.kotlinStdLib))
30+
testImplementation(libs.kotlin.test.junit)
31+
testImplementation(libs.mockito.kotlin)
32+
testImplementation(libs.mockito.inline)
33+
testImplementation(projects.sentryTestSupport)
34+
35+
val gummyBearsModule = libs.gummy.bears.api21.get().module
36+
signature("${gummyBearsModule}:${libs.versions.gummyBears.get()}@signature")
37+
}
38+
39+
configure<SourceSetContainer> { test { java.srcDir("src/test/java") } }
40+
41+
jacoco { toolVersion = libs.versions.jacoco.get() }
42+
43+
tasks.jacocoTestReport {
44+
reports {
45+
xml.required.set(true)
46+
html.required.set(false)
47+
}
48+
}
49+
50+
tasks {
51+
jacocoTestCoverageVerification {
52+
violationRules { rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } } }
53+
}
54+
check {
55+
dependsOn(jacocoTestCoverageVerification)
56+
dependsOn(jacocoTestReport)
57+
dependsOn(animalsnifferMain)
58+
}
59+
}
60+
61+
buildConfig {
62+
useJavaOutput()
63+
packageName("io.sentry.spotlight")
64+
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
65+
}
66+
67+
tasks.withType<JavaCompile>().configureEach {
68+
dependsOn(tasks.generateBuildConfig)
69+
options.errorprone {
70+
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
71+
option("NullAway:AnnotatedPackages", "io.sentry")
72+
}
73+
}
74+
75+
tasks.jar {
76+
manifest {
77+
attributes(
78+
"Sentry-Version-Name" to project.version,
79+
"Sentry-SDK-Name" to Config.Sentry.SENTRY_JAVA_SDK_NAME,
80+
"Sentry-SDK-Package-Name" to "maven:io.sentry:sentry-spotlight",
81+
"Implementation-Vendor" to "Sentry",
82+
"Implementation-Title" to project.name,
83+
"Implementation-Version" to project.version,
84+
)
85+
}
86+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
##---------------Begin: proguard configuration for sentry-spotlight ----------
2+
3+
# The SDK checks at runtime if this class is available via Class.forName
4+
-keep class io.sentry.spotlight.SpotlightIntegration { <init>(...); }
5+
6+
# To ensure that stack traces is unambiguous
7+
# https://developer.android.com/studio/build/shrink-code#decode-stack-trace
8+
-keepattributes LineNumberTable,SourceFile
9+
10+
##---------------End: proguard configuration for sentry-spotlight ----------

sentry/src/main/java/io/sentry/SpotlightIntegration.java renamed to sentry-spotlight/src/main/java/io/sentry/spotlight/SpotlightIntegration.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
package io.sentry;
1+
package io.sentry.spotlight;
22

33
import static io.sentry.SentryLevel.DEBUG;
44
import static io.sentry.SentryLevel.ERROR;
55
import static io.sentry.SentryLevel.WARNING;
66
import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion;
77

8+
import io.sentry.Hint;
9+
import io.sentry.ILogger;
10+
import io.sentry.IScopes;
11+
import io.sentry.ISentryExecutorService;
12+
import io.sentry.Integration;
13+
import io.sentry.NoOpLogger;
14+
import io.sentry.NoOpSentryExecutorService;
15+
import io.sentry.SentryEnvelope;
16+
import io.sentry.SentryExecutorService;
17+
import io.sentry.SentryIntegrationPackageStorage;
18+
import io.sentry.SentryOptions;
819
import io.sentry.util.Platform;
920
import java.io.Closeable;
1021
import java.io.IOException;
@@ -22,6 +33,11 @@
2233
public final class SpotlightIntegration
2334
implements Integration, SentryOptions.BeforeEnvelopeCallback, Closeable {
2435

36+
static {
37+
SentryIntegrationPackageStorage.getInstance()
38+
.addPackage("maven:io.sentry:sentry-spotlight", BuildConfig.VERSION_NAME);
39+
}
40+
2541
private @Nullable SentryOptions options;
2642
private @NotNull ILogger logger = NoOpLogger.getInstance();
2743
private @NotNull ISentryExecutorService executorService = NoOpSentryExecutorService.getInstance();

0 commit comments

Comments
 (0)