MIF_E31222656/lib/services/session_manager.dart

467 lines
14 KiB
Dart

import 'dart:async';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:tugas_akhir_supabase/services/user_presence_service.dart';
class SessionManager {
static const String _lastActiveTimeKey = 'last_active_time';
static const String _lastUserInteractionKey = 'last_user_interaction';
static const String _sessionStateKey = 'session_state';
static const int _sessionTimeoutMinutes =
480; // Increased to 8 hours for permissive mode
static Timer? _sessionCheckTimer;
static Timer? _presenceUpdateTimer;
static bool _isCheckingSession = false;
static bool _isAppInBackground = false;
static bool _isSessionExpired = false;
static bool _hasLoggedInUser = false;
static bool _isAppJustLaunched = true;
static final StreamController<bool> _sessionExpiredController =
StreamController<bool>.broadcast();
// Stream untuk mendengarkan perubahan status session
static Stream<bool> get sessionExpiredStream =>
_sessionExpiredController.stream;
// Getter untuk mendapatkan waktu timeout session dalam menit
static int getSessionTimeout() {
return _sessionTimeoutMinutes;
}
// Getter untuk status login
static bool get hasLoggedInUser => _hasLoggedInUser;
// Metode untuk menghindari blocking pada SharedPreferences
static Future<SharedPreferences?> _getSafeSharedPreferences() async {
try {
return await SharedPreferences.getInstance().timeout(
const Duration(seconds: 2),
onTimeout: () {
debugPrint('Session: SharedPreferences timeout');
throw TimeoutException('SharedPreferences timeout');
},
);
} catch (e) {
debugPrint('Session: Error getting SharedPreferences - $e');
return null;
}
}
// Initialize session management
static Future<void> initializeSession() async {
try {
_isAppJustLaunched = true;
debugPrint('Session: App just launched flag set to true');
Future.delayed(Duration(seconds: 10), () {
_isAppJustLaunched = false;
debugPrint('Session: App just launched flag set to false after delay');
});
final currentUser = Supabase.instance.client.auth.currentUser;
if (currentUser == null) {
debugPrint('Session: No authenticated user found');
_hasLoggedInUser = false;
return;
}
_hasLoggedInUser = true;
await updateLastUserInteraction();
_isAppInBackground = false;
_setSessionExpired(false);
// Initialize user presence service
try {
if (GetIt.instance.isRegistered<UserPresenceService>()) {
await GetIt.instance<UserPresenceService>().initialize();
_startPresenceUpdates();
}
} catch (e) {
debugPrint('Session: Error initializing presence service - $e');
}
Future.delayed(Duration(seconds: 5), () {
_startSessionMonitoring();
});
debugPrint(
'Session: Initialized successfully for user: ${currentUser.email}',
);
} catch (e) {
debugPrint('Session: Error initializing - $e');
_hasLoggedInUser = false;
}
}
// Method baru untuk memperbarui status login
static void setUserLoggedIn(bool isLoggedIn) {
_hasLoggedInUser = isLoggedIn;
debugPrint('Session: User login status set to: $isLoggedIn');
if (isLoggedIn) {
_setSessionExpired(false); // Pastikan expired direset!
_isSessionExpired = false;
_sessionExpiredController.add(false); // Kirim event expired false
updateLastUserInteraction();
_startSessionMonitoring();
_startPresenceUpdates();
} else {
_stopSessionMonitoring();
_stopPresenceUpdates();
_setSessionExpired(false); // Reset expired saat logout juga!
_isSessionExpired = false;
_sessionExpiredController.add(false);
}
}
// Tambahkan fungsi untuk reset expired state secara manual
static void resetExpiredState() {
_isSessionExpired = false;
_sessionExpiredController.add(false);
}
// Start periodic presence updates
static void _startPresenceUpdates() {
_stopPresenceUpdates(); // Stop any existing timer
// Update presence every 30 seconds
_presenceUpdateTimer = Timer.periodic(Duration(seconds: 30), (timer) {
if (_hasLoggedInUser && !_isSessionExpired) {
try {
if (GetIt.instance.isRegistered<UserPresenceService>()) {
GetIt.instance<UserPresenceService>().updatePresence();
}
} catch (e) {
debugPrint('Session: Error updating presence - $e');
}
}
});
debugPrint('Session: Started presence updates');
}
// Stop presence updates
static void _stopPresenceUpdates() {
_presenceUpdateTimer?.cancel();
_presenceUpdateTimer = null;
}
// Check if session is valid with extremely permissive logic
static Future<bool> isSessionValid() async {
debugPrint('Session: Checking session validity (PERMISSIVE MODE)...');
// Check if user is actually logged in
final currentUser = Supabase.instance.client.auth.currentUser;
final currentSession = Supabase.instance.client.auth.currentSession;
if (currentUser == null || currentSession == null) {
debugPrint('Session: No user or session found, returning false');
return false;
}
// Always return true for permissive mode if user is logged in
debugPrint('Session: PERMISSIVE MODE - User logged in, returning true');
return true;
}
// BARU: Update timestamp interaksi pengguna terakhir
static Future<void> updateLastUserInteraction() async {
try {
final currentUser = Supabase.instance.client.auth.currentUser;
if (currentUser == null) {
debugPrint(
'Session: Cannot update interaction - user not authenticated',
);
return;
}
SharedPreferences? prefs = await _getSafeSharedPreferences();
if (prefs == null) return;
final now = DateTime.now().millisecondsSinceEpoch;
try {
await prefs
.setInt(_lastUserInteractionKey, now)
.timeout(
const Duration(seconds: 2),
onTimeout: () {
debugPrint(
'Session: Timeout setting last user interaction time',
);
return false;
},
);
await updateLastActiveTime();
// Update presence when user interacts
try {
if (GetIt.instance.isRegistered<UserPresenceService>()) {
GetIt.instance<UserPresenceService>().updatePresence();
}
} catch (e) {
debugPrint('Session: Error updating presence on interaction - $e');
}
debugPrint(
'Session: User interaction recorded at ${DateTime.fromMillisecondsSinceEpoch(now)}',
);
} catch (e) {
debugPrint(
'Session: Error writing user interaction to SharedPreferences - $e',
);
}
} catch (e) {
debugPrint('Session: Error updating user interaction - $e');
}
}
// Update last active time with better error handling
static Future<void> updateLastActiveTime() async {
try {
final currentUser = Supabase.instance.client.auth.currentUser;
if (currentUser == null) {
debugPrint('Session: Cannot update activity - user not authenticated');
return;
}
SharedPreferences? prefs = await _getSafeSharedPreferences();
if (prefs == null) return;
final now = DateTime.now().millisecondsSinceEpoch;
try {
await prefs
.setInt(_lastActiveTimeKey, now)
.timeout(
const Duration(seconds: 2),
onTimeout: () {
debugPrint('Session: Timeout setting last active time');
return false;
},
);
await prefs
.setString(_sessionStateKey, 'active')
.timeout(
const Duration(seconds: 2),
onTimeout: () {
debugPrint('Session: Timeout setting session state');
return false;
},
);
debugPrint(
'Session: Activity updated at ${DateTime.fromMillisecondsSinceEpoch(now)}',
);
} catch (e) {
debugPrint('Session: Error writing to SharedPreferences - $e');
}
} catch (e) {
debugPrint('Session: Error updating activity - $e');
}
}
// Called when app goes to background
static Future<void> onAppBackground() async {
debugPrint('Session: App entering background');
_isAppInBackground = true;
await updateLastActiveTime();
_startSessionMonitoring();
}
// Called when app comes to foreground
static Future<void> onAppForeground() async {
debugPrint('Session: App entering foreground');
if (!_isAppInBackground) return;
_isAppInBackground = false;
try {
final currentUser = Supabase.instance.client.auth.currentUser;
if (currentUser == null) {
debugPrint(
'Session: No authenticated user when returning to foreground',
);
_stopSessionMonitoring();
return;
}
debugPrint(
'Session: Checking session validity after returning to foreground',
);
// Use permissive mode - don't check timeout
final isValid = await isSessionValid();
if (!isValid) {
debugPrint('Session: Session invalid after background');
await clearSession();
Future.delayed(const Duration(milliseconds: 500), () {
_setSessionExpired(true);
});
} else {
debugPrint('Session: Still valid after background');
await updateLastActiveTime();
_startSessionMonitoring();
}
} catch (e) {
debugPrint('Session: Error during foreground transition - $e');
}
}
// Set session expired state and notify listeners
static void _setSessionExpired(bool value) {
// Jangan trigger expired jika user memang belum login
if (!_hasLoggedInUser) {
debugPrint('Session: Not logged in, skip setting expired state');
return;
}
if (_isSessionExpired != value) {
_isSessionExpired = value;
debugPrint('Session: Setting expired state to $value');
_sessionExpiredController.add(value);
// If session is expired, force clear session
if (value) {
debugPrint('Session: Session expired, clearing session data');
clearSession();
}
}
}
// Get session status for UI components
static bool get isExpired => _isSessionExpired;
// Check if user is properly authenticated
static bool get isAuthenticated {
if (_isSessionExpired) {
debugPrint('Session: Session is marked as expired, not authenticated');
return false;
}
if (!_hasLoggedInUser) {
return false;
}
final currentUser = Supabase.instance.client.auth.currentUser;
final currentSession = Supabase.instance.client.auth.currentSession;
final isValid =
currentUser != null && currentSession != null && !_isSessionExpired;
// Only update state if there's a significant change, and be more conservative
if (!isValid && _hasLoggedInUser) {
// Double check before changing state to avoid race conditions
final doubleCheckUser = Supabase.instance.client.auth.currentUser;
final doubleCheckSession = Supabase.instance.client.auth.currentSession;
if (doubleCheckUser == null || doubleCheckSession == null) {
debugPrint(
'Session: User authentication state changed, updating flags',
);
_hasLoggedInUser = false;
_setSessionExpired(true);
} else {
debugPrint('Session: User still exists, keeping authentication state');
}
}
return isValid;
}
// Clear session data with proper cleanup
static Future<void> clearSession() async {
try {
_stopSessionMonitoring();
_setSessionExpired(true);
_hasLoggedInUser = false;
_isSessionExpired = false;
_sessionExpiredController.add(false);
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_lastActiveTimeKey);
await prefs.remove(_lastUserInteractionKey);
await prefs.remove(_sessionStateKey);
await Supabase.instance.client.auth.signOut();
debugPrint('Session: Cleared and signed out successfully');
} catch (e) {
debugPrint('Session: Error during cleanup - $e');
_setSessionExpired(true);
_hasLoggedInUser = false;
_isSessionExpired = false;
_sessionExpiredController.add(false);
}
}
// Refresh session - extend if still valid
static Future<bool> refreshSession() async {
try {
final isValid = await isSessionValid();
if (isValid) {
await updateLastUserInteraction();
return true;
}
return false;
} catch (e) {
debugPrint('Session: Error refreshing - $e');
return false;
}
}
// Fungsi baru untuk memberi tahu aplikasi tentang aktivitas penting yang harus memperbarui sesi
static Future<void> notifyImportantActivity(String activityType) async {
try {
debugPrint('Session: Important activity detected: $activityType');
await updateLastUserInteraction();
Timer(Duration(seconds: 2), () {
debugPrint('Session: Follow-up check after important activity');
isSessionValid();
});
} catch (e) {
debugPrint('Session: Error handling important activity - $e');
}
}
// Start session monitoring (DISABLED for permissive mode)
static void _startSessionMonitoring() {
_stopSessionMonitoring();
debugPrint('Session: Session monitoring DISABLED for permissive mode');
// No timer setup - completely disabled
debugPrint('Session: No periodic checks will run');
}
// Stop session monitoring
static void _stopSessionMonitoring() {
_sessionCheckTimer?.cancel();
_sessionCheckTimer = null;
_stopPresenceUpdates();
debugPrint('Session: Monitoring stopped');
}
// Dispose resources
static void dispose() {
_sessionCheckTimer?.cancel();
_presenceUpdateTimer?.cancel();
_sessionExpiredController.close();
try {
if (GetIt.instance.isRegistered<UserPresenceService>()) {
GetIt.instance<UserPresenceService>().dispose();
}
} catch (e) {
debugPrint('Session: Error disposing presence service - $e');
}
}
}