mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	fix(mobile): mobile logging out randomly (#11431)
* fix(mobile): refactor splash screen to not require online connection * chore: bump flutter sdk path for vscode * refactor: authentication provider always try network calls and only fail if 401 or no local user * lint * fix: revert change to lookup serverendpoint from store the isar store implementation is very broken * fix: clear serverUrl and serverEndpoint on logout, and await logout call * refactor: remove unneeded extra conditions in splash screen useEffect * revert change to remove serverEndpoint on logging out * pr feedback --------- Co-authored-by: Zack Pollard <zackpollard@ymail.com>
This commit is contained in:
		
							parent
							
								
									21d3f248da
								
							
						
					
					
						commit
						17c3e8e8bf
					
				
							
								
								
									
										2
									
								
								mobile/.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								mobile/.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "dart.flutterSdkPath": ".fvm/versions/3.22.1",
 | 
			
		||||
  "dart.flutterSdkPath": ".fvm/versions/3.22.3",
 | 
			
		||||
  "search.exclude": {
 | 
			
		||||
    "**/.fvm": true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,6 @@ import 'package:immich_mobile/routing/router.dart';
 | 
			
		||||
import 'package:immich_mobile/entities/store.entity.dart';
 | 
			
		||||
import 'package:immich_mobile/providers/api.provider.dart';
 | 
			
		||||
import 'package:logging/logging.dart';
 | 
			
		||||
import 'package:openapi/api.dart';
 | 
			
		||||
 | 
			
		||||
@RoutePage()
 | 
			
		||||
class SplashScreenPage extends HookConsumerWidget {
 | 
			
		||||
@ -19,45 +18,22 @@ class SplashScreenPage extends HookConsumerWidget {
 | 
			
		||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
			
		||||
    final apiService = ref.watch(apiServiceProvider);
 | 
			
		||||
    final serverUrl = Store.tryGet(StoreKey.serverUrl);
 | 
			
		||||
    final endpoint = Store.tryGet(StoreKey.serverEndpoint);
 | 
			
		||||
    final accessToken = Store.tryGet(StoreKey.accessToken);
 | 
			
		||||
    final log = Logger("SplashScreenPage");
 | 
			
		||||
 | 
			
		||||
    void performLoggingIn() async {
 | 
			
		||||
      bool isSuccess = false;
 | 
			
		||||
      bool deviceIsOffline = false;
 | 
			
		||||
      bool isAuthSuccess = false;
 | 
			
		||||
 | 
			
		||||
      if (accessToken != null && serverUrl != null) {
 | 
			
		||||
        try {
 | 
			
		||||
          // Resolve API server endpoint from user provided serverUrl
 | 
			
		||||
          await apiService.resolveAndSetEndpoint(serverUrl);
 | 
			
		||||
        } on ApiException catch (error, stackTrace) {
 | 
			
		||||
          log.severe(
 | 
			
		||||
            "Failed to resolve endpoint [ApiException]",
 | 
			
		||||
            error,
 | 
			
		||||
            stackTrace,
 | 
			
		||||
          );
 | 
			
		||||
          // okay, try to continue anyway if offline
 | 
			
		||||
          if (error.code == 503) {
 | 
			
		||||
            deviceIsOffline = true;
 | 
			
		||||
            log.warning("Device seems to be offline upon launch");
 | 
			
		||||
          } else {
 | 
			
		||||
            log.severe("Failed to resolve endpoint", error);
 | 
			
		||||
          }
 | 
			
		||||
        } catch (error, stackTrace) {
 | 
			
		||||
          log.severe(
 | 
			
		||||
            "Failed to resolve endpoint [Catch All]",
 | 
			
		||||
            error,
 | 
			
		||||
            stackTrace,
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      if (accessToken != null && serverUrl != null && endpoint != null) {
 | 
			
		||||
        apiService.setEndpoint(endpoint);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
          isSuccess = await ref
 | 
			
		||||
          isAuthSuccess = await ref
 | 
			
		||||
              .read(authenticationProvider.notifier)
 | 
			
		||||
              .setSuccessLoginInfo(
 | 
			
		||||
                accessToken: accessToken,
 | 
			
		||||
                serverUrl: serverUrl,
 | 
			
		||||
                offlineLogin: deviceIsOffline,
 | 
			
		||||
              );
 | 
			
		||||
        } catch (error, stackTrace) {
 | 
			
		||||
          log.severe(
 | 
			
		||||
@ -66,39 +42,35 @@ class SplashScreenPage extends HookConsumerWidget {
 | 
			
		||||
            stackTrace,
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        isAuthSuccess = false;
 | 
			
		||||
        log.severe(
 | 
			
		||||
          'Missing authentication, server, or endpoint info from the local store',
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!isAuthSuccess) {
 | 
			
		||||
        log.severe(
 | 
			
		||||
          'Unable to login using offline or online methods - Logging out completely',
 | 
			
		||||
        );
 | 
			
		||||
        ref.read(authenticationProvider.notifier).logout();
 | 
			
		||||
        context.replaceRoute(const LoginRoute());
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // If the device is offline and there is a currentUser stored locallly
 | 
			
		||||
      // Proceed into the app
 | 
			
		||||
      if (deviceIsOffline && Store.tryGet(StoreKey.currentUser) != null) {
 | 
			
		||||
      context.replaceRoute(const TabControllerRoute());
 | 
			
		||||
      } else if (isSuccess) {
 | 
			
		||||
        // If device was able to login through the internet successfully
 | 
			
		||||
 | 
			
		||||
      final hasPermission =
 | 
			
		||||
          await ref.read(galleryPermissionNotifier.notifier).hasPermission;
 | 
			
		||||
      if (hasPermission) {
 | 
			
		||||
        // Resume backup (if enable) then navigate
 | 
			
		||||
        ref.watch(backupProvider.notifier).resumeBackup();
 | 
			
		||||
      }
 | 
			
		||||
        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());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    useEffect(
 | 
			
		||||
      () {
 | 
			
		||||
        if (serverUrl != null && accessToken != null) {
 | 
			
		||||
        performLoggingIn();
 | 
			
		||||
        } else {
 | 
			
		||||
          context.replaceRoute(const LoginRoute());
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
      },
 | 
			
		||||
      [],
 | 
			
		||||
 | 
			
		||||
@ -101,7 +101,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 | 
			
		||||
    try {
 | 
			
		||||
      String? userEmail = Store.tryGet(StoreKey.currentUser)?.email;
 | 
			
		||||
 | 
			
		||||
      _apiService.authenticationApi
 | 
			
		||||
      await _apiService.authenticationApi
 | 
			
		||||
          .logout()
 | 
			
		||||
          .then((_) => log.info("Logout was successful for $userEmail"))
 | 
			
		||||
          .onError(
 | 
			
		||||
@ -156,7 +156,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 | 
			
		||||
  Future<bool> setSuccessLoginInfo({
 | 
			
		||||
    required String accessToken,
 | 
			
		||||
    required String serverUrl,
 | 
			
		||||
    bool offlineLogin = false,
 | 
			
		||||
  }) async {
 | 
			
		||||
    _apiService.setAccessToken(accessToken);
 | 
			
		||||
 | 
			
		||||
@ -165,31 +164,26 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 | 
			
		||||
        Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
 | 
			
		||||
 | 
			
		||||
    bool shouldChangePassword = false;
 | 
			
		||||
    User? user;
 | 
			
		||||
    User? user = Store.tryGet(StoreKey.currentUser);
 | 
			
		||||
 | 
			
		||||
    bool retResult = false;
 | 
			
		||||
    User? offlineUser = Store.tryGet(StoreKey.currentUser);
 | 
			
		||||
 | 
			
		||||
    // If the user is offline and there is a user saved on the device,
 | 
			
		||||
    // if not try an online login
 | 
			
		||||
    if (offlineLogin && offlineUser != null) {
 | 
			
		||||
      user = offlineUser;
 | 
			
		||||
      retResult = false;
 | 
			
		||||
    } else {
 | 
			
		||||
      UserAdminResponseDto? userResponseDto;
 | 
			
		||||
    UserAdminResponseDto? userResponse;
 | 
			
		||||
    UserPreferencesResponseDto? userPreferences;
 | 
			
		||||
    try {
 | 
			
		||||
        userResponseDto = await _apiService.usersApi.getMyUser();
 | 
			
		||||
        userPreferences = await _apiService.usersApi.getMyPreferences();
 | 
			
		||||
      final responses = await Future.wait([
 | 
			
		||||
        _apiService.usersApi.getMyUser(),
 | 
			
		||||
        _apiService.usersApi.getMyPreferences(),
 | 
			
		||||
      ]);
 | 
			
		||||
      userResponse = responses[0] as UserAdminResponseDto;
 | 
			
		||||
      userPreferences = responses[1] as UserPreferencesResponseDto;
 | 
			
		||||
    } on ApiException catch (error, stackTrace) {
 | 
			
		||||
      if (error.code == 401) {
 | 
			
		||||
        _log.severe("Unauthorized access, token likely expired. Logging out.");
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      _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]",
 | 
			
		||||
@ -198,24 +192,28 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      if (userResponseDto != null) {
 | 
			
		||||
    // If the user information is successfully retrieved, update the store
 | 
			
		||||
    // Due to the flow of the code, this will always happen on first login
 | 
			
		||||
    if (userResponse != null) {
 | 
			
		||||
      Store.put(StoreKey.deviceId, deviceId);
 | 
			
		||||
      Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
 | 
			
		||||
      Store.put(
 | 
			
		||||
        StoreKey.currentUser,
 | 
			
		||||
          User.fromUserDto(userResponseDto, userPreferences),
 | 
			
		||||
        User.fromUserDto(userResponse, userPreferences),
 | 
			
		||||
      );
 | 
			
		||||
      Store.put(StoreKey.serverUrl, serverUrl);
 | 
			
		||||
      Store.put(StoreKey.accessToken, accessToken);
 | 
			
		||||
 | 
			
		||||
        shouldChangePassword = userResponseDto.shouldChangePassword;
 | 
			
		||||
        user = User.fromUserDto(userResponseDto, userPreferences);
 | 
			
		||||
 | 
			
		||||
        retResult = true;
 | 
			
		||||
      shouldChangePassword = userResponse.shouldChangePassword;
 | 
			
		||||
      user = User.fromUserDto(userResponse, userPreferences);
 | 
			
		||||
    } else {
 | 
			
		||||
      _log.severe("Unable to get user information from the server.");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the user is null, the login was not successful
 | 
			
		||||
    // and we don't have a local copy of the user from a prior successful login
 | 
			
		||||
    if (user == null) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state = state.copyWith(
 | 
			
		||||
@ -229,7 +227,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 | 
			
		||||
      deviceId: deviceId,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return retResult;
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user