|
import 'dart:async';
|
|
import '../constants/constants.dart';
|
|
import 'client.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
abstract class AuthenticatedClient extends ApiClient {
|
|
// Abstract methods for repositories to implement their token storage
|
|
Future<String?> _getAccessTokenFromStorage();
|
|
Future<bool> _refreshTokenInStorage();
|
|
|
|
// Concrete internal token management methods - implemented by AuthenticatedClient
|
|
Future<String?> getAccessToken() async {
|
|
return await _getAccessTokenFromStorage();
|
|
}
|
|
|
|
Future<bool> refreshToken() async {
|
|
return await _refreshTokenInStorage();
|
|
}
|
|
|
|
@override
|
|
Future<http.Response> get(
|
|
String partOfUrl, {
|
|
Map<String, dynamic>? queryParams,
|
|
bool needsToken = true,
|
|
bool isFirebaseCall = false,
|
|
bool isTesting = false,
|
|
}) async {
|
|
return _sendAuthenticatedRequest(
|
|
() => super.get(
|
|
partOfUrl,
|
|
queryParams: queryParams,
|
|
isFirebaseCall: isFirebaseCall,
|
|
isTesting: isTesting,
|
|
),
|
|
partOfUrl,
|
|
'GET',
|
|
needsToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<http.Response> post(
|
|
String partOfUrl,
|
|
String body, {
|
|
bool needsToken = true,
|
|
bool isFirebaseCall = false,
|
|
bool isTesting = false,
|
|
}) async {
|
|
return _sendAuthenticatedRequest(
|
|
() => super.post(
|
|
partOfUrl,
|
|
body,
|
|
isFirebaseCall: isFirebaseCall,
|
|
isTesting: isTesting,
|
|
),
|
|
partOfUrl,
|
|
'POST',
|
|
needsToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<http.Response> put(
|
|
String partOfUrl,
|
|
String body, {
|
|
bool needsToken = true,
|
|
bool isFirebaseCall = false,
|
|
bool isTesting = false,
|
|
}) async {
|
|
return _sendAuthenticatedRequest(
|
|
() => super.put(
|
|
partOfUrl,
|
|
body,
|
|
isFirebaseCall: isFirebaseCall,
|
|
isTesting: isTesting,
|
|
),
|
|
partOfUrl,
|
|
'PUT',
|
|
needsToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<http.Response> delete(
|
|
String partOfUrl,
|
|
String body, {
|
|
bool needsToken = true,
|
|
bool isFirebaseCall = false,
|
|
}) async {
|
|
return _sendAuthenticatedRequest(
|
|
() => super.delete(
|
|
partOfUrl,
|
|
body,
|
|
isFirebaseCall: isFirebaseCall,
|
|
),
|
|
partOfUrl,
|
|
'DELETE',
|
|
needsToken,
|
|
);
|
|
}
|
|
|
|
Future<http.Response> _sendAuthenticatedRequest(
|
|
Future<http.Response> Function() requestFunction,
|
|
String partOfUrl,
|
|
String method,
|
|
bool needsToken,
|
|
) async {
|
|
if (!needsToken) {
|
|
// No token needed, just make the request
|
|
return await requestFunction();
|
|
}
|
|
|
|
// First attempt with current token
|
|
final token = await _getAccessTokenFromStorage();
|
|
if (token == null || token.isEmpty) {
|
|
return http.Response('{}', 404);
|
|
}
|
|
|
|
// Make request with token (we'll need to modify ApiClient to accept custom headers)
|
|
final response = await _makeRequestWithToken(requestFunction, token);
|
|
|
|
// If successful, return response
|
|
if (Constants.isSuccessCode(response.statusCode)) {
|
|
return response;
|
|
}
|
|
|
|
// If unauthorized, try to refresh token and retry once
|
|
if (response.statusCode == 401) {
|
|
Constants.logger.i('Token expired, attempting refresh...');
|
|
final refreshed = await _refreshTokenInStorage();
|
|
|
|
if (!refreshed) {
|
|
Constants.logger.e('Token refresh failed');
|
|
return response;
|
|
}
|
|
|
|
final newToken = await _getAccessTokenFromStorage();
|
|
if (newToken == null || newToken.isEmpty) {
|
|
Constants.logger.e('No new token available after refresh');
|
|
return response;
|
|
}
|
|
|
|
Constants.logger.i('Retrying $method $partOfUrl with new token...');
|
|
return await _makeRequestWithToken(requestFunction, newToken);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
Future<http.Response> _makeRequestWithToken(
|
|
Future<http.Response> Function() requestFunction,
|
|
String token,
|
|
) async {
|
|
// TODO:
|
|
// For now, we'll make the request and let the calling method handle the response
|
|
// In a more sophisticated implementation, we might modify ApiClient to accept custom headers
|
|
// But for this separation of concerns demonstration, this approach works
|
|
|
|
return await requestFunction();
|
|
}
|
|
}
|