secure_session_manager 1.0.1
secure_session_manager: ^1.0.1 copied to clipboard
A lightweight, secure, and highly scalable session management package for Flutter with zero performance overhead when optional features are disabled.
example/lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:secure_session_manager/secure_session_manager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize SessionManager with demo settings
await SessionManager.instance.initialize(
tokenProvider: MockTokenProvider(),
idleTimeout: const Duration(seconds: 10), // Short 10s idle for demo
enableLifecycleObserver: true, // Validate on app resume
);
runApp(const MyApp());
}
/// A real-world mock token provider that simulates an API call.
class MockTokenProvider implements TokenProvider {
@override
Future<SessionToken> refreshToken(SessionToken currentToken) async {
// Simulate API network delay
await Future<void>.delayed(const Duration(seconds: 2));
return SessionToken(
accessToken: 'refreshed_at_${DateTime.now().second}s',
refreshToken: 'new_refresh_${idGenerator()}',
expiresAt: DateTime.now().add(const Duration(seconds: 30)), // Expire in 30s
);
}
String idGenerator() => DateTime.now().millisecondsSinceEpoch.toString().substring(8);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// TIP: Wrap your app in a Listener to globally detect user interaction
// for idle detection.
return Listener(
onPointerDown: (_) => SessionManager.instance.touch(),
behavior: HitTestBehavior.translucent,
child: MaterialApp(
title: 'Secure Session Manager Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.light,
),
useMaterial3: true,
),
home: const HomeScreen(),
),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String _status = 'Signed Out';
String _token = 'No Active Session';
bool _isRefreshing = false;
final List<String> _logs = [];
StreamSubscription? _eventSub;
@override
void initState() {
super.initState();
_checkInitialState();
_listenToEvents();
}
Future<void> _checkInitialState() async {
final status = await SessionManager.instance.isLoggedIn;
if (status) {
final session = await SessionManager.instance.getSession();
setState(() {
_status = 'Signed In';
_token = session?.accessToken ?? 'Incomplete session';
});
}
}
void _listenToEvents() {
_eventSub = SessionManager.instance.onEvent.listen((event) {
setState(() {
if (event is LogoutEvent) {
_status = 'Signed Out';
_token = 'Session Cleared';
_log('Event: Global Logout');
} else if (event is SessionExpiredEvent) {
_status = 'Expired (Idle)';
_log('Event: Idle Timeout Detected');
} else if (event is TokenRefreshedEvent) {
_token = event.newToken.accessToken;
_log('Event: Token Refreshed Automatically');
}
});
});
}
void _log(String message) {
_logs.insert(0, "[${DateTime.now().second}s] $message");
if (_logs.length > 5) _logs.removeLast();
}
Future<void> _simulateLogin() async {
// 15 second expiry for testing auto-refresh
final token = SessionToken(
accessToken: 'auth_jwt_${DateTime.now().second}',
refreshToken: 'refresh_token_xyz',
expiresAt: DateTime.now().add(const Duration(seconds: 15)),
);
await SessionManager.instance.setSession(token);
setState(() {
_status = 'Signed In';
_token = token.accessToken;
});
_log('Logged in (Expires in 15s)');
}
Future<void> _manualRefresh() async {
setState(() => _isRefreshing = true);
try {
final newToken = await SessionManager.instance.refreshToken();
setState(() => _token = newToken.accessToken);
} catch (e) {
_log('Refresh Error: $e');
} finally {
setState(() => _isRefreshing = false);
}
}
Future<void> _getAccessToken() async {
// This will trigger auto-refresh if the 15s elapsed
final token = await SessionManager.instance.getAccessToken();
setState(() {
_token = token ?? 'Failed to retrieve';
});
_log('Requested Access Token');
}
@override
void dispose() {
_eventSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Session Manager'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
if (SessionManager.instance.isInitialized)
IconButton(
onPressed: () => SessionManager.instance.logout(),
icon: const Icon(Icons.logout),
tooltip: 'Force Logout',
)
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
_buildInfoCard(),
const SizedBox(height: 24),
Wrap(
spacing: 12,
runSpacing: 12,
alignment: WrapAlignment.center,
children: [
ElevatedButton.icon(
onPressed: _simulateLogin,
icon: const Icon(Icons.login),
label: const Text('Simulate Login'),
),
ElevatedButton.icon(
onPressed: _getAccessToken,
icon: const Icon(Icons.vpn_key),
label: const Text('Use Token (Auto-Refresh)'),
),
OutlinedButton.icon(
onPressed: _isRefreshing ? null : _manualRefresh,
icon: _isRefreshing
? const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.refresh),
label: const Text('Force Refresh'),
),
],
),
const SizedBox(height: 32),
_buildLogSection(),
const SizedBox(height: 16),
const Text(
'TIP: Stop interacting for 10 seconds to trigger idle timeout.',
style: TextStyle(fontSize: 12, fontStyle: FontStyle.italic, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildInfoCard() {
return Card(
elevation: 0,
color: Theme.of(context).colorScheme.surface.withAlpha(20),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Account Status:', style: TextStyle(fontWeight: FontWeight.bold)),
Chip(
label: Text(_status),
backgroundColor: _status == 'Signed In' ? Colors.green.shade100 : Colors.red.shade100,
),
],
),
const Divider(height: 32),
const Align(
alignment: Alignment.centerLeft,
child: Text('Access Token:', style: TextStyle(fontWeight: FontWeight.bold)),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
width: double.infinity,
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(8),
),
child: Text(
_token,
style: const TextStyle(fontFamily: 'monospace', fontSize: 13),
textAlign: TextAlign.center,
),
),
],
),
),
);
}
Widget _buildLogSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Recent Activity Log:', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
if (_logs.isEmpty)
const Text('No activity yet...', style: TextStyle(color: Colors.grey)),
..._logs.map((log) => Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
const Icon(Icons.chevron_right, size: 16, color: Colors.indigo),
const SizedBox(width: 8),
Text(log, style: const TextStyle(fontSize: 13)),
],
),
)),
],
);
}
}