Line data Source code
1 : import 'dart:async';
2 : import 'dart:convert';
3 : import 'dart:io';
4 :
5 : import 'package:walletkit_dart/src/common/http_client.dart';
6 :
7 : typedef JSON = Map<String, dynamic>;
8 : typedef JSONList = List<Map<String, dynamic>>;
9 :
10 : const retryInterval = Duration(seconds: 3);
11 : const timeoutDuration = Duration(seconds: 35);
12 :
13 : abstract class HTTPRepository {
14 : final List<String> apiKeys;
15 : final String baseURL;
16 : final String apiKeyHeader;
17 :
18 1 : const HTTPRepository({
19 : required this.apiKeys,
20 : required this.baseURL,
21 : required this.apiKeyHeader,
22 : });
23 1 : Map<String, String> _getHeaders({String? apiKey}) {
24 1 : return {
25 1 : "Content-Type": "application/json",
26 1 : "Accept": "application/json",
27 1 : if (apiKey != null) "TRON-PRO-API-KEY": apiKey,
28 : };
29 : }
30 :
31 1 : String? _getApiKey(int i) {
32 4 : final index = i % (apiKeys.length + 1);
33 3 : if (index == apiKeys.length) {
34 : return null;
35 : }
36 2 : return apiKeys[index];
37 : }
38 :
39 1 : Future<T> getCall<T>(String url) async {
40 1 : final uri = Uri.parse(url);
41 3 : String? apiKey = _getApiKey(apiKeys.length);
42 1 : for (int i = 0; true; i++) {
43 : try {
44 1 : return await _getCall<T>(uri, apiKey: apiKey);
45 1 : } on RateLimitException {
46 1 : apiKey = _getApiKey(i);
47 1 : await Future.delayed(retryInterval);
48 1 : } on UnAuthorizedException {
49 1 : apiKey = _getApiKey(i);
50 1 : await Future.delayed(retryInterval);
51 : } catch (e) {
52 : rethrow;
53 : }
54 : }
55 : }
56 :
57 1 : Future<T> _getCall<T>(Uri url, {String? apiKey}) async {
58 1 : final response = await HTTPService.client
59 1 : .get(
60 : url,
61 1 : headers: _getHeaders(apiKey: apiKey),
62 : )
63 1 : .timeout(timeoutDuration);
64 :
65 2 : if (response.statusCode != 200) {
66 2 : if (response.statusCode == 403) {
67 1 : throw RateLimitException("Rate Limit Exceeded");
68 : }
69 :
70 3 : if (response.statusCode == 401) throw UnAuthorizedException();
71 :
72 0 : throw HTTPStatusException(response.statusCode, response.body, url);
73 : }
74 :
75 1 : final body = response.body;
76 :
77 : try {
78 1 : final json = jsonDecode(body);
79 :
80 1 : if (json is JSON) {
81 1 : final code = json["code"];
82 1 : final message = json["message"];
83 1 : if (code != null && code != 200) {
84 0 : throw HTTPStatusException(code, message, url);
85 : }
86 : }
87 :
88 : return json as T;
89 0 : } on HTTPStatusException {
90 : rethrow;
91 : } catch (e) {
92 0 : throw JSONParsingException(body);
93 : }
94 : }
95 :
96 1 : Future<T> postCall<T>(String url, {required JSON data}) async {
97 1 : final dataString = jsonEncode(data);
98 1 : final uri = Uri.parse(url);
99 :
100 : String? apiKey;
101 0 : for (int i = 0; true; i++) {
102 : try {
103 1 : return await _postCall<T>(uri, apiKey: apiKey, data: dataString);
104 0 : } on RateLimitException {
105 0 : apiKey = _getApiKey(i);
106 0 : await Future.delayed(retryInterval);
107 : } catch (e) {
108 : rethrow;
109 : }
110 : }
111 : }
112 :
113 1 : Future<T> _postCall<T>(
114 : Uri url, {
115 : String? apiKey,
116 : required String data,
117 : }) async {
118 1 : final response = await HTTPService.client
119 1 : .post(
120 : url,
121 1 : headers: _getHeaders(apiKey: apiKey),
122 : body: data,
123 : )
124 1 : .timeout(timeoutDuration);
125 :
126 2 : if (response.statusCode != 200) {
127 0 : if (response.statusCode == 403) {
128 0 : throw RateLimitException("Rate Limit Exceeded");
129 : }
130 :
131 0 : throw HTTPStatusException(response.statusCode, response.body, url);
132 : }
133 :
134 1 : final body = response.body;
135 :
136 : try {
137 1 : final json = jsonDecode(body);
138 :
139 1 : if (json is JSON) {
140 1 : final code = json["code"];
141 1 : final message = json["message"];
142 :
143 1 : if (code is int && code != 200) {
144 0 : throw HTTPStatusException(code, message, url);
145 : }
146 1 : if (code is String && code != "SUCCESS") {
147 0 : throw HTTPStatusException(400, message, url);
148 : }
149 : }
150 :
151 : return json as T;
152 0 : } on HTTPStatusException {
153 : rethrow;
154 : } catch (e) {
155 0 : throw JSONParsingException(body);
156 : }
157 : }
158 : }
159 :
160 : extension QueryUtil on dynamic {
161 1 : String get asQueryString {
162 : if (this == null) {
163 : return "";
164 : }
165 1 : return toString();
166 : }
167 : }
168 :
169 : class HTTPStatusException extends HttpException {
170 : final int statusCode;
171 :
172 1 : const HTTPStatusException(this.statusCode, super.message, Uri? uri)
173 1 : : super(uri: uri);
174 :
175 0 : @override
176 : String toString() {
177 0 : return "HTTPStatusException: $statusCode - $message";
178 : }
179 : }
180 :
181 : class JSONParsingException implements Exception {
182 : final String body;
183 :
184 0 : const JSONParsingException(this.body);
185 :
186 0 : @override
187 : String toString() {
188 0 : return "JSONParsingException: $body";
189 : }
190 : }
191 :
192 : class RateLimitException extends HTTPStatusException {
193 1 : const RateLimitException(
194 : String message,
195 1 : ) : super(429, message, null);
196 :
197 0 : @override
198 : String toString() {
199 0 : return "RateLimitException: $message";
200 : }
201 : }
202 :
203 : class UnAuthorizedException extends HTTPStatusException {
204 2 : const UnAuthorizedException() : super(401, "Unauthorized", null);
205 :
206 0 : @override
207 : String toString() {
208 : return "UnAuthorizedException: Unauthorized";
209 : }
210 : }
|