cookies as persistent storage

This commit is contained in:
Mikkel Troels Kongsted 2025-03-19 14:52:07 +01:00
parent fd288dafee
commit f36505a38e
3 changed files with 69 additions and 30 deletions

View File

@ -1,12 +1,40 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/models/user.dart'; import 'package:mobile/models/user.dart';
import 'package:mobile/results.dart'; import 'package:mobile/results.dart';
import 'package:mobile/server/server.dart'; import 'package:mobile/server/server.dart';
import 'package:path_provider/path_provider.dart';
class CookieController {
CookieController();
static Future<File> get _cacheFile async {
final directory = await getApplicationCacheDirectory();
return File("${directory.path}/cookies.txt").create();
}
Future<void> clear() async {
(await _cacheFile).writeAsString("", mode: FileMode.write);
}
Future<void> save(String token) async {
(await _cacheFile).writeAsString(token, mode: FileMode.write);
}
Future<Result<String, Null>> load() async {
final token = await (await _cacheFile).readAsString();
if (token.isEmpty) {
return const Err(null);
}
return Ok(token);
}
}
class SessionController { class SessionController {
final Server server; final Server server;
final CookieController cookieController = CookieController();
String? _sessionToken;
User? _sessionUser; User? _sessionUser;
final List<_ChangeListener> _sessionChangeListeners = []; final List<_ChangeListener> _sessionChangeListeners = [];
@ -18,7 +46,7 @@ class SessionController {
final loginResult = await server.login(email, password); final loginResult = await server.login(email, password);
switch (loginResult) { switch (loginResult) {
case Ok<String, String>(value: final sessionToken): case Ok<String, String>(value: final sessionToken):
_sessionToken = sessionToken; await cookieController.save(sessionToken);
notifySessionChangeListeners(); notifySessionChangeListeners();
return const Ok(null); return const Ok(null);
case Err<String, String>(value: final message): case Err<String, String>(value: final message):
@ -27,8 +55,13 @@ class SessionController {
} }
Future<Result<Null, Null>> loadCachedUser() async { Future<Result<Null, Null>> loadCachedUser() async {
// TODO: retrieve session from cache, if exists switch (await cookieController.load()) {
return _loadCurrentUser(); case Ok<String, Null>():
return _loadCurrentUser();
case Err<String, Null>():
notifyUserChangeListeners();
return const Err(null);
}
} }
Future<Result<Null, Null>> loadUpdatedUser() async { Future<Result<Null, Null>> loadUpdatedUser() async {
@ -49,13 +82,16 @@ class SessionController {
} }
Future<Null> logout() async { Future<Null> logout() async {
final sessionToken = _sessionToken; switch (await cookieController.load()) {
if (sessionToken == null) { case Ok<String, Null>(value: final sessionToken):
return; await server.logout(sessionToken);
await cookieController.clear();
_sessionUser = null;
notifySessionChangeListeners();
case Err<String, Null>():
notifySessionChangeListeners();
return;
} }
await server.logout(sessionToken);
_sessionToken = null;
notifySessionChangeListeners();
} }
User get user { User get user {
@ -86,21 +122,22 @@ class SessionController {
Future<Result<T, String>> requestWithSession<T>( Future<Result<T, String>> requestWithSession<T>(
Future<Result<T, String>> Function(Server server, String sessionToken) Future<Result<T, String>> Function(Server server, String sessionToken)
func) async { func) async {
final sessionToken = _sessionToken; switch (await cookieController.load()) {
if (sessionToken == null) { case Err<String, Null>():
return const Err("unathorized");
}
final result = await func(server, sessionToken);
if (result case Err<T, String>(value: final message)) {
if (message == "unauthorized") {
_sessionToken = null;
_sessionUser = null;
notifySessionChangeListeners();
notifyUserChangeListeners();
return const Err("unathorized"); return const Err("unathorized");
} case Ok<String, Null>(value: final sessionToken):
final result = await func(server, sessionToken);
if (result case Err<T, String>(value: final message)) {
if (message == "unauthorized") {
cookieController.clear();
_sessionUser = null;
notifySessionChangeListeners();
notifyUserChangeListeners();
return const Err("unathorized");
}
}
return result;
} }
return result;
} }
void notifySessionChangeListeners() { void notifySessionChangeListeners() {

View File

@ -18,6 +18,7 @@ import 'package:provider/provider.dart';
import 'package:mobile/controllers/routing.dart'; import 'package:mobile/controllers/routing.dart';
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized();
final server = BackendServer(); final server = BackendServer();
final usersController = UsersController(server: server); final usersController = UsersController(server: server);
final sessionController = SessionController(server: server); final sessionController = SessionController(server: server);
@ -47,6 +48,7 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiProvider( return MultiProvider(
providers: [ providers: [
Provider(create: (_) => CookieController()),
ChangeNotifierProvider( ChangeNotifierProvider(
create: (_) => SessionProvider(controller: sessionController)), create: (_) => SessionProvider(controller: sessionController)),
ChangeNotifierProvider( ChangeNotifierProvider(
@ -79,12 +81,12 @@ class MyApp extends StatelessWidget {
useMaterial3: true, useMaterial3: true,
), ),
home: Consumer2<SessionProvider, CurrentUserProvider>( home: Consumer2<SessionProvider, CurrentUserProvider>(
builder: (_, provider1, provider2, ___) { builder: (_, sessionProvider, currentUserProvider, ___) {
if (provider1.controller.hasUser) { if (sessionProvider.controller.hasUser) {
return Dashboard(); return Dashboard();
} }
return FutureBuilder( return FutureBuilder(
future: provider1.controller.loadCachedUser(), future: sessionProvider.controller.loadCachedUser(),
builder: (_, snapshot) { builder: (_, snapshot) {
final error = snapshot.error; final error = snapshot.error;
if (error != null) { if (error != null) {

View File

@ -102,12 +102,12 @@ class AllProductsPage extends StatefulWidget {
} }
class _AllProductsPageState extends State<AllProductsPage> { class _AllProductsPageState extends State<AllProductsPage> {
final seawchContwowwew = TextEditingController(); final searchController = TextEditingController();
@override @override
void initState() { void initState() {
final contwowwew = context.read<ProductController>(); final controller = context.read<ProductController>();
seawchContwowwew.text = contwowwew.query; searchController.text = controller.query;
super.initState(); super.initState();
} }
@ -128,7 +128,7 @@ class _AllProductsPageState extends State<AllProductsPage> {
onChanged: (query) { onChanged: (query) {
productRepo.searchProducts(query); productRepo.searchProducts(query);
}, },
controller: seawchContwowwew, controller: searchController,
decoration: const InputDecoration( decoration: const InputDecoration(
label: Text("Search"), label: Text("Search"),
contentPadding: EdgeInsets.only(top: 20))), contentPadding: EdgeInsets.only(top: 20))),