TF-4136 Apply early exit to prevent string operation in log
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/utils/build_utils.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:core/utils/sentry/sentry_manager.dart';
|
||||
@@ -59,26 +61,33 @@ void _internalLog(
|
||||
? PlatformInfo.isWeb
|
||||
: BuildUtils.isDebugMode;
|
||||
|
||||
if (!shouldPrint) {
|
||||
final shouldSentry = _shouldReportToSentry(level);
|
||||
|
||||
if (!shouldPrint && !shouldSentry) {
|
||||
return;
|
||||
}
|
||||
|
||||
final rawMessage = _buildRawMessage(message, exception, extras, stackTrace);
|
||||
final formattedMessage = _formatMessage(level, rawMessage);
|
||||
|
||||
if (webConsoleEnabled && PlatformInfo.isWeb) {
|
||||
_printWebConsole(level, formattedMessage);
|
||||
} else {
|
||||
// ignore: avoid_print
|
||||
print('$appLogName $formattedMessage');
|
||||
if (shouldPrint) {
|
||||
final formattedMessage = _formatMessage(level, rawMessage);
|
||||
|
||||
if (webConsoleEnabled && PlatformInfo.isWeb) {
|
||||
_printWebConsole(level, formattedMessage);
|
||||
} else {
|
||||
// ignore: avoid_print
|
||||
print('$appLogName $formattedMessage');
|
||||
}
|
||||
}
|
||||
|
||||
if (_shouldReportToSentry(level)) {
|
||||
SentryManager.instance.captureException(
|
||||
exception ?? rawMessage,
|
||||
stackTrace: stackTrace,
|
||||
message: rawMessage,
|
||||
extras: extras,
|
||||
if (shouldSentry) {
|
||||
unawaited(
|
||||
SentryManager.instance.captureException(
|
||||
exception ?? rawMessage,
|
||||
stackTrace: stackTrace,
|
||||
message: rawMessage,
|
||||
extras: extras,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,71 +2,159 @@ import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class ApplicationManager {
|
||||
ApplicationManager._internal();
|
||||
|
||||
static final ApplicationManager _instance = ApplicationManager._internal();
|
||||
|
||||
factory ApplicationManager() => _instance;
|
||||
|
||||
ApplicationManager._internal();
|
||||
|
||||
// Allow overriding in unit tests
|
||||
@visibleForTesting
|
||||
static DeviceInfoPlugin? debugDeviceInfoOverride;
|
||||
|
||||
DeviceInfoPlugin get _deviceInfoPlugin =>
|
||||
DeviceInfoPlugin get _deviceInfo =>
|
||||
debugDeviceInfoOverride ?? DeviceInfoPlugin();
|
||||
|
||||
Future<PackageInfo> getPackageInfo() async {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
log('ApplicationManager::getPackageInto: $packageInfo');
|
||||
return packageInfo;
|
||||
PackageInfo? _packageInfoCache;
|
||||
String? _versionCache;
|
||||
String? _cachedWebUserAgent;
|
||||
String? _cachedMobileUserAgent;
|
||||
bool _isMobileUserAgentInitialized = false;
|
||||
|
||||
@visibleForTesting
|
||||
void clearCache() {
|
||||
_packageInfoCache = null;
|
||||
_versionCache = null;
|
||||
_cachedWebUserAgent = null;
|
||||
_cachedMobileUserAgent = null;
|
||||
}
|
||||
|
||||
Future<String> getVersion() async {
|
||||
Future<PackageInfo> getPackageInfo() async {
|
||||
if (_packageInfoCache != null) {
|
||||
return _packageInfoCache!;
|
||||
}
|
||||
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
_packageInfoCache = info;
|
||||
|
||||
log('ApplicationManager:getPackageInfo -> cached: $info');
|
||||
return info;
|
||||
}
|
||||
|
||||
Future<String> getAppVersion() async {
|
||||
if (_versionCache != null) {
|
||||
return _versionCache!;
|
||||
}
|
||||
|
||||
try {
|
||||
final version = (await getPackageInfo()).version;
|
||||
log('ApplicationManager::getVersion: $version');
|
||||
_versionCache = version;
|
||||
|
||||
log('ApplicationManager:getAppVersion -> cached: $version');
|
||||
return version;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getUserAgent() async {
|
||||
try {
|
||||
String userAgent = '';
|
||||
if (PlatformInfo.isWeb) {
|
||||
final webBrowserInfo = await _deviceInfoPlugin.webBrowserInfo;
|
||||
userAgent = webBrowserInfo.userAgent ?? '';
|
||||
} else if (PlatformInfo.isMobile) {
|
||||
userAgent = FkUserAgent.userAgent ?? '';
|
||||
}
|
||||
log('ApplicationManager::getUserAgent: $userAgent');
|
||||
return userAgent;
|
||||
} catch(e) {
|
||||
logWarning('ApplicationManager::getUserAgent: Exception: $e');
|
||||
logWarning('ApplicationManager:getAppVersion failedd, Exception = $e');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initUserAgent() async {
|
||||
if (PlatformInfo.isMobile) {
|
||||
if (!PlatformInfo.isMobile) return;
|
||||
|
||||
try {
|
||||
await FkUserAgent.init();
|
||||
_isMobileUserAgentInitialized = true;
|
||||
_cachedMobileUserAgent = null;
|
||||
|
||||
log('ApplicationManager:initUserAgent -> initialized');
|
||||
} catch (e) {
|
||||
logWarning('ApplicationManager:initUserAgent failed, Exception $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> releaseUserAgent() async {
|
||||
if (PlatformInfo.isMobile) {
|
||||
if (!PlatformInfo.isMobile) return;
|
||||
|
||||
try {
|
||||
FkUserAgent.release();
|
||||
_isMobileUserAgentInitialized = false;
|
||||
_cachedMobileUserAgent = null;
|
||||
|
||||
log('ApplicationManager:releaseUserAgent -> released & cache cleared');
|
||||
} catch (e) {
|
||||
logWarning('ApplicationManager:releaseUserAgent failed, Exception $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> generateApplicationUserAgent() async {
|
||||
final userAgent = await getUserAgent();
|
||||
final version = await getVersion();
|
||||
return 'Twake-Mail/$version $userAgent';
|
||||
Future<String> getUserAgent() async {
|
||||
try {
|
||||
if (PlatformInfo.isWeb) {
|
||||
return _getWebUserAgent();
|
||||
}
|
||||
|
||||
if (PlatformInfo.isMobile) {
|
||||
return _getMobileUserAgent();
|
||||
}
|
||||
|
||||
return '';
|
||||
} catch (e) {
|
||||
logWarning('ApplicationManager:getUserAgent failed');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _getWebUserAgent() async {
|
||||
if (_cachedWebUserAgent != null) {
|
||||
return _cachedWebUserAgent!;
|
||||
}
|
||||
|
||||
WebBrowserInfo webInfo;
|
||||
|
||||
try {
|
||||
webInfo = await _deviceInfo.webBrowserInfo;
|
||||
} catch (e) {
|
||||
logWarning('ApplicationManager:_getWebUserAgent failed, Exception $e');
|
||||
return '';
|
||||
}
|
||||
|
||||
final userAgent = webInfo.userAgent ?? '';
|
||||
_cachedWebUserAgent = userAgent;
|
||||
|
||||
log('ApplicationManager:_getWebUserAgent -> cached');
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
Future<String> _getMobileUserAgent() async {
|
||||
if (!_isMobileUserAgentInitialized) {
|
||||
logWarning(
|
||||
'ApplicationManager:_getMobileUserAgent called before initUserAgent',
|
||||
);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (_cachedMobileUserAgent != null) {
|
||||
return _cachedMobileUserAgent!;
|
||||
}
|
||||
|
||||
final userAgent = FkUserAgent.userAgent;
|
||||
|
||||
if (userAgent == null || userAgent.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
_cachedMobileUserAgent = userAgent;
|
||||
log('ApplicationManager:_getMobileUserAgent -> cached');
|
||||
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
Future<String> generateApplicationUserAgent() async {
|
||||
final version = await getAppVersion();
|
||||
final userAgent = await getUserAgent();
|
||||
|
||||
return 'Twake-Mail/$version $userAgent'.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class SentryConfig {
|
||||
throw Exception('Sentry configuration is missing');
|
||||
}
|
||||
|
||||
final appVersion = await ApplicationManager().getVersion();
|
||||
final appVersion = await ApplicationManager().getAppVersion();
|
||||
|
||||
return SentryConfig(
|
||||
dsn: sentryDSN,
|
||||
|
||||
@@ -22,6 +22,7 @@ void main() {
|
||||
ApplicationManager.debugDeviceInfoOverride = mockDeviceInfoPlugin;
|
||||
|
||||
applicationManager = ApplicationManager();
|
||||
applicationManager.clearCache();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@@ -38,97 +39,170 @@ void main() {
|
||||
FkUserAgent.release();
|
||||
});
|
||||
|
||||
group('ApplicationManager::getUserAgent test', () {
|
||||
group('ApplicationManager::getUserAgent', () {
|
||||
test(
|
||||
'WHEN platform is Web THEN getUserAgent should return user agent for web',
|
||||
() async {
|
||||
const webUserAgent = 'User-Agent-Twake-Mail-Web';
|
||||
'WHEN platform is Web THEN return web userAgent AND cache it',
|
||||
() async {
|
||||
const webUserAgent = 'User-Agent-Twake-Mail-Web';
|
||||
|
||||
PlatformInfo.isTestingForWeb = true;
|
||||
PlatformInfo.isTestingForWeb = true;
|
||||
|
||||
when(mockDeviceInfoPlugin.webBrowserInfo).thenAnswer(
|
||||
(_) async => WebBrowserInfo(
|
||||
userAgent: webUserAgent,
|
||||
appCodeName: '',
|
||||
appName: '',
|
||||
appVersion: '',
|
||||
deviceMemory: null,
|
||||
language: '',
|
||||
languages: [],
|
||||
platform: '',
|
||||
product: '',
|
||||
productSub: '',
|
||||
vendor: '',
|
||||
vendorSub: '',
|
||||
maxTouchPoints: null,
|
||||
hardwareConcurrency: null,
|
||||
),
|
||||
);
|
||||
when(mockDeviceInfoPlugin.webBrowserInfo).thenAnswer(
|
||||
(_) async => WebBrowserInfo(
|
||||
userAgent: webUserAgent,
|
||||
appCodeName: '',
|
||||
appName: '',
|
||||
appVersion: '',
|
||||
deviceMemory: null,
|
||||
language: '',
|
||||
languages: const [],
|
||||
platform: '',
|
||||
product: '',
|
||||
productSub: '',
|
||||
vendor: '',
|
||||
vendorSub: '',
|
||||
maxTouchPoints: null,
|
||||
hardwareConcurrency: null,
|
||||
),
|
||||
);
|
||||
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
final firstCall = await applicationManager.getUserAgent();
|
||||
final secondCall = await applicationManager.getUserAgent();
|
||||
|
||||
expect(userAgent, webUserAgent);
|
||||
});
|
||||
expect(firstCall, webUserAgent);
|
||||
expect(secondCall, webUserAgent);
|
||||
|
||||
// platform channel must be called only once (cached)
|
||||
verify(mockDeviceInfoPlugin.webBrowserInfo).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'WHEN platform is Android THEN getUserAgent should return user agent for Android',
|
||||
() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
'WHEN platform is Android THEN return mobile userAgent AND cache it',
|
||||
() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
const androidUserAgent = 'User-Agent-Twake-Mail-Android';
|
||||
const androidUserAgent = 'User-Agent-Twake-Mail-Android';
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
(message) async {
|
||||
if (message.method == 'getProperties') {
|
||||
return {'userAgent': androidUserAgent};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
(message) async {
|
||||
if (message.method == 'getProperties') {
|
||||
return {'userAgent': androidUserAgent};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
await FkUserAgent.init();
|
||||
await applicationManager.initUserAgent();
|
||||
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
final firstCall = await applicationManager.getUserAgent();
|
||||
final secondCall = await applicationManager.getUserAgent();
|
||||
|
||||
expect(userAgent, androidUserAgent);
|
||||
});
|
||||
expect(firstCall, androidUserAgent);
|
||||
expect(secondCall, androidUserAgent);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'WHEN platform is Web AND mockDeviceInfoPlugin.webBrowserInfo throws exception THEN return empty string',
|
||||
() async {
|
||||
PlatformInfo.isTestingForWeb = true;
|
||||
'WHEN platform is Web AND webBrowserInfo throws exception THEN return empty string',
|
||||
() async {
|
||||
PlatformInfo.isTestingForWeb = true;
|
||||
|
||||
when(mockDeviceInfoPlugin.webBrowserInfo)
|
||||
.thenThrow(Exception('Failed to get web browser info'));
|
||||
when(mockDeviceInfoPlugin.webBrowserInfo).thenAnswer(
|
||||
(_) => Future<WebBrowserInfo>.error(
|
||||
Exception('Failed to get web browser info'),
|
||||
),
|
||||
);
|
||||
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
|
||||
expect(userAgent, '');
|
||||
});
|
||||
expect(userAgent, '');
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'WHEN platform is Android AND FkUserAgent.userAgent empty THEN return empty string',
|
||||
() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
'WHEN platform is Android AND FkUserAgent returns empty THEN return empty string',
|
||||
() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
(message) async {
|
||||
if (message.method == 'getProperties') {
|
||||
return {};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
(message) async {
|
||||
if (message.method == 'getProperties') {
|
||||
return {};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
await FkUserAgent.init();
|
||||
await applicationManager.initUserAgent();
|
||||
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
|
||||
expect(userAgent, '');
|
||||
});
|
||||
expect(userAgent, '');
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'WHEN mobile userAgent is released THEN getUserAgent returns empty string',
|
||||
() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
const androidUserAgent = 'User-Agent-Twake-Mail-Android';
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
(message) async {
|
||||
if (message.method == 'getProperties') {
|
||||
return {'userAgent': androidUserAgent};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
await applicationManager.initUserAgent();
|
||||
|
||||
final firstCall = await applicationManager.getUserAgent();
|
||||
await applicationManager.releaseUserAgent();
|
||||
final secondCall = await applicationManager.getUserAgent();
|
||||
|
||||
expect(firstCall, androidUserAgent);
|
||||
expect(secondCall, '');
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'WHEN mobile userAgent is released AND re-initialized THEN cache rebuilt',
|
||||
() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
const androidUserAgent = 'User-Agent-Twake-Mail-Android';
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
(message) async {
|
||||
if (message.method == 'getProperties') {
|
||||
return {'userAgent': androidUserAgent};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
await applicationManager.initUserAgent();
|
||||
final firstCall = await applicationManager.getUserAgent();
|
||||
|
||||
await applicationManager.releaseUserAgent();
|
||||
await applicationManager.initUserAgent();
|
||||
final secondCall = await applicationManager.getUserAgent();
|
||||
|
||||
expect(firstCall, androidUserAgent);
|
||||
expect(secondCall, androidUserAgent);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user