diff --git a/.gitignore b/.gitignore index da53ba0b..c8c53d7a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ .history .svn/ +# Sentry API key +lib/dsn.dart + # App signing key android/key.properties diff --git a/lib/inventree/sentry.dart b/lib/inventree/sentry.dart new file mode 100644 index 00000000..f0b63793 --- /dev/null +++ b/lib/inventree/sentry.dart @@ -0,0 +1,141 @@ +import 'dart:io'; + +import 'package:device_info/device_info.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:package_info/package_info.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:one_context/one_context.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:InvenTree/api.dart'; + +Future> getDeviceInfo() async { + + // Extract device information + final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + + Map device_info = {}; + + // Extract some platform information + if (Platform.isIOS) { + final iosDeviceInfo = await deviceInfo.iosInfo; + + device_info = { + 'name': iosDeviceInfo.name, + 'model': iosDeviceInfo.model, + 'systemName': iosDeviceInfo.systemName, + 'systemVersion': iosDeviceInfo.systemVersion, + 'localizedModel': iosDeviceInfo.localizedModel, + 'utsname': iosDeviceInfo.utsname.sysname, + 'identifierForVendor': iosDeviceInfo.identifierForVendor, + 'isPhysicalDevice': iosDeviceInfo.isPhysicalDevice, + }; + + } else if (Platform.isAndroid) { + final androidDeviceInfo = await deviceInfo.androidInfo; + + device_info = { + 'type': androidDeviceInfo.type, + 'model': androidDeviceInfo.model, + 'device': androidDeviceInfo.device, + 'id': androidDeviceInfo.id, + 'androidId': androidDeviceInfo.androidId, + 'brand': androidDeviceInfo.brand, + 'display': androidDeviceInfo.display, + 'hardware': androidDeviceInfo.hardware, + 'manufacturer': androidDeviceInfo.manufacturer, + 'product': androidDeviceInfo.product, + 'version': androidDeviceInfo.version.release, + 'supported32BitAbis': androidDeviceInfo.supported32BitAbis, + 'supported64BitAbis': androidDeviceInfo.supported64BitAbis, + 'supportedAbis': androidDeviceInfo.supportedAbis, + 'isPhysicalDevice': androidDeviceInfo.isPhysicalDevice, + }; + } + + return device_info; +} + + +Map getServerInfo() => { + "version": InvenTreeAPI().version, +}; + + +Future> getAppInfo() async { + // Add app info + final package_info = await PackageInfo.fromPlatform(); + + return { + "name": package_info.appName, + "build": package_info.buildNumber, + "version": package_info.version, + "package": package_info.packageName, + }; +} + + +bool isInDebugMode() { + bool inDebugMode = false; + + assert(inDebugMode = true); + + return inDebugMode; +} + +Future sentryReportError(dynamic error, dynamic stackTrace) async { + + print('Intercepted error: $error'); + print(stackTrace); + + // Errors thrown in development mode are unlikely to be interesting. You can + // check if you are running in dev mode using an assertion and omit sending + // the report. + if (isInDebugMode()) { + + print('In dev mode. Not sending report to Sentry.io.'); + return; + } + + final server_info = getServerInfo(); + final app_info = await getAppInfo(); + final device_info = await getDeviceInfo(); + + Sentry.configureScope((scope) { + scope.setExtra("server", server_info); + scope.setExtra("app", app_info); + scope.setExtra("device", device_info); + }); + + Sentry.captureException(error, stackTrace: stackTrace).catchError((error) { + print("Error uploading information to Sentry.io:"); + print(error); + }).then((response) { + print("Uploaded information to Sentry.io : ${response.toString()}"); + }); +} + + +Future sentryReportMessage(String message) async { + + final server_info = getServerInfo(); + final app_info = await getAppInfo(); + final device_info = await getDeviceInfo(); + + print("Sending user message to Sentry"); + + Sentry.configureScope((scope) { + scope.setExtra("server", server_info); + scope.setExtra("app", app_info); + scope.setExtra("device", device_info); + }); + + final sentryId = await Sentry.captureMessage(message).catchError((error) { + print("Error uploading sentry messages..."); + print(error); + return null; + }); + + return sentryId != null; +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index e647af8f..c27bef8c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,7 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:InvenTree/inventree/sentry.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -6,11 +10,42 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:one_context/one_context.dart'; +import 'dsn.dart'; + +import 'package:sentry_flutter/sentry_flutter.dart'; + + void main() async { + + await Sentry.init((options) { + options.dsn = SENTRY_DSN_KEY; + }, + //appRunner: () => runApp(InvenTreeApp()) + ); + + await runZonedGuarded>(() async { + WidgetsFlutterBinding.ensureInitialized(); + // This captures errors reported by the Flutter framework. + FlutterError.onError = (FlutterErrorDetails details) async { + if (isInDebugMode()) { + // In development mode simply print to console. + FlutterError.dumpErrorToConsole(details); + } else { + // In production mode report to the application zone to report to + // Sentry. + Zone.current.handleUncaughtError(details.exception, details.stack); + } + }; + runApp(InvenTreeApp()); + + }, (Object error, StackTrace stackTrace) { + sentryReportError(error, stackTrace); + }); + } class InvenTreeApp extends StatelessWidget { diff --git a/pubspec.lock b/pubspec.lock index 9beaf37d..95d4f0dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -427,6 +427,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.4.9" + sentry: + dependency: transitive + description: + name: sentry + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.5" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.5" shared_preferences: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dbd03ad5..fe823e78 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: device_info: ^1.0.0 # Information about the device font_awesome_flutter: ^8.8.1 # FontAwesome icon set flutter_speed_dial: ^1.2.5 # FAB menu elements + sentry_flutter: ^4.0.4 # Error reporting flutter_typeahead: ^1.8.1 # Auto-complete input field image_picker: ^0.6.6 # Select or take photos url_launcher: ^5.7.10 # Open link in system browser