import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:http/io_client.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/exceptions/api_exception.dart'; import 'package:mc_common_app/main.dart'; import 'package:mc_common_app/models/user_models/refresh_token.dart'; import 'package:mc_common_app/models/user_models/user.dart'; import 'package:mc_common_app/utils/shared_prefrence.dart'; import 'package:mc_common_app/utils/utils.dart'; typedef FactoryConstructor = U Function(dynamic); class APIError { int errorCode; String errorMessage; APIError(this.errorCode, this.errorMessage); Map toJson() => {'errorCode': errorCode, 'errorMessage': errorMessage}; @override String toString() { return jsonEncode(this); } static APIException throwAPIException(Response response) { switch (response.statusCode) { case 400: APIError? apiError; if (response.body != null && response.body.isNotEmpty) { var jsonError = jsonDecode(response.body); apiError = APIError(jsonError['errorCode'], jsonError['errorMessage']); } return APIException(APIException.BAD_REQUEST, error: apiError); case 401: return const APIException(APIException.UNAUTHORIZED); case 403: return const APIException(APIException.FORBIDDEN); case 404: return const APIException(APIException.NOT_FOUND); case 500: return const APIException(APIException.INTERNAL_SERVER_ERROR); case 444: var downloadUrl = response.headers["location"]; return APIException(APIException.UPGRADE_REQUIRED, arguments: downloadUrl); default: return const APIException(APIException.OTHER); } } } abstract class ApiClient { Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); Future getJsonForObject(FactoryConstructor factoryConstructor, String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); } class ApiClientImp implements ApiClient { @override Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { var headers0 = {'Accept': 'application/json'}; if (headers != null && headers.isNotEmpty) { headers0.addAll(headers); } var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: headers0, retryTimes: retryTimes); try { if (!kReleaseMode) { log("statusCode:${response.statusCode}"); } var jsonData = jsonDecode(response.body); return factoryConstructor(jsonData); } catch (ex) { log(ex.toString()); log("exception:$ex"); throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex); } } @override Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { String? requestBody; if (jsonObject != null) { requestBody = jsonEncode(jsonObject); if (headers == null) { headers = {'Content-Type': 'application/json'}; } else { headers['Content-Type'] = 'application/json'; } } return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); } Future _postForResponse( String url, requestBody, { String? token, Map? queryParameters, Map? headers, int retryTimes = 0, }) async { try { var headers0 = {}; if (token != null) { headers0['Authorization'] = 'Bearer $token'; } if (headers != null && headers.isNotEmpty) { headers0.addAll(headers); } if (queryParameters != null) { var queryString = Uri(queryParameters: queryParameters).query; url = '$url?$queryString'; } Response response; response = await _post(Uri.parse(url), body: requestBody, headers: headers0).timeout(const Duration(seconds: 100)); if (!kReleaseMode) { logger.d("------URL------"); logger.i(url); logger.d("------Payload------"); logger.i(jsonDecode(requestBody)); logger.d("------Response------"); logger.i(jsonDecode(response.body)); } if (response.statusCode >= 200 && response.statusCode < 500) { var jsonData = jsonDecode(response.body); if (jsonData["StatusMessage"] != null && jsonData["StatusMessage"] == "Unauthorized user attempt to access API") { String mToken = await updateUserToken(); return await _postForResponse(url, requestBody, token: mToken, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); } return response; } else { throw APIError.throwAPIException(response); } } on SocketException catch (e) { if (retryTimes > 0) { log('will retry after 3 seconds...'); await Future.delayed(const Duration(seconds: 3)); return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } } on HttpException catch (e) { if (retryTimes > 0) { log('will retry after 3 seconds...'); await Future.delayed(const Duration(seconds: 3)); return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } } on TimeoutException catch (e) { BuildContext? context = navigatorKey.currentContext; if (context != null) { NoInternetDialog.show(context); } throw APIException(APIException.TIMEOUT, arguments: e); } on ClientException catch (e) { if (retryTimes > 0) { log('will retry after 3 seconds...'); await Future.delayed(const Duration(seconds: 3)); return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } } catch (ex) { log("exception1:$ex"); throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex); } } bool _certificateCheck(X509Certificate cert, String host, int port) => true; Future _withClient(Future Function(Client) fn) async { var httpClient = HttpClient()..badCertificateCallback = _certificateCheck; var client = IOClient(httpClient); try { return await fn(client); } finally { client.close(); } } Future _post(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding)); @override Future getJsonForObject(FactoryConstructor factoryConstructor, String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { var headers0 = {'Accept': 'application/json'}; if (headers != null && headers.isNotEmpty) { headers0.addAll(headers); } var response = await getJsonForResponse(url, token: token, queryParameters: queryParameters, headers: headers0, retryTimes: retryTimes); try { var jsonData = jsonDecode(response.body); return factoryConstructor(jsonData); } catch (ex) { log("exception:${response.body}"); throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex); } } @override Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { if (headers == null) { headers = {'Content-Type': 'application/json'}; } else { headers['Content-Type'] = 'application/json'; } return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); } bool isFirstCall = true; Future _getForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { try { var headers0 = {}; if (token != null) { headers0['Authorization'] = 'Bearer $token'; } if (headers != null && headers.isNotEmpty) { headers0.addAll(headers); } if (queryParameters != null) { String queryString = Uri(queryParameters: queryParameters).query; if (isFirstCall) url = '$url?$queryString'; } if (!kReleaseMode) { logger.i(url); logger.i("$queryParameters"); } var response = await _get(Uri.parse(url), headers: headers0).timeout(const Duration(seconds: 60)); if (!kReleaseMode) { logger.i(jsonDecode(response.body)); } if (response.statusCode >= 200 && response.statusCode < 500) { var jsonData = jsonDecode(response.body); if (jsonData["StatusMessage"] != null && jsonData["StatusMessage"] == "Unauthorized user attempt to access API") { String mToken = await updateUserToken(); isFirstCall = false; return await _getForResponse(url, token: mToken, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); } return response; } else { throw APIError.throwAPIException(response); } } on SocketException catch (e) { if (retryTimes > 0) { log('will retry after 3 seconds...'); await Future.delayed(const Duration(seconds: 3)); return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } } on HttpException catch (e) { if (retryTimes > 0) { log('will retry after 3 seconds...'); await Future.delayed(const Duration(seconds: 3)); return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } } on TimeoutException catch (e) { final context = navigatorKey.currentContext; if (context != null) { NoInternetDialog.show(context); } throw APIException(APIException.TIMEOUT, arguments: e); } on ClientException catch (e) { if (retryTimes > 0) { await Future.delayed(const Duration(seconds: 3)); return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } } } Future _get(url, {Map? headers}) => _withClient((client) => client.get(url, headers: headers)); // Future refreshTokenAPI(String token, String refreshToken) async { // var postParams = {"token": token, "refreshToken": refreshToken}; // // String t = AppState().getUser.data!.accessToken ?? ""; // return await postJsonForObject((json) => RefreshToken.fromJson(json), ApiConsts.RefreshToken, postParams); // } // //TODO: needs to solve later this. // String updateUserToken() { // return ""; // } Future refreshTokenAPI(String token, String refreshToken) async { var postParams = {"token": token, "refreshToken": refreshToken}; // String t = AppState().getUser.data!.accessToken ?? ""; return await postJsonForObject((json) => RefreshToken.fromJson(json), ApiConsts.refreshToken, postParams); } Future updateUserToken() async { String token = await SharedPrefManager.getUserToken(); String refreshToken = await SharedPrefManager.getRefreshToken(); RefreshToken refresh = await refreshTokenAPI(token, refreshToken); SharedPrefManager.setUserToken(refresh.data!.accessToken ?? ""); SharedPrefManager.setRefreshToken(refresh.data!.refreshToken ?? ""); String mdata = await SharedPrefManager.getData(); UserInfo info = UserInfo.fromJson(jsonDecode(mdata)); User user = User(); user.data = UserData(accessToken: refresh.data!.accessToken ?? "", refreshToken: refresh.data!.refreshToken ?? "", userInfo: info); AppState().setUser = user; return refresh.data!.accessToken ?? ""; } }