mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
fix(app): standardize branding and disable telemetry
This commit is contained in:
@@ -18,13 +18,14 @@ class DiagnosticsCoordinator(
|
||||
val crashReporter: CrashReporter,
|
||||
val bugReports: BugReportService,
|
||||
private val scope: CoroutineScope,
|
||||
private val included: Boolean = DiagnosticsBuildConfig.INCLUDED,
|
||||
) {
|
||||
fun start() {
|
||||
// Install id is useful for bug-report correlation even without telemetry.
|
||||
scope.launch {
|
||||
preferencesRepository.ensureDiagnosticsInstallId()
|
||||
}
|
||||
if (!DiagnosticsBuildConfig.INCLUDED) return
|
||||
if (!included) return
|
||||
crashReporter.startObservingPreferences()
|
||||
crashReporter.installUnhandledExceptionHandler()
|
||||
scope.launch {
|
||||
@@ -33,7 +34,7 @@ class DiagnosticsCoordinator(
|
||||
}
|
||||
|
||||
fun record(name: String, properties: Map<String, String> = emptyMap()) {
|
||||
if (!DiagnosticsBuildConfig.INCLUDED) return
|
||||
if (!included) return
|
||||
telemetry.record(name, properties)
|
||||
}
|
||||
|
||||
@@ -45,6 +46,7 @@ class DiagnosticsCoordinator(
|
||||
preferencesRepository: PreferencesRepository,
|
||||
scope: CoroutineScope,
|
||||
transport: DiagnosticsTransport = NoOpDiagnosticsTransport(),
|
||||
included: Boolean = DiagnosticsBuildConfig.INCLUDED,
|
||||
): DiagnosticsCoordinator {
|
||||
val breadcrumbs = BreadcrumbBuffer()
|
||||
val crashStore = createPendingCrashStore(appDataDir)
|
||||
@@ -53,6 +55,7 @@ class DiagnosticsCoordinator(
|
||||
transport = transport,
|
||||
breadcrumbs = breadcrumbs,
|
||||
scope = scope,
|
||||
included = included,
|
||||
)
|
||||
val crashReporter = CrashReporter(
|
||||
store = crashStore,
|
||||
@@ -78,6 +81,7 @@ class DiagnosticsCoordinator(
|
||||
crashReporter = crashReporter,
|
||||
bugReports = bugReports,
|
||||
scope = scope,
|
||||
included = included,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ fun createDiagnosticsTransport(
|
||||
platform: String,
|
||||
installIdProvider: suspend () -> String,
|
||||
): DiagnosticsTransport = buildDiagnosticsTransport(
|
||||
included = DiagnosticsBuildConfig.INCLUDED,
|
||||
endpoint = DiagnosticsBuildConfig.ENDPOINT,
|
||||
ingestKey = DiagnosticsBuildConfig.INGEST_KEY,
|
||||
appVersion = appVersion,
|
||||
@@ -127,12 +128,14 @@ fun createDiagnosticsTransport(
|
||||
)
|
||||
|
||||
internal fun buildDiagnosticsTransport(
|
||||
included: Boolean = true,
|
||||
endpoint: String,
|
||||
ingestKey: String,
|
||||
appVersion: String,
|
||||
platform: String,
|
||||
installIdProvider: suspend () -> String,
|
||||
): DiagnosticsTransport {
|
||||
if (!included) return NoOpDiagnosticsTransport()
|
||||
val normalizedEndpoint = endpoint.trim()
|
||||
val normalizedIngestKey = ingestKey.trim()
|
||||
if (normalizedEndpoint.isEmpty() && normalizedIngestKey.isEmpty()) return NoOpDiagnosticsTransport()
|
||||
|
||||
@@ -31,6 +31,7 @@ class TelemetryRecorder(
|
||||
private val flushIntervalMillis: Long = DefaultFlushIntervalMillis,
|
||||
private val retryBackoffMillis: Long = DefaultRetryBackoffMillis,
|
||||
private val automaticRetryCount: Int = DefaultAutomaticRetryCount,
|
||||
private val included: Boolean = true,
|
||||
) {
|
||||
private val bufferMutex = Mutex()
|
||||
private val state = AtomicReference(TelemetryState())
|
||||
@@ -42,22 +43,24 @@ class TelemetryRecorder(
|
||||
require(flushIntervalMillis > 0) { "flushIntervalMillis must be positive" }
|
||||
require(retryBackoffMillis > 0) { "retryBackoffMillis must be positive" }
|
||||
require(automaticRetryCount >= 0) { "automaticRetryCount must not be negative" }
|
||||
scope.launch {
|
||||
preferencesRepository.preferences
|
||||
.map { it.diagnosticsEnabled }
|
||||
.distinctUntilChanged()
|
||||
.collect { isEnabled ->
|
||||
updateState { current ->
|
||||
if (isEnabled) current.copy(enabled = true) else TelemetryState(enabled = false)
|
||||
if (included) {
|
||||
scope.launch {
|
||||
preferencesRepository.preferences
|
||||
.map { it.diagnosticsEnabled }
|
||||
.distinctUntilChanged()
|
||||
.collect { isEnabled ->
|
||||
updateState { current ->
|
||||
if (isEnabled) current.copy(enabled = true) else TelemetryState(enabled = false)
|
||||
}
|
||||
flushSignals.trySend(Unit)
|
||||
}
|
||||
flushSignals.trySend(Unit)
|
||||
}
|
||||
}
|
||||
scope.launch { runAutomaticFlushes() }
|
||||
}
|
||||
scope.launch { runAutomaticFlushes() }
|
||||
}
|
||||
|
||||
fun record(name: String, properties: Map<String, String> = emptyMap()) {
|
||||
if (!DiagnosticsBuildConfig.INCLUDED) return
|
||||
if (!included) return
|
||||
val sanitizedName = sanitizeDiagnosticName(name)
|
||||
if (sanitizedName.isBlank()) return
|
||||
val sanitizedProperties = sanitizeDiagnosticProperties(properties)
|
||||
|
||||
@@ -88,6 +88,7 @@ class SettingsViewModel(
|
||||
private val messages: UiMessageController,
|
||||
private val bugReports: BugReportService,
|
||||
private val diagnostics: DiagnosticsCoordinator? = null,
|
||||
private val diagnosticsIncluded: Boolean = DiagnosticsBuildConfig.INCLUDED,
|
||||
) : ViewModel() {
|
||||
private val _state = MutableStateFlow(
|
||||
SettingsState(
|
||||
@@ -200,7 +201,7 @@ class SettingsViewModel(
|
||||
}
|
||||
|
||||
fun setDiagnosticsEnabled(enabled: Boolean) {
|
||||
if (!DiagnosticsBuildConfig.INCLUDED) return
|
||||
if (!diagnosticsIncluded) return
|
||||
viewModelScope.launch {
|
||||
preferencesRepository.setDiagnosticsEnabled(enabled)
|
||||
diagnostics?.record(
|
||||
|
||||
@@ -273,6 +273,19 @@ class DiagnosticsTest {
|
||||
assertIs<NoOpDiagnosticsTransport>(transport)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun excludedTransportIgnoresIncompleteConfiguration() {
|
||||
val transport = buildDiagnosticsTransport(
|
||||
included = false,
|
||||
endpoint = "https://diag.example",
|
||||
ingestKey = "",
|
||||
appVersion = "1.0",
|
||||
platform = "Test",
|
||||
installIdProvider = { "id" },
|
||||
)
|
||||
assertIs<NoOpDiagnosticsTransport>(transport)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noOpTransportReportsUnavailableDelivery() = runTest {
|
||||
val result = NoOpDiagnosticsTransport().sendEvents(
|
||||
@@ -329,6 +342,27 @@ class DiagnosticsTest {
|
||||
assertEquals(1, breadcrumbs.snapshot().size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun excludedTelemetryDoesNotStartOrRecordEvents() = runTest {
|
||||
val transport = RecordingDiagnosticsTransport()
|
||||
val breadcrumbs = BreadcrumbBuffer()
|
||||
val recorder = TelemetryRecorder(
|
||||
preferencesRepository = fakePrefs(diagnosticsEnabled = true),
|
||||
transport = transport,
|
||||
breadcrumbs = breadcrumbs,
|
||||
scope = TestScope(UnconfinedTestDispatcher(testScheduler)),
|
||||
flushThreshold = 1,
|
||||
included = false,
|
||||
)
|
||||
|
||||
recorder.record("app_open")
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(0, recorder.pendingCount())
|
||||
assertTrue(transport.events.isEmpty())
|
||||
assertTrue(breadcrumbs.snapshot().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun telemetryRetainsColdStartEventsUntilConsentLoads() = runTest {
|
||||
val backing = fakePrefs(diagnosticsEnabled = true)
|
||||
|
||||
@@ -158,7 +158,7 @@ class ViewModelsTest {
|
||||
fun settingsTogglesDiagnosticsPreference() = runTest {
|
||||
Dispatchers.setMain(StandardTestDispatcher(testScheduler))
|
||||
val preferences = preferences()
|
||||
val viewModel = settingsViewModel(preferences)
|
||||
val viewModel = settingsViewModel(preferences, diagnosticsIncluded = true)
|
||||
advanceUntilIdle()
|
||||
assertFalse(viewModel.state.value.diagnosticsEnabled)
|
||||
viewModel.setDiagnosticsEnabled(true)
|
||||
@@ -167,6 +167,20 @@ class ViewModelsTest {
|
||||
assertTrue(viewModel.state.value.diagnosticsEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsIgnoresDiagnosticsOptInWhenExcluded() = runTest {
|
||||
Dispatchers.setMain(StandardTestDispatcher(testScheduler))
|
||||
val preferences = preferences()
|
||||
val viewModel = settingsViewModel(preferences)
|
||||
advanceUntilIdle()
|
||||
|
||||
viewModel.setDiagnosticsEnabled(true)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertFalse(preferences.mutablePreferences.value.diagnosticsEnabled)
|
||||
assertFalse(viewModel.state.value.diagnosticsEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsSubmitsBugReportAndClearsForm() = runTest {
|
||||
Dispatchers.setMain(StandardTestDispatcher(testScheduler))
|
||||
@@ -647,6 +661,7 @@ class ViewModelsTest {
|
||||
notifications: FakeNotificationService = FakeNotificationService(),
|
||||
transport: DiagnosticsTransport = RecordingDiagnosticsTransport(),
|
||||
fileSystem: FakeFileSystemService = FakeFileSystemService(folder),
|
||||
diagnosticsIncluded: Boolean = false,
|
||||
) = SettingsViewModel(
|
||||
environment(),
|
||||
{ DeviceInfo("Device", "Model", "OS", "Wi-Fi", "80%") },
|
||||
@@ -662,6 +677,7 @@ class ViewModelsTest {
|
||||
platform = "Test",
|
||||
logReader = { "sample log line" },
|
||||
),
|
||||
diagnosticsIncluded = diagnosticsIncluded,
|
||||
)
|
||||
|
||||
private fun receivedTransfer(id: ULong, status: TransferStatus) = Transfer(
|
||||
|
||||
Reference in New Issue
Block a user