import 'dart:async'; import 'dart:convert'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/hbbs/hbbs.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import '../common.dart'; import 'model.dart'; import 'platform_model.dart'; bool refreshingUser = false; class UserModel { final RxString userName = ''.obs; final RxBool isAdmin = false.obs; bool get isLogin => userName.isNotEmpty; WeakReference parent; UserModel(this.parent); void refreshCurrentUser() async { final token = bind.mainGetLocalOption(key: 'access_token'); if (token == '') { await updateOtherModels(); return; } _updateLocalUserInfo(); final url = await bind.mainGetApiServer(); final body = { 'id': await bind.mainGetMyId(), 'uuid': await bind.mainGetUuid() }; if (refreshingUser) return; try { refreshingUser = true; final response = await http.post(Uri.parse('$url/api/currentUser'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $token' }, body: json.encode(body)); refreshingUser = false; final status = response.statusCode; if (status == 401 || status == 400) { reset(clearAbCache: status == 401); return; } final data = json.decode(utf8.decode(response.bodyBytes)); final error = data['error']; if (error != null) { throw error; } final user = UserPayload.fromJson(data); _parseAndUpdateUser(user); } catch (e) { debugPrint('Failed to refreshCurrentUser: $e'); } finally { refreshingUser = false; await updateOtherModels(); } } static Map? getLocalUserInfo() { final userInfo = bind.mainGetLocalOption(key: 'user_info'); if (userInfo == '') { return null; } try { return json.decode(userInfo); } catch (e) { debugPrint('Failed to get local user info "$userInfo": $e'); } return null; } _updateLocalUserInfo() { final userInfo = getLocalUserInfo(); if (userInfo != null) { userName.value = userInfo['name']; } } Future reset({bool clearAbCache = false}) async { await bind.mainSetLocalOption(key: 'access_token', value: ''); await bind.mainSetLocalOption(key: 'user_info', value: ''); if (clearAbCache) await bind.mainClearAb(); await gFFI.groupModel.reset(); userName.value = ''; } _parseAndUpdateUser(UserPayload user) { userName.value = user.name; isAdmin.value = user.isAdmin; bind.mainSetLocalOption(key: 'user_info', value: jsonEncode(user)); } // update ab and group status static Future updateOtherModels() async { await Future.wait([gFFI.abModel.pullAb(), gFFI.groupModel.pull()]); } Future logOut({String? apiServer}) async { final tag = gFFI.dialogManager.showLoading(translate('Waiting')); try { final url = apiServer ?? await bind.mainGetApiServer(); final authHeaders = getHttpHeaders(); authHeaders['Content-Type'] = "application/json"; await http .post(Uri.parse('$url/api/logout'), body: jsonEncode({ 'id': await bind.mainGetMyId(), 'uuid': await bind.mainGetUuid(), }), headers: authHeaders) .timeout(Duration(seconds: 2)); } catch (e) { debugPrint("request /api/logout failed: err=$e"); } finally { await reset(clearAbCache: true); gFFI.dialogManager.dismissByTag(tag); } } /// throw [RequestException] Future login(LoginRequest loginRequest) async { final url = await bind.mainGetApiServer(); final resp = await http.post(Uri.parse('$url/api/login'), headers: {'Content-Type': 'application/json'}, body: jsonEncode(loginRequest.toJson())); final Map body; try { body = jsonDecode(utf8.decode(resp.bodyBytes)); } catch (e) { debugPrint("login: jsonDecode resp body failed: ${e.toString()}"); if (resp.statusCode != 200) { BotToast.showText( contentColor: Colors.red, text: 'HTTP ${resp.statusCode}'); } rethrow; } if (resp.statusCode != 200) { throw RequestException(resp.statusCode, body['error'] ?? ''); } if (body['error'] != null) { throw RequestException(0, body['error']); } return getLoginResponseFromAuthBody(body); } LoginResponse getLoginResponseFromAuthBody(Map body) { final LoginResponse loginResponse; try { loginResponse = LoginResponse.fromJson(body); } catch (e) { debugPrint("login: jsonDecode LoginResponse failed: ${e.toString()}"); rethrow; } if (loginResponse.user != null) { _parseAndUpdateUser(loginResponse.user!); } return loginResponse; } static Future> queryOidcLoginOptions() async { try { final url = await bind.mainGetApiServer(); if (url.trim().isEmpty) return []; final resp = await http.get(Uri.parse('$url/api/login-options')); final List ops = []; for (final item in jsonDecode(resp.body)) { ops.add(item as String); } for (final item in ops) { if (item.startsWith('common-oidc/')) { return jsonDecode(item.substring('common-oidc/'.length)); } } return ops .where((item) => item.startsWith('oidc/')) .map((item) => {'name': item.substring('oidc/'.length)}) .toList(); } catch (e) { debugPrint( "queryOidcLoginOptions: jsonDecode resp body failed: ${e.toString()}"); return []; } } }