diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index a9ac5b3381..5493fc2840 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -155,7 +155,7 @@ SPEC CHECKSUMS: flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04 flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d - fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c + fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461 image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 @@ -180,4 +180,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index 03d06bd140..02d10c7674 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -181,10 +181,21 @@ class AuthenticationNotifier extends StateNotifier { UserResponseDto? userResponseDto; try { userResponseDto = await _apiService.userApi.getMyUserInfo(); - } on ApiException catch (e) { - if (e.innerException is SocketException) { + } on ApiException catch (error, stackTrace) { + _log.severe( + "Error getting user information from the server [API EXCEPTION]", + error, + stackTrace, + ); + if (error.innerException is SocketException) { state = state.copyWith(isAuthenticated: true); } + } catch (error, stackTrace) { + _log.severe( + "Error getting user information from the server [CATCH ALL]", + error, + stackTrace, + ); } if (userResponseDto != null) { diff --git a/mobile/lib/modules/login/ui/login_form.dart b/mobile/lib/modules/login/ui/login_form.dart index 5c7049a4ea..284e14276a 100644 --- a/mobile/lib/modules/login/ui/login_form.dart +++ b/mobile/lib/modules/login/ui/login_form.dart @@ -3,6 +3,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/login/providers/oauth.provider.dart'; @@ -86,6 +87,7 @@ class LoginForm extends HookConsumerWidget { context: context, msg: e.message ?? 'login_form_api_exception'.tr(), toastType: ToastType.error, + gravity: ToastGravity.TOP, ); isOauthEnable.value = false; isPasswordLoginEnable.value = true; @@ -96,6 +98,7 @@ class LoginForm extends HookConsumerWidget { context: context, msg: 'login_form_handshake_exception'.tr(), toastType: ToastType.error, + gravity: ToastGravity.TOP, ); isOauthEnable.value = false; isPasswordLoginEnable.value = true; @@ -106,6 +109,7 @@ class LoginForm extends HookConsumerWidget { context: context, msg: 'login_form_server_error'.tr(), toastType: ToastType.error, + gravity: ToastGravity.TOP, ); isOauthEnable.value = false; isPasswordLoginEnable.value = true; @@ -174,6 +178,7 @@ class LoginForm extends HookConsumerWidget { context: context, msg: "login_form_failed_login".tr(), toastType: ToastType.error, + gravity: ToastGravity.TOP, ); } } finally { @@ -197,6 +202,7 @@ class LoginForm extends HookConsumerWidget { context: context, msg: "login_form_failed_get_oauth_server_config".tr(), toastType: ToastType.error, + gravity: ToastGravity.TOP, ); isLoading.value = false; return; @@ -225,6 +231,7 @@ class LoginForm extends HookConsumerWidget { context: context, msg: "login_form_failed_login".tr(), toastType: ToastType.error, + gravity: ToastGravity.TOP, ); } } @@ -235,6 +242,7 @@ class LoginForm extends HookConsumerWidget { context: context, msg: "login_form_failed_get_oauth_server_disable".tr(), toastType: ToastType.info, + gravity: ToastGravity.TOP, ); isLoading.value = false; return; diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index bebed7fc27..9593f03555 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/shared/services/server_info.service.dart'; import 'package:immich_mobile/shared/models/server_info/server_config.model.dart'; import 'package:immich_mobile/shared/models/server_info/server_features.model.dart'; import 'package:immich_mobile/shared/models/server_info/server_version.model.dart'; +import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; class ServerInfoNotifier extends StateNotifier { @@ -47,6 +48,7 @@ class ServerInfoNotifier extends StateNotifier { ); final ServerInfoService _serverInfoService; + final _log = Logger("ServerInfoNotifier"); Future getServerInfo() async { await getServerVersion(); @@ -55,17 +57,25 @@ class ServerInfoNotifier extends StateNotifier { } getServerVersion() async { - final serverVersion = await _serverInfoService.getServerVersion(); + try { + final serverVersion = await _serverInfoService.getServerVersion(); - if (serverVersion == null) { + if (serverVersion == null) { + state = state.copyWith( + isVersionMismatch: true, + versionMismatchErrorMessage: "common_server_error".tr(), + ); + return; + } + + await _checkServerVersionMismatch(serverVersion); + } catch (e, stackTrace) { + _log.severe("Failed to get server version", e, stackTrace); state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "common_server_error".tr(), ); return; } - - await _checkServerVersionMismatch(serverVersion); } _checkServerVersionMismatch(ServerVersion serverVersion) async { diff --git a/mobile/lib/shared/services/api.service.dart b/mobile/lib/shared/services/api.service.dart index 9e44136fe7..e0dd3b6b98 100644 --- a/mobile/lib/shared/services/api.service.dart +++ b/mobile/lib/shared/services/api.service.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/utils/url_helper.dart'; +import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; import 'package:http/http.dart'; @@ -34,6 +35,7 @@ class ApiService { } } String? _accessToken; + final _log = Logger("ApiService"); setEndpoint(String endpoint) { _apiClient = ApiClient(basePath: endpoint); @@ -95,14 +97,17 @@ class ApiService { serverUrl += '/api'; } - // Throw Socket or Timeout exceptions, - // we do not care if the endpoints hits an HTTP error try { - await client - .get( - Uri.parse(serverUrl), - ) + final response = await client + .get(Uri.parse("$serverUrl/server-info/ping")) .timeout(const Duration(seconds: 5)); + + if (response.statusCode != 200) { + _log.severe( + "Server Gateway Error: ${response.body} - Cannot communicate to the server", + ); + return false; + } } on TimeoutException catch (_) { return false; } on SocketException catch (_) { diff --git a/mobile/lib/shared/views/splash_screen.dart b/mobile/lib/shared/views/splash_screen.dart index 47b550f9d0..62f1dbe8f6 100644 --- a/mobile/lib/shared/views/splash_screen.dart +++ b/mobile/lib/shared/views/splash_screen.dart @@ -25,20 +25,30 @@ class SplashScreenPage extends HookConsumerWidget { void performLoggingIn() async { bool isSuccess = false; bool deviceIsOffline = false; + if (accessToken != null && serverUrl != null) { try { // Resolve API server endpoint from user provided serverUrl await apiService.resolveAndSetEndpoint(serverUrl); - } on ApiException catch (e) { + } on ApiException catch (error, stackTrace) { + log.severe( + "Failed to resolve endpoint [ApiException]", + error, + stackTrace, + ); // okay, try to continue anyway if offline - if (e.code == 503) { + if (error.code == 503) { deviceIsOffline = true; - log.fine("Device seems to be offline upon launch"); + log.warning("Device seems to be offline upon launch"); } else { - log.severe("Failed to resolve endpoint", e); + log.severe("Failed to resolve endpoint", error); } - } catch (e) { - log.severe("Failed to resolve endpoint", e); + } catch (error, stackTrace) { + log.severe( + "Failed to resolve endpoint [Catch All]", + error, + stackTrace, + ); } try { @@ -50,15 +60,11 @@ class SplashScreenPage extends HookConsumerWidget { offlineLogin: deviceIsOffline, ); } catch (error, stackTrace) { - ref.read(authenticationProvider.notifier).logout(); - log.severe( 'Cannot set success login info', error, stackTrace, ); - - context.pushRoute(const LoginRoute()); } } @@ -76,6 +82,11 @@ class SplashScreenPage extends HookConsumerWidget { } context.replaceRoute(const TabControllerRoute()); } else { + log.severe( + 'Unable to login through offline or online methods - logging out completely', + ); + + ref.read(authenticationProvider.notifier).logout(); // User was unable to login through either offline or online methods context.replaceRoute(const LoginRoute()); }