Files
.github
android
api
assets
buildtools
debian
drift_schemas
ios
lib
controllers
database
providers
channel.dart
chat_call.dart
config.dart
database.dart
experience.dart
keypair.dart
link_preview.dart
navigation.dart
notification.dart
post.dart
relationship.dart
sn_attachment.dart
sn_network.dart
sn_realm.dart
sn_sticker.dart
special_day.dart
theme.dart
translation.dart
user_directory.dart
userinfo.dart
websocket.dart
widget.dart
screens
types
widgets
firebase_options.dart
logger.dart
main.dart
router.dart
theme.dart
linux
macos
snap
test
web
windows
.gitignore
.metadata
.roadsignrc
CODE_OF_CONDUCT.md
README.md
analysis_options.yaml
build.yaml
devtools_options.yaml
firebase.json
pubspec.lock
pubspec.yaml
roadsign.toml
App/lib/providers/userinfo.dart
2025-03-22 21:50:01 +08:00

98 lines
2.6 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/account.dart';
class UserProvider extends ChangeNotifier {
bool isAuthorized = false;
SnAccount? user;
late final SnNetworkProvider _sn;
late final ConfigProvider _config;
UserProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
_config = context.read<ConfigProvider>();
}
Future<String?> get atk async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(kAtkStoreKey);
}
Future<void> initialize() async {
final value = _config.prefs.getString(kAtkStoreKey);
isAuthorized = value != null;
notifyListeners();
refreshUser().then((value) async {
if (value != null) {
logging.info('[Auth] Logged in as @${value.name}');
logging.debug('[Auth] Access token: ${await atk}');
}
});
}
Future<Map<String, dynamic>?> get atkClaims async {
final tk = (await atk);
if (tk == null) return null;
final atkParts = tk.split('.');
if (atkParts.length != 3) {
throw Exception('invalid format of access token');
}
var rawPayload = atkParts[1].replaceAll('-', '+').replaceAll('_', '/');
switch (rawPayload.length % 4) {
case 0:
break;
case 2:
rawPayload += '==';
break;
case 3:
rawPayload += '=';
break;
default:
throw Exception('illegal format of access token payload');
}
final b64 = utf8.fuse(base64Url);
return jsonDecode(b64.decode(rawPayload));
}
Future<SnAccount?> refreshUser() async {
if (!isAuthorized) return null;
final resp = await _sn.client.get('/cgi/id/users/me');
final out = SnAccount.fromJson(resp.data);
isAuthorized = true;
user = out;
notifyListeners();
return out;
}
void logoutUser() async {
atkClaims.then((value) async {
if (value != null) {
await _sn.client.delete('/cgi/id/users/me/tickets/${value['sed']}');
logging.info('[Auth] Current session has been destroyed.');
}
_sn.clearTokenPair();
});
isAuthorized = false;
user = null;
notifyListeners();
}
void setLanguage(String? value) {
if (value == null) return;
if (user == null) return;
user = user!.copyWith(language: value);
notifyListeners();
}
}