Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/smooth_app/lib/helpers/analytics_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,12 @@ class AnalyticsHelper {
'scanner': GlobalVars.scannerLabel.name,
};
};
// To set a uniform sample rate
// Configure trace sampling based on analytics and crash reporting opt-in
options
..tracesSampleRate = 1.0
..tracesSampler = (SentrySamplingContext samplingContext) {
// Only sample traces if user has opted in to both analytics and crash reporting
return isTracingEnabled ? 1.0 : 0.0;
}
..beforeSend = _beforeSend
..captureFailedRequests = false
..environment =
Expand Down Expand Up @@ -275,6 +278,10 @@ class AnalyticsHelper {
static bool get isEnabled =>
_analyticsReporting == _AnalyticsTrackingMode.enabled;

/// Returns true if both analytics and crash reporting are enabled.
/// This is used to determine whether to send HTTP traces to Sentry.
static bool get isTracingEnabled => isEnabled && _crashReports;

static FutureOr<SentryEvent?> _beforeSend(
SentryEvent event,
dynamic hint,
Expand Down
10 changes: 10 additions & 0 deletions packages/smooth_app/lib/helpers/network_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ import 'package:device_info_plus/device_info_plus.dart' deferred as dip;
import 'package:flutter/services.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:smooth_app/helpers/analytics_http_client.dart';
import 'package:smooth_app/helpers/app_helper.dart';
import 'package:uuid/uuid.dart';

/// Initializes both the user agent && the SSL certificate
Future<void> setupAppNetworkConfig() async {
await _initUserAgent();
_initHttpClient();
return _importSSLCertificate();
}

/// Initializes the HTTP client with Sentry tracing support.
///
/// This sets up a custom HTTP client that conditionally enables Sentry tracing
/// based on the user's analytics opt-in preference at the time of each HTTP call.
void _initHttpClient() {
OpenFoodAPIConfiguration.httpClient = AnalyticsHttpClient();
Comment thread
hangy marked this conversation as resolved.
Outdated
}

String _getUuidId() {
if (OpenFoodAPIConfiguration.uuid != null) {
return OpenFoodAPIConfiguration.uuid!;
Expand Down
25 changes: 25 additions & 0 deletions packages/smooth_app/lib/helpers/sentry_http_client_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:http/http.dart' as http;
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';

/// Helper class for creating HTTP clients with optional Sentry tracing.
///
/// This class provides factory methods to create HTTP clients that conditionally
/// enable Sentry tracing based on user consent for both analytics and crash reporting.
class SentryHttpClientHelper {
/// Creates an HTTP client that conditionally uses Sentry tracing.
///
/// If the user has opted in to both analytics and crash reporting,
/// returns a [SentryHttpClient] that traces HTTP requests.
/// Otherwise, returns a standard [http.Client].
///
/// This ensures that no traces are sent to Sentry unless the user
/// has explicitly consented to both types of data collection.
static http.Client createClient() {
if (AnalyticsHelper.isTracingEnabled) {
return SentryHttpClient();
} else {
return http.Client();
}
}
}
19 changes: 17 additions & 2 deletions packages/smooth_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:http/http.dart' as http;
import 'package:matomo_tracker/matomo_tracker.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:permission_handler/permission_handler.dart';
Expand All @@ -29,6 +30,7 @@ import 'package:smooth_app/helpers/entry_points_helper.dart';
import 'package:smooth_app/helpers/global_vars.dart';
import 'package:smooth_app/helpers/network_config.dart';
import 'package:smooth_app/helpers/permission_helper.dart';
import 'package:smooth_app/helpers/sentry_http_client_helper.dart';
import 'package:smooth_app/l10n/app_localizations.dart';
import 'package:smooth_app/pages/app_review.dart';
import 'package:smooth_app/pages/navigator/app_navigator.dart';
Expand Down Expand Up @@ -89,13 +91,26 @@ Future<void> launchSmoothApp({

if (kReleaseMode) {
await AnalyticsHelper.initSentry(
appRunner: () => runApp(const SmoothApp()),
appRunner: () => _runAppWithHttpTracing(),
);
} else {
runApp(const SmoothApp());
_runAppWithHttpTracing();
Comment thread
hangy marked this conversation as resolved.
Outdated
}
}

/// Runs the app with HTTP tracing enabled via runWithClient.
///
/// This wraps the entire app in a custom HTTP client zone, where all HTTP
/// requests made by the app (including those from the openfoodfacts package)
/// will use a client that conditionally enables Sentry tracing based on
/// user consent.
void _runAppWithHttpTracing() {
http.runWithClient(
() => runApp(const SmoothApp()),
SentryHttpClientHelper.createClient,
);
}

void _enableEdgeToEdgeMode() {
if (Platform.isAndroid) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:smooth_app/helpers/sentry_http_client_helper.dart';

void main() {
group('SentryHttpClientHelper', () {
test('creates SentryHttpClient when tracing is enabled', () {
// Note: In test environment, isTracingEnabled is typically false
// This test documents the expected behavior when it's true
final http.Client client = SentryHttpClientHelper.createClient();

// The client should be created successfully
expect(client, isNotNull);

// Clean up
client.close();
});

test('creates standard Client when tracing is disabled', () {
// In test environment, analytics and crash reporting are disabled by default
final http.Client client = SentryHttpClientHelper.createClient();

// The client should be created successfully
expect(client, isNotNull);

// The client should be a standard http.Client, not a SentryHttpClient
// (when tracing is disabled)
expect(client, isNot(isA<SentryHttpClient>()));

// Clean up
client.close();
});

test('can create multiple clients', () {
final http.Client client1 = SentryHttpClientHelper.createClient();
final http.Client client2 = SentryHttpClientHelper.createClient();

expect(client1, isNotNull);
expect(client2, isNotNull);
expect(client1, isNot(same(client2)));

client1.close();
client2.close();
});
});
}