add refresh token support
This commit is contained in:
@@ -2,7 +2,7 @@ import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
typedef TokenProvider = String? Function();
|
||||
typedef UnauthorizedHandler = Future<void> Function();
|
||||
typedef UnauthorizedHandler = Future<bool> Function();
|
||||
|
||||
class ApiService {
|
||||
String _baseUrl;
|
||||
@@ -36,35 +36,47 @@ class ApiService {
|
||||
_client.close();
|
||||
}
|
||||
|
||||
Map<String, String> _buildHeaders(Map<String, String>? extra) {
|
||||
Map<String, String> _buildHeaders(
|
||||
Map<String, String>? extra, {
|
||||
bool includeAuth = true,
|
||||
}) {
|
||||
final token = _getToken?.call();
|
||||
final headers = {'accept': 'application/json', ...?extra};
|
||||
if (token != null && token.isNotEmpty) {
|
||||
if (includeAuth && token != null && token.isNotEmpty) {
|
||||
headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
Future<dynamic> get(String endpoint, {Map<String, String>? headers}) async {
|
||||
final response = await _client
|
||||
.get(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers),
|
||||
)
|
||||
.timeout(timeout);
|
||||
Future<dynamic> get(
|
||||
String endpoint, {
|
||||
Map<String, String>? headers,
|
||||
bool includeAuth = true,
|
||||
bool allowRetry = true,
|
||||
}) async {
|
||||
final response = await _sendWithRetry(
|
||||
() => _client.get(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers, includeAuth: includeAuth),
|
||||
),
|
||||
allowRetry: allowRetry,
|
||||
);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
Future<ApiBinaryResponse> getBytes(
|
||||
String endpoint, {
|
||||
Map<String, String>? headers,
|
||||
bool includeAuth = true,
|
||||
bool allowRetry = true,
|
||||
}) async {
|
||||
final response = await _client
|
||||
.get(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers),
|
||||
)
|
||||
.timeout(timeout);
|
||||
final response = await _sendWithRetry(
|
||||
() => _client.get(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers, includeAuth: includeAuth),
|
||||
),
|
||||
allowRetry: allowRetry,
|
||||
);
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
final contentDisposition = response.headers['content-disposition'];
|
||||
@@ -76,10 +88,6 @@ class ApiService {
|
||||
);
|
||||
}
|
||||
|
||||
if (response.statusCode == 401 && _onUnauthorized != null) {
|
||||
await _onUnauthorized!();
|
||||
}
|
||||
|
||||
final body = _decodeBody(response);
|
||||
final message = _extractErrorMessage(body);
|
||||
throw ApiException(
|
||||
@@ -93,15 +101,21 @@ class ApiService {
|
||||
String endpoint,
|
||||
dynamic data, {
|
||||
Map<String, String>? headers,
|
||||
bool includeAuth = true,
|
||||
bool allowRetry = true,
|
||||
}) async {
|
||||
final hasBody = data != null;
|
||||
final response = await _client
|
||||
.post(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(hasBody ? _jsonHeaders(headers) : headers),
|
||||
body: hasBody ? jsonEncode(data) : null,
|
||||
)
|
||||
.timeout(timeout);
|
||||
final response = await _sendWithRetry(
|
||||
() => _client.post(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(
|
||||
hasBody ? _jsonHeaders(headers) : headers,
|
||||
includeAuth: includeAuth,
|
||||
),
|
||||
body: hasBody ? jsonEncode(data) : null,
|
||||
),
|
||||
allowRetry: allowRetry,
|
||||
);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
@@ -112,38 +126,53 @@ class ApiService {
|
||||
String fieldName = 'file',
|
||||
Map<String, String>? fields,
|
||||
Map<String, String>? headers,
|
||||
bool includeAuth = true,
|
||||
bool allowRetry = true,
|
||||
}) async {
|
||||
final request = http.MultipartRequest(
|
||||
'POST',
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
);
|
||||
request.headers.addAll(_buildHeaders(headers));
|
||||
if (fields != null && fields.isNotEmpty) {
|
||||
request.fields.addAll(fields);
|
||||
Future<http.Response> send() async {
|
||||
final request = http.MultipartRequest(
|
||||
'POST',
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
);
|
||||
request.headers.addAll(_buildHeaders(headers, includeAuth: includeAuth));
|
||||
if (fields != null && fields.isNotEmpty) {
|
||||
request.fields.addAll(fields);
|
||||
}
|
||||
request.files.add(
|
||||
http.MultipartFile.fromBytes(
|
||||
fieldName,
|
||||
bytes,
|
||||
filename: filename,
|
||||
),
|
||||
);
|
||||
final streamed = await _client.send(request);
|
||||
return http.Response.fromStream(streamed);
|
||||
}
|
||||
request.files.add(
|
||||
http.MultipartFile.fromBytes(
|
||||
fieldName,
|
||||
bytes,
|
||||
filename: filename,
|
||||
),
|
||||
);
|
||||
final streamed = await _client.send(request).timeout(timeout);
|
||||
final response = await http.Response.fromStream(streamed);
|
||||
|
||||
final response = await _sendWithRetry(send, allowRetry: allowRetry);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
Future<dynamic> postForm(String endpoint, Map<String, String> data) async {
|
||||
final response = await _client
|
||||
.post(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders({
|
||||
Future<dynamic> postForm(
|
||||
String endpoint,
|
||||
Map<String, String> data, {
|
||||
bool includeAuth = true,
|
||||
bool allowRetry = true,
|
||||
}) async {
|
||||
final response = await _sendWithRetry(
|
||||
() => _client.post(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(
|
||||
{
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'accept': 'application/json',
|
||||
}),
|
||||
body: data, // http package handles form-encoding for Map<String, String>
|
||||
)
|
||||
.timeout(timeout);
|
||||
},
|
||||
includeAuth: includeAuth,
|
||||
),
|
||||
body: data, // http package handles form-encoding for Map<String, String>
|
||||
),
|
||||
allowRetry: allowRetry,
|
||||
);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
@@ -151,28 +180,37 @@ class ApiService {
|
||||
String endpoint,
|
||||
dynamic data, {
|
||||
Map<String, String>? headers,
|
||||
bool includeAuth = true,
|
||||
bool allowRetry = true,
|
||||
}) async {
|
||||
final hasBody = data != null;
|
||||
final response = await _client
|
||||
.put(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(hasBody ? _jsonHeaders(headers) : headers),
|
||||
body: hasBody ? jsonEncode(data) : null,
|
||||
)
|
||||
.timeout(timeout);
|
||||
final response = await _sendWithRetry(
|
||||
() => _client.put(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(
|
||||
hasBody ? _jsonHeaders(headers) : headers,
|
||||
includeAuth: includeAuth,
|
||||
),
|
||||
body: hasBody ? jsonEncode(data) : null,
|
||||
),
|
||||
allowRetry: allowRetry,
|
||||
);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
Future<dynamic> delete(
|
||||
String endpoint, {
|
||||
Map<String, String>? headers,
|
||||
bool includeAuth = true,
|
||||
bool allowRetry = true,
|
||||
}) async {
|
||||
final response = await _client
|
||||
.delete(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers),
|
||||
)
|
||||
.timeout(timeout);
|
||||
final response = await _sendWithRetry(
|
||||
() => _client.delete(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers, includeAuth: includeAuth),
|
||||
),
|
||||
allowRetry: allowRetry,
|
||||
);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
@@ -186,10 +224,6 @@ class ApiService {
|
||||
return body;
|
||||
}
|
||||
|
||||
if (res.statusCode == 401 && _onUnauthorized != null) {
|
||||
await _onUnauthorized!();
|
||||
}
|
||||
|
||||
final message = _extractErrorMessage(body);
|
||||
throw ApiException(
|
||||
statusCode: res.statusCode,
|
||||
@@ -239,6 +273,20 @@ class ApiService {
|
||||
return body.toString();
|
||||
}
|
||||
|
||||
Future<http.Response> _sendWithRetry(
|
||||
Future<http.Response> Function() send, {
|
||||
required bool allowRetry,
|
||||
}) async {
|
||||
var response = await send().timeout(timeout);
|
||||
if (response.statusCode == 401 && allowRetry && _onUnauthorized != null) {
|
||||
final refreshed = await _onUnauthorized!();
|
||||
if (refreshed) {
|
||||
response = await send().timeout(timeout);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
String? _extractFilename(String? contentDisposition) {
|
||||
if (contentDisposition == null || contentDisposition.isEmpty) return null;
|
||||
final utf8Match =
|
||||
|
||||
Reference in New Issue
Block a user