import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:dio/dio.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:moneymgr_mobile/services/api/api_token.dart'; import 'package:moneymgr_mobile/services/storage/prefs.dart'; import 'package:moneymgr_mobile/services/storage/secure_storage.dart'; import 'package:moneymgr_mobile/utils/string_utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; part 'api_client.g.dart'; /// API token header const apiTokenHeader = "X-Auth-Token"; /// Client API class ApiClient { final ApiToken token; final Dio client; final SharedPreferencesWithCache prefs; ApiClient({required this.token, required this.prefs}) : client = Dio(BaseOptions(baseUrl: token.apiUrl)); /// Get Dio instance Future> execute(String uri, {String method = "GET"}) async { Logger.root.fine("Request on ${token.apiUrl} - URI $uri"); return client.request( uri, options: Options( method: method, headers: {apiTokenHeader: _genJWT(method, uri)}, ), ); } /// Generate authentication JWT String _genJWT(String method, String uri) { final jwt = JWT( {"nonce": getRandomString(15), "met": method, "uri": uri}, header: {"kid": token.tokenId.toString()}, ); return jwt.sign( SecretKey(token.tokenValue), algorithm: JWTAlgorithm.HS256, expiresIn: Duration(minutes: 15), ); } } /// An API service that handles authentication and exposes an [ApiClient]. /// /// Every API call coming from UI should watch/read this provider instead of /// instantiating the [ApiClient] itself. When being watched, it will force any /// data provider (provider that fetches data) to refetch when the /// authentication state changes. /// /// The API client is kept alive to follow dio's recommendation to use the same /// client instance for the entire app. @riverpod ApiClient? apiService(Ref ref) { final storage = ref.watch(secureStorageProvider); final prefs = ref.watch(prefsProvider); final t = storage.value?.token(); if (t == null || prefs.value == null) return null; ref.keepAlive(); return ApiClient(token: t, prefs: prefs.value!); }