Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions lib/core/api/dio_interceptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_auth_app/core/core.dart';
import 'package:flutter_auth_app/features/auth/auth.dart';
import 'package:flutter_auth_app/utils/utils.dart';

// coverage:ignore-start
class DioInterceptor extends Interceptor with FirebaseCrashLogger {
class DioInterceptor extends Interceptor
with FirebaseCrashLogger, MainBoxMixin {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
String headerMessage = '';
options.headers.forEach((k, v) => headerMessage += '► $k: $v\n');

try {
options.queryParameters.forEach(
(k, v) => debugPrint(
'► $k: $v',
),
);
options.queryParameters.forEach((k, v) => debugPrint('► $k: $v'));
} catch (_) {}
try {
const JsonEncoder encoder = JsonEncoder.withIndent(' ');
Expand All @@ -38,14 +37,67 @@ class DioInterceptor extends Interceptor with FirebaseCrashLogger {
}

@override
void onError(DioException dioException, ErrorInterceptorHandler handler) {
Future<void> onError(
DioException dioException,
ErrorInterceptorHandler handler,
) async {
log.e(
"<-- ${dioException.message} ${dioException.response?.requestOptions != null ? (dioException.response!.requestOptions.baseUrl + dioException.response!.requestOptions.path) : 'URL'}\n\n"
"${dioException.response != null ? dioException.response!.data : 'Unknown Error'}",
);

nonFatalError(error: dioException, stackTrace: dioException.stackTrace);
super.onError(dioException, handler);
if (dioException.response?.statusCode == 401 &&
dioException.response?.data['meta']['description'] ==
'Unauthenticated.') {
if (getData(MainBoxKeys.refreshToken) != null) {
await refreshToken();

// Retry the request with the new token
return handler.resolve(await _retry(dioException.requestOptions));
} else {
logoutBox();
}
}
return handler.next(dioException);
}

Future<Response<dynamic>> _retry(RequestOptions requestOptions) {
final options = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);

return DioClient().dio.request<dynamic>(
requestOptions.path,
data: requestOptions.data,
queryParameters: requestOptions.queryParameters,
options: options,
);
}

Future<void> refreshToken() async {
/// Call API Refresh token
final response = await DioClient().postRequest(
ListAPI.generalToken,
data: {
'clientId': const String.fromEnvironment('USER_CLIENT_ID'),
'clientSecret': const String.fromEnvironment('USER_CLIENT_SECRET'),
'grantType': 'refresh_token',
'refreshToken': getData(MainBoxKeys.refreshToken),
},
converter: (response) =>
LoginResponse.fromJson(response as Map<String, dynamic>),
);

response.fold((l) => logoutBox(), (r) {
final data = r.data;
addData(
MainBoxKeys.refreshToken,
'${data?.tokenType} ${data?.refreshToken}',
);
addData(MainBoxKeys.authToken, '${data?.tokenType} ${data?.token}');
});
}

@override
Expand All @@ -66,4 +118,5 @@ class DioInterceptor extends Interceptor with FirebaseCrashLogger {
super.onResponse(response, handler);
}
}

// coverage:ignore-end
1 change: 1 addition & 0 deletions lib/core/api/list_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class ListAPI {

/// Auth
static const String generalToken = '/v1/api/auth/general';
static const String refreshToken = '/v1/api/auth/refresh';
static const String user = '/v1/api/user';
static const String login = '/v1/api/auth/login';
static const String logout = '/v1/api/auth/logout';
Expand Down
1 change: 1 addition & 0 deletions lib/features/auth/data/models/login_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ sealed class DataLogin with _$DataLogin {
const factory DataLogin({
@JsonKey(name: 'token') String? token,
@JsonKey(name: 'tokenType') String? tokenType,
@JsonKey(name: 'refreshToken') String? refreshToken,
}) = _DataLogin;

factory DataLogin.fromJson(Map<String, dynamic> json) =>
Expand Down
40 changes: 19 additions & 21 deletions lib/features/auth/data/repositories/auth_repository_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ class AuthRepositoryImpl implements AuthRepository {
Future<Either<Failure, Login>> login(LoginParams params) async {
final response = await authRemoteDatasource.login(params);

return response.fold(
(failure) => Left(failure),
(loginResponse) {
mainBoxMixin.addData(MainBoxKeys.isLogin, true);
mainBoxMixin.addData(
MainBoxKeys.authToken,
'${loginResponse.data?.tokenType} ${loginResponse.data?.token}',
);
return response.fold((failure) => Left(failure), (loginResponse) {
mainBoxMixin.addData(MainBoxKeys.isLogin, true);
mainBoxMixin.addData(
MainBoxKeys.authToken,
'${loginResponse.data?.tokenType} ${loginResponse.data?.token}',
);
mainBoxMixin.addData(
MainBoxKeys.refreshToken,
'${loginResponse.data?.tokenType} ${loginResponse.data?.refreshToken}',
);

return Right(loginResponse.toEntity());
},
);
return Right(loginResponse.toEntity());
});
}

@override
Expand All @@ -44,17 +45,14 @@ class AuthRepositoryImpl implements AuthRepository {
) async {
final response = await authRemoteDatasource.generalToken(params);

return response.fold(
(failure) => Left(failure),
(loginResponse) {
mainBoxMixin.addData(
MainBoxKeys.generalToken,
'${loginResponse.data?.tokenType} ${loginResponse.data?.token}',
);
return response.fold((failure) => Left(failure), (loginResponse) {
mainBoxMixin.addData(
MainBoxKeys.generalToken,
'${loginResponse.data?.tokenType} ${loginResponse.data?.token}',
);

return Right(loginResponse.toEntity());
},
);
return Right(loginResponse.toEntity());
});
}

@override
Expand Down
1 change: 1 addition & 0 deletions lib/utils/services/hive/main_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum ActiveTheme {
enum MainBoxKeys {
generalToken,
authToken,
refreshToken,
fcm,
language,
theme,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ void main() {
token:
'lazycatlabs.eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJhdXRoX2FwcCIsImlhdCI6MTY5OTExMDMxOSwiZXhwIjoxNjk5NzE1MTE5fQ.SlybkWOQs9JJpTG-tRYJapalESGNE63atPEfw6ry7NdcoZFkjYDAdlYfnIBlp9eUISAjSc7IUtvKGks0jEJ27V_iUBdKcS0aTVvtd8g1yW14UBGW6jKsOn9QtgxWnPELP0GZ1TRzObZW3bYAXpiVsC9o0LnONmq5ehMUgHVYknF_wTfHwSB2pb77pAZguwK4I9MI4BoqcvcuET36MEgYs9vY-e0f2y50nHN4kbjVe9iFay0GeNIRQsWzzmyN5Xd9Zv5HiSCgbB80UA6SrneoExBi-fNIlxrOxJRaVt16-1ElXu04W5Y_FIoY-jekmMWusE54csh3Woo6ChQQJEopfuU6prdP50TN7UpqiH_o3R77MdgcYBdJ-puZOt-XsplOHNAjDtp2rpo9UExQUlOVxQFuvSKkanxaOSsAXYuOaEh9iBoq0LQ_JiaIbrZBn7EVxKhFnUJokv7SvPMg2LG7p7wczgxYjnuxG0fDRRjK2vAQyAj0rIigd6xpA6g-ii5VWRsk_sMJw-QJW_ivZdQZwjlXeH-EcVeTaZ9yn2zmmavF6sxDxC1SDGGkbKjUpfIdQYa-t82sPO0HUd_OBQ8ZiBGmSV1gi-8lAat1XtJTgsgM0zTxqK5kwekc3gsoZfVdlhJ8SyN6ohyOMU8Hv8M2H7lG8u1DTq1jj7rb1BEtwGw',
tokenType: 'Bearer',
refreshToken:
'lazycatlabs.eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJhdXRoX2FwcCIsImlhdCI6MTY5OTExMDMxOSwiZXhwIjoxNjk5NzE1MTE5fQ.SlybkWOQs9JJpTG-tRYJapalESGNE63atPEfw6ry7NdcoZFkjYDAdlYfnIBlp9eUISAjSc7IUtvKGks0jEJ27V_iUBdKcS0aTVvtd8g1yW14UBGW6jKsOn9QtgxWnPELP0GZ1TRzObZW3bYAXpiVsC9o0LnONmq5ehMUgHVYknF_wTfHwSB2pb77pAZguwK4I9MI4BoqcvcuET36MEgYs9vY-e0f2y50nHN4kbjVe9iFay0GeNIRQsWzzmyN5Xd9Zv5HiSCgbB80UA6SrneoExBi-fNIlxrOxJRaVt16-1ElXu04W5Y_FIoY-jekmMWusE54csh3Woo6ChQQJEopfuU6prdP50TN7UpqiH_o3R77MdgcYBdJ-puZOt-XsplOHNAjDtp2rpo9UExQUlOVxQFuvSKkanxaOSsAXYuOaEh9iBoq0LQ_JiaIbrZBn7EVxKhFnUJokv7SvPMg2LG7p7wczgxYjnuxG0fDRRjK2vAQyAj0rIigd6xpA6g-ii5VWRsk_sMJw-QJW_ivZdQZwjlXeH-EcVeTaZ9yn2zmmavF6sxDxC1SDGGkbKjUpfIdQYa-t82sPO0HUd_OBQ8ZiBGmSV1gi-8lAat1XtJTgsgM0zTxqK5kwekc3gsoZfVdlhJ8SyN6ohyOMU8Hv8',
),
);

Expand All @@ -38,6 +40,8 @@ void main() {
'token':
'lazycatlabs.eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJhdXRoX2FwcCIsImlhdCI6MTY5OTExMDMxOSwiZXhwIjoxNjk5NzE1MTE5fQ.SlybkWOQs9JJpTG-tRYJapalESGNE63atPEfw6ry7NdcoZFkjYDAdlYfnIBlp9eUISAjSc7IUtvKGks0jEJ27V_iUBdKcS0aTVvtd8g1yW14UBGW6jKsOn9QtgxWnPELP0GZ1TRzObZW3bYAXpiVsC9o0LnONmq5ehMUgHVYknF_wTfHwSB2pb77pAZguwK4I9MI4BoqcvcuET36MEgYs9vY-e0f2y50nHN4kbjVe9iFay0GeNIRQsWzzmyN5Xd9Zv5HiSCgbB80UA6SrneoExBi-fNIlxrOxJRaVt16-1ElXu04W5Y_FIoY-jekmMWusE54csh3Woo6ChQQJEopfuU6prdP50TN7UpqiH_o3R77MdgcYBdJ-puZOt-XsplOHNAjDtp2rpo9UExQUlOVxQFuvSKkanxaOSsAXYuOaEh9iBoq0LQ_JiaIbrZBn7EVxKhFnUJokv7SvPMg2LG7p7wczgxYjnuxG0fDRRjK2vAQyAj0rIigd6xpA6g-ii5VWRsk_sMJw-QJW_ivZdQZwjlXeH-EcVeTaZ9yn2zmmavF6sxDxC1SDGGkbKjUpfIdQYa-t82sPO0HUd_OBQ8ZiBGmSV1gi-8lAat1XtJTgsgM0zTxqK5kwekc3gsoZfVdlhJ8SyN6ohyOMU8Hv8M2H7lG8u1DTq1jj7rb1BEtwGw',
'tokenType': 'Bearer',
'refreshToken':
'lazycatlabs.eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJhdXRoX2FwcCIsImlhdCI6MTY5OTExMDMxOSwiZXhwIjoxNjk5NzE1MTE5fQ.SlybkWOQs9JJpTG-tRYJapalESGNE63atPEfw6ry7NdcoZFkjYDAdlYfnIBlp9eUISAjSc7IUtvKGks0jEJ27V_iUBdKcS0aTVvtd8g1yW14UBGW6jKsOn9QtgxWnPELP0GZ1TRzObZW3bYAXpiVsC9o0LnONmq5ehMUgHVYknF_wTfHwSB2pb77pAZguwK4I9MI4BoqcvcuET36MEgYs9vY-e0f2y50nHN4kbjVe9iFay0GeNIRQsWzzmyN5Xd9Zv5HiSCgbB80UA6SrneoExBi-fNIlxrOxJRaVt16-1ElXu04W5Y_FIoY-jekmMWusE54csh3Woo6ChQQJEopfuU6prdP50TN7UpqiH_o3R77MdgcYBdJ-puZOt-XsplOHNAjDtp2rpo9UExQUlOVxQFuvSKkanxaOSsAXYuOaEh9iBoq0LQ_JiaIbrZBn7EVxKhFnUJokv7SvPMg2LG7p7wczgxYjnuxG0fDRRjK2vAQyAj0rIigd6xpA6g-ii5VWRsk_sMJw-QJW_ivZdQZwjlXeH-EcVeTaZ9yn2zmmavF6sxDxC1SDGGkbKjUpfIdQYa-t82sPO0HUd_OBQ8ZiBGmSV1gi-8lAat1XtJTgsgM0zTxqK5kwekc3gsoZfVdlhJ8SyN6ohyOMU8Hv8',
},
};

Expand Down
5 changes: 3 additions & 2 deletions test/helpers/stubs/login_response_200.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
},
"data": {
"token": "lazycatlabs.eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJhdXRoX2FwcCIsImlhdCI6MTY5OTExMDMxOSwiZXhwIjoxNjk5NzE1MTE5fQ.SlybkWOQs9JJpTG-tRYJapalESGNE63atPEfw6ry7NdcoZFkjYDAdlYfnIBlp9eUISAjSc7IUtvKGks0jEJ27V_iUBdKcS0aTVvtd8g1yW14UBGW6jKsOn9QtgxWnPELP0GZ1TRzObZW3bYAXpiVsC9o0LnONmq5ehMUgHVYknF_wTfHwSB2pb77pAZguwK4I9MI4BoqcvcuET36MEgYs9vY-e0f2y50nHN4kbjVe9iFay0GeNIRQsWzzmyN5Xd9Zv5HiSCgbB80UA6SrneoExBi-fNIlxrOxJRaVt16-1ElXu04W5Y_FIoY-jekmMWusE54csh3Woo6ChQQJEopfuU6prdP50TN7UpqiH_o3R77MdgcYBdJ-puZOt-XsplOHNAjDtp2rpo9UExQUlOVxQFuvSKkanxaOSsAXYuOaEh9iBoq0LQ_JiaIbrZBn7EVxKhFnUJokv7SvPMg2LG7p7wczgxYjnuxG0fDRRjK2vAQyAj0rIigd6xpA6g-ii5VWRsk_sMJw-QJW_ivZdQZwjlXeH-EcVeTaZ9yn2zmmavF6sxDxC1SDGGkbKjUpfIdQYa-t82sPO0HUd_OBQ8ZiBGmSV1gi-8lAat1XtJTgsgM0zTxqK5kwekc3gsoZfVdlhJ8SyN6ohyOMU8Hv8M2H7lG8u1DTq1jj7rb1BEtwGw",
"tokenType": "Bearer"
"tokenType": "Bearer",
"refreshToken": "lazycatlabs.eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJhdXRoX2FwcCIsImlhdCI6MTY5OTExMDMxOSwiZXhwIjoxNjk5NzE1MTE5fQ.SlybkWOQs9JJpTG-tRYJapalESGNE63atPEfw6ry7NdcoZFkjYDAdlYfnIBlp9eUISAjSc7IUtvKGks0jEJ27V_iUBdKcS0aTVvtd8g1yW14UBGW6jKsOn9QtgxWnPELP0GZ1TRzObZW3bYAXpiVsC9o0LnONmq5ehMUgHVYknF_wTfHwSB2pb77pAZguwK4I9MI4BoqcvcuET36MEgYs9vY-e0f2y50nHN4kbjVe9iFay0GeNIRQsWzzmyN5Xd9Zv5HiSCgbB80UA6SrneoExBi-fNIlxrOxJRaVt16-1ElXu04W5Y_FIoY-jekmMWusE54csh3Woo6ChQQJEopfuU6prdP50TN7UpqiH_o3R77MdgcYBdJ-puZOt-XsplOHNAjDtp2rpo9UExQUlOVxQFuvSKkanxaOSsAXYuOaEh9iBoq0LQ_JiaIbrZBn7EVxKhFnUJokv7SvPMg2LG7p7wczgxYjnuxG0fDRRjK2vAQyAj0rIigd6xpA6g-ii5VWRsk_sMJw-QJW_ivZdQZwjlXeH-EcVeTaZ9yn2zmmavF6sxDxC1SDGGkbKjUpfIdQYa-t82sPO0HUd_OBQ8ZiBGmSV1gi-8lAat1XtJTgsgM0zTxqK5kwekc3gsoZfVdlhJ8SyN6ohyOMU8Hv8"
}
}
}
Loading