mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Co-authored-by: npub1ft62tztwwm2x9xamk25smmuaj4sfckdkldksruf2x2jwqalffkrq0g7arr <4af4a5896e76d4629bbbb2a90def9d95609c59b6fb6d01f12a32a4e077e94d86@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1ft62tztwwm2x9xamk25smmuaj4sfckdkldksruf2x2jwqalffkrq0g7arr <4af4a5896e76d4629bbbb2a90def9d95609c59b6fb6d01f12a32a4e077e94d86@sprout-oss.stage.blox.sqprod.co>
136 lines
3.8 KiB
Dart
136 lines
3.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import 'sentry_config.dart';
|
|
|
|
const diagnosticsConsentPreferenceKey = 'buzz_crash_reporting_consent';
|
|
|
|
typedef DiagnosticsLog = void Function(String message);
|
|
|
|
abstract interface class CrashReporter {
|
|
Future<void> initialize(SentryConfig config);
|
|
Future<void> close();
|
|
}
|
|
|
|
class SentryCrashReporter implements CrashReporter {
|
|
const SentryCrashReporter();
|
|
|
|
@override
|
|
Future<void> initialize(SentryConfig config) {
|
|
return SentryFlutter.init(config.applyTo);
|
|
}
|
|
|
|
@override
|
|
Future<void> close() => Sentry.close();
|
|
}
|
|
|
|
class DiagnosticsController extends ChangeNotifier {
|
|
DiagnosticsController({
|
|
required SharedPreferences preferences,
|
|
required SentryConfig config,
|
|
required CrashReporter crashReporter,
|
|
DiagnosticsLog? log,
|
|
}) : _preferences = preferences,
|
|
_config = config,
|
|
_crashReporter = crashReporter,
|
|
_log = log ?? debugPrint,
|
|
_consentGranted =
|
|
preferences.getBool(diagnosticsConsentPreferenceKey) ?? false;
|
|
|
|
final SharedPreferences _preferences;
|
|
final SentryConfig _config;
|
|
final CrashReporter _crashReporter;
|
|
final DiagnosticsLog _log;
|
|
|
|
bool _consentGranted;
|
|
bool _initialized = false;
|
|
Future<void> _pendingOperation = Future.value();
|
|
|
|
bool get consentGranted => _consentGranted;
|
|
bool get isConfigured => _config.isConfigured;
|
|
|
|
/// Applies persisted consent before the app starts rendering.
|
|
Future<void> applyStartupConsent() => _serialize(_applyCurrentConsent);
|
|
|
|
/// Persists consent and applies it immediately.
|
|
Future<void> setConsent(bool granted) {
|
|
return _serialize(() async {
|
|
if (_consentGranted == granted) {
|
|
return;
|
|
}
|
|
|
|
if (granted && !_config.isConfigured) {
|
|
_log(
|
|
'Diagnostics unchanged: SENTRY_DSN is empty; consent not enabled.',
|
|
);
|
|
throw StateError('Crash reporting is unavailable in this build');
|
|
}
|
|
|
|
final previousConsent = _consentGranted;
|
|
_consentGranted = granted;
|
|
notifyListeners();
|
|
try {
|
|
final persisted = await _preferences.setBool(
|
|
diagnosticsConsentPreferenceKey,
|
|
granted,
|
|
);
|
|
if (!persisted) {
|
|
throw StateError('Failed to persist diagnostics consent');
|
|
}
|
|
await _applyCurrentConsent();
|
|
} on Object {
|
|
_consentGranted = previousConsent;
|
|
final rolledBack = await _preferences.setBool(
|
|
diagnosticsConsentPreferenceKey,
|
|
previousConsent,
|
|
);
|
|
notifyListeners();
|
|
if (!rolledBack) {
|
|
throw StateError('Failed to roll back diagnostics consent');
|
|
}
|
|
rethrow;
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _applyCurrentConsent() async {
|
|
if (!_consentGranted) {
|
|
if (_initialized) {
|
|
await _crashReporter.close();
|
|
_initialized = false;
|
|
_log('Diagnostics disabled: user consent is off; Sentry closed.');
|
|
} else {
|
|
_log(
|
|
'Diagnostics disabled: user consent is off; Sentry not initialized.',
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!_config.isConfigured) {
|
|
_log(
|
|
'Diagnostics disabled: SENTRY_DSN is empty; Sentry not initialized.',
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (_initialized) {
|
|
_log('Diagnostics already enabled: Sentry initialization skipped.');
|
|
return;
|
|
}
|
|
|
|
await _crashReporter.initialize(_config);
|
|
_initialized = true;
|
|
_log('Diagnostics enabled: Sentry initialized after user consent.');
|
|
}
|
|
|
|
Future<void> _serialize(Future<void> Function() operation) {
|
|
final result = _pendingOperation.then((_) => operation());
|
|
_pendingOperation = result.catchError((Object _) {});
|
|
return result;
|
|
}
|
|
}
|