An token refresh library for dart. This library consists of a collection of packages which specialize in a particular aspect of token refresh.
Package | Pub |
---|---|
fresh | |
fresh_dio | |
fresh_graphql |
๐ A token refresh library for Dart.
Home Page: https://github.com/felangel/fresh
An token refresh library for dart. This library consists of a collection of packages which specialize in a particular aspect of token refresh.
Package | Pub |
---|---|
fresh | |
fresh_dio | |
fresh_graphql |
Hello,
Thank you for writing this package, so far it works great for me.
I have found a minor issue using it with Dio: When an error occurs during the request Fresh checks the response object whether it should refresh, specifically response.statusCode
. However the response
object can be null
thus causing noSuchMethod
.
Checking the dio_error.dart file:
/// Response info, it may be `null` if the request can't reach to
/// the http server, for example, occurring a dns error, network is not available.
Response response;
In my case it also occured during a connection timeout error. This will cause the original DioError.CONNECT_TIMEOUT
to be changed to DioErrorType.DEFAULT
so it will result in a different error type caught in the code when I want to display a timeout message.
A simple null aware change could make it work like intended I think. Adding the question mark to fresh.dart:148.
static bool _defaultShouldRefresh(Response response) {
return response?.statusCode == 401;
}
Full exception log:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1 Fresh._defaultShouldRefresh (package:fresh_dio/src/fresh.dart:148:21)
#2 Fresh.onError (package:fresh_dio/src/fresh.dart:110:42)
#3 DioMixin._request._errorInterceptorWrapper.<anonymous closure> (package:dio/src/dio.dart:867:40)
#4 _rootRunUnary (dart:async/zone.dart:1198:47)
#5 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
#6 _FutureListener.handleError (dart:async/future_impl.dart:160:20)
#7 Future._propagateToListeners.handleError (dart:async/future_impl.dart:708:47)
#8 Future._propagateToListeners (dart:async/future_impl.dart:729:24)
#9 Future._completeError (dart:async/future_impl.dart:537:5)
#10 _SyncCompleter._completeError (dart:async/future_impl.dart:59:12)
#11 _Completer.completeError (dart:async/future_impl.dart:31:5)
#12 Future.any.onError (dart:async/future.dart:476:45)
#13 _rootRunBinary (dart:async/zone.dart:1214:47)
#14 _CustomZone.runBinary (dart:async/zone.dart:1107:19)
#15 _FutureListener.handleError (dart:async/future_impl.dart:157:20)
#16 Future._propagateToListeners.handleError (dart:async/future_impl.dart:708:47)
#17 Future._propagateToListeners (dart:async/future_impl.dart:729:24)
#18 Future._completeError (dart:async/future_impl.dart:537:5)
#19 _AsyncAwaitCompleter.completeError (dart:async-patch/async_patch.dart:47:15)
#20 DioMixin._dispatchRequest (package:dio/src/dio.dart)
<asynchronous suspension>
#21 DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:dio/src/dio.dart:849:37)
#22 DioMixin.checkIfNeedEnqueue (package:dio/src/dio.dart:1117:22)
#23 DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure> (package:dio/src/dio.dart:846:22)
#24 new Future.<anonymous closure> (dart:async/future.dart:174:37)
#25 _rootRun (dart:async/zone.dart:1182:47)
#26 _CustomZone.run (dart:async/zone.dart:1093:19)
#27 _CustomZone.runGuarded (dart:async/zone.dart:997:7)
#28 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
#29 _rootRun (dart:async/zone.dart:1190:13)
#30 _CustomZone.run (dart:async/zone.dart:1093:19)
#31 _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1021:23)
#32 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#33 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:397:19)
#34 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:428:5)
#35 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
fresh version: 0.2.1
fresh_dio version: 0.1.1
dio version: 3.0.9
I could also submit a pull request if you want. Please let me know what you think.
They just released new dio version 5.0.0 and this package depends on dio version ^4.0.0.
I can't refresh token because of i use accessToken for API refreshToken for refreshing token but _httpClient interceptors using Fresh. There are changed headers Authorization only for refreshToken. How to solve this problem?
Could be nice to see grpc refresh token implementation:
https://pub.dev/packages/grpc
Code to reproduce:
import 'package:fresh/fresh.dart';
class InMemoryDelayed extends InMemoryTokenStorage<String> {
@override
Future<String?> read() async {
await Future<dynamic>.delayed(Duration.zero);
return super.read();
}
}
class Foo with FreshMixin<String> {
Foo(InMemoryDelayed tokenStorage) {
this.tokenStorage = tokenStorage;
}
}
void main() async {
var storage = InMemoryDelayed();
await storage.write('token');
var foo = Foo(storage);
var token = await foo.token;
print(token); // prints null, but should print 'token'
}
The token
method is not waiting for the first read from storage to complete.
https://github.com/felangel/fresh/blob/master/packages/fresh/lib/src/fresh.dart#L110 this line can be changed to
await authenticationStatus.firstWhere((status) => status != AuthenticationStatus.initial);
I can put up a PR if you think this fix is appropriate.
Can't figure out how to implement a shouldRefresh
condition.
shouldRefresh: (response) => response.errors... // somehow check if a token is stale ?
I am using this library like this:
Future<GraphQLClient> _getClient({
required final String path,
final String? accessToken,
}) async {
// final Link link = HttpLink(path);
// Link link = HttpLink(path, httpClient: LoggerHttpClient(http.Client()));
// if (accessToken != null) {
// link = AuthLink(getToken: () async => 'Bearer $accessToken').concat(
// HttpLink(
// path,
// ),
// );
// }
final freshLink = FreshLink.oAuth2(
shouldRefresh: (final _) {
debugPrint(_.errors.toString());
debugPrint(_.data.toString());
return true;
},
tokenStorage: InMemoryTokenStorage(),
refreshToken: (final token, final client) {
// Perform refresh and return new token
debugPrint('--------->refreshToken $token');
return Future.value();
},
)..authenticationStatus.listen(
(final data) {
debugPrint('---------> authenticationStatus $data');
},
onError: (final err) {
debugPrint('--------->onError $err');
},
);
await freshLink.setToken(OAuth2Token(accessToken: 'Bearer $accessToken'));
return getClient = GraphQLClient(
link: Link.from([freshLink, HttpLink(path)]),
cache: GraphQLCache(store: InMemoryStore()),
);
}
Future<QueryResult> query({
required final String mainPath,
required final String query,
final Function(Map<String, dynamic>? result)? action,
final Function(String message)? error,
final bool requiredToken = true,
final bool showDialogResend = false,
final Map<String, dynamic>? params,
final FetchPolicy fetchPolicy = FetchPolicy.networkOnly,
}) async {
// try {
// Get token from local storage if required token is true
UserTokenModel? token;
if (requiredToken) {
token = UserStoreService.to.getToken();
}
final client = await _getClient(
path: mainPath,
accessToken: requiredToken ? token!.accessToken : null,
);
return client.query(
QueryOptions(
document: gql(query),
fetchPolicy: fetchPolicy,
variables: params ?? {},
),
);
}
But just this print is printed:
---------> authenticationStatus AuthenticationStatus.unauthenticated
and other callback not triggered?
I am using unit test to test that?
Hey Felix, thanks for this repo. It is possible to make spesific example with reflesh token. This examples doesn't look like real time example. The title is reflesh token this cause api should auth api but in this example the api irrelevant with auth.
Currently this library uses updateContextEntry
incorrectly in the latest version of flutter_graphql. The function updateContextEntry
now no longer mutates the request object, but is a pure function. Now it returns the new request that you need to pass to the forward function.
Essentially this bit of the code:
request.updateContextEntry<HttpLinkHeaders>(
(headers) => HttpLinkHeaders(
headers: {
...headers?.headers ?? <String, String>{},
}..addAll(tokenHeaders),
),
);
Does nothing. It should be:
final newRequest = request.updateContextEntry<HttpLinkHeaders>(
(headers) => HttpLinkHeaders(
headers: {
...headers?.headers ?? <String, String>{},
}..addAll(tokenHeaders),
),
);
And the you need to work with newRequest down the line. The result of this mistake makes it so that the authorization header is not put in the context and authentication fails.
Great idea this package and exactly what I was looking for. ๐
Based on this idea it would be possible to create a similar link implementation for subscription.
What do you think?
This is my current code that could be replaced
class WSLink extends Link {
/// Creates a new [WebSocketLink] instance with the specified config.
WSLink(
this.url, {
this.headers = const {},
this.getToken,
});
final String url;
final Map<String, dynamic> headers;
final Future<String?>? getToken;
// cannot be final because we're changing the instance upon a header change.
SocketClient? _socketClient;
@override
Stream<Response> request(Request request, [forward]) async* {
// Get Token or Refresh
if (getToken != null) {
var token = await getToken;
headers['Authorization'] = 'Bearer $token';
}
if (_socketClient == null) {
connectOrReconnect();
}
yield* _socketClient!.subscribe(request, true);
}
/// Connects or reconnects to the server with the specified headers.
void connectOrReconnect() {
_socketClient?.dispose();
_socketClient = SocketClient(
url,
config: SocketClientConfig(
inactivityTimeout: Duration(seconds: 30),
delayBetweenReconnectionAttempts: const Duration(seconds: 2),
autoReconnect: true,
connect: (url, protocols) => IOWebSocketChannel.connect(
url,
protocols: protocols,
headers: headers,
),
),
);
}
/// Disposes the underlying socket client explicitly. Only use this, if you want to disconnect from
/// the current server in favour of another one. If that's the case, create a new [WebSocketLink] instance.
Future<void> dispose() async {
await _socketClient?.dispose();
_socketClient = null;
}
}
Still have troubles to upgrade to new flutter
Because fresh_graphql >=0.3.0 depends on fresh ^0.3.0 and fresh_dio >=0.3.0-nullsafety.0 depends on fresh ^0.4.0, fresh_graphql >=0.3.0 is incompatible with fresh_dio >=0.3.0-nullsafety.0.
Hi @felangel ,
I want to request a new Feature for fresh. I need to request the token before i got response error.
Which i need to check the request and token expired or not from the timestamp that i got from login.
Example for the json :
{ "token" : "aZakRij123", "validUntil" : "2021-12-25T00:00:00" }
I looked at the code, maybe I missed it and I apologise, but it would be better if the library had an option to retry the same http request if it detected a token refresh was necessary (e.g. Got a 401 error, did a refresh, then send the old request again with the new token).
Also include a timer to try to refresh before expiring, so there's less chance for an http request will fail.
Hi,
I am using this package for a while and have copied code from my prev project. But with updated version i do not see headers comming to server:
My code looks like this:
_freshLink = FreshLink<OAuth2Token>(
tokenStorage: _inMemoryTokenStorage,
refreshToken: _refreshToken,
tokenHeader: _tokenHeader,
shouldRefresh: (gql_exec.Response result) {
if (!hasToken) {
return false;
}
return true;
},
)..authenticationStatus.listen(print);
Map<String, String> _tokenHeader(OAuth2Token token) {
if (token.accessToken == '') {
return {};
}
return {'authorization': '${token.tokenType} ${token.accessToken}', 'requestId': uuid.v1()};
}
Any ideas?
I am using:
fresh_graphql: ^0.4.0
fresh_dio: ^0.2.0
Hello, I have been using fresh_dio package for a Dio http client. I was able to implement the refreshToken
+ shouldRefresh
functions, and it is refreshing the token whenever the http call fail based on my conditions.
However, the initial fired request does fail. Is it possible to retry the request after refreshing the token? If not, what best approach to handle this? Should I create an interceptor which check the token expiration time before firing the request?
At the first time I do setToken(), status always turns to "unauthorized".
After refresh it becomes authorized.
code:
final freshLink = FreshLink.oAuth2(
tokenStorage: InMemoryTokenStorage(),
refreshToken: (token, client) async {
// Perform refresh and return new token
log.d('refreshing token!');
return OAuth2Token(accessToken: accessToken, tokenType: 'Bearer'); // TODO refresh with RefreshToken
},
shouldRefresh: (response) => true, // TODO implement
)..authenticationStatus.listen(log.d);
await freshLink.setToken(OAuth2Token(accessToken: accessToken, tokenType: 'Bearer'));
I debugged it and cannot find an issue, but the problem is definitely in setToken()
.
I await it and suddenly execution goes to void _updateStatus
where token
is null !
Please update the example code using bloc 8.0.0 thanks
If I have 4 network requests and response is UNAUTHENTICATED, shouldRefresh will return true, all of requests will call refreshToken() ()to get the new OAuth2Token then retry the request.
Is it possible to call refreshToken only first time then retry all request?
Hi,
Thanks for the work,
What about concurrent calls ? I mean let's say for the exemple :
@override
void initState() {
super.initState();
<ApiCall_1>
<ApiCall_2>
<ApiCall_3>
}
Won't this provoke refresh tokens infinite loop ?
How to handle this ?
How does your lib handle this ?
'cause Queue.lock( ) / .unlock( ) that would intervene on [ Error 401 ("token-expired") ] would trigger too late so...
Thanks in advance (^_^)
Hi there! This is by far the most intuitive package for refreshing tokens in graphql. Here is one enhancement that I would like to see:
in a case like this:
refreshToken: (token, client) async {
await tokenManager.refresh();
}
if there is an error in refreshing the token, it is not possible to get it to the client; any thrown error here is "swallowed". If the api looked something like this:
refreshToken: (token, client, resp) async {
await tokenManager.refresh();
}
then you could do something like:
refreshToken: (token, client, resp) async {
try {
await tokenManager.refresh();
} catch {
resp.errors.add(GraphQLError(message: 'Error refreshing access tokens`));
}
}
and then it would be easy to log the user out or similar in a situation like this. Right now, I'm just assuming if response.hasErrors && response.errors == null
then this is an error in the refresh token, which is obviously not a robust assumption (as it's possible that the response would have errors but no errors have been added for other reasons.)
I don't think it's an unfair assumption to believe that for some reason, a token refresh could fail: for instance, if a refresh token is compromised manually resetting it on the server would cause the refresh to fail; in a situation like this, I probably want to log the user out.
Thanks for the package! It helps a lot in my development.
I think dio authors will soon deprecate interceptors.
See cfug/dio#1308
When using fresh_graphql: ^0.3.0
and graphql_flutter: ^4.0.0-beta.3
I get in the console:
Because every version of fresh_graphql depends on graphql ^3.0.2 and graphql_flutter 4.0.0-beta.3 depends on graphql ^4.0.0-beta.3, fresh_graphql is incompatible with graphql_flutter 4.0.0-beta.3.
And because no versions of graphql_flutter match >4.0.0-beta.3 <5.0.0, fresh_graphql is incompatible with graphql_flutter ^4.0.0-beta.3.
I guess a bump of the version of graphql
that fresh_graphql
uses is needed (or sit tight until 4.0.0 releases)? Currently getting around it by overriding dependencies
dependency_overrides:
graphql: ^4.0.0-beta.3
The app won't compile with the dependency override.
Hi!
I'm using ur package pretty easily so thank you for the work.
But I have a question for using local storage.
I have to store token at local storage because of autoSignIn feature and AuthBloc do that when
authentication event occurs.
But since the current package uses authenticationStatus
enum for authentication state event,
token cannot be passed to the AuthBloc.
So, I created an authenticated event object that can hold a token and made AuthBloc subscribe to it.
Is this the right way? What's a good way to refresh the token and store it in storage at the same time?
I have this error when trying to use latest graphql_flutter: "Because graphql_flutter >=4.0.0 depends on graphql ^4.0.0 and fresh_graphql <0.4.0-dev.1 depends on graphql ^3.0.2, graphql_flutter >=4.0.0 is incompatible with fresh_graphql <0.4.0-dev.1."
It is just an idea, but can the dependency be changed from graphql to gql_link? Is it even possible?
The maintainer of ferry mentioned it in a comment: gql-dart/ferry#296
Any thoughts about the comment?
Currently fresh allows the user to store tokens in which every token storage they may choose.
What if fresh acted a little bit like HydratedBloc and provided a HydratedBlocStorage which is based on Hive.
And additionally the end user should be given an option to extend HydratedStorage to use whichever storage media they choose.
For my private project I have extended OAuth2Token and created a new Token class and corresponding type adapter which can be stored in Hive.
And Extended TokenStorage to create a HiveTokenStorage.
Hi @felangel Dio treats HTTP codes above 400 as DioError so the code in
Future<dynamic> onResponse(Response response) async { ... }
won't be executed when OAuth server responds with code 401.
It should be handled in Future<dynamic> onError(DioError error) async { ... }
.
Sorry if the issue is badly formatted, first one I ever wrote :)
Thanks for the package!
I'm trying to test an API that uses the fresh_dio passing a HttpClientAdapter Mock as a dio.httpClientAdapter.
I can make some tests pass. Like this one:
`final httpPostResponse = ResponseBody.fromString(
responseData,
200,
headers: {
Headers.contentTypeHeader: [Headers.jsonContentType],
},
);
when(
() => mockDioAdapter.fetch(
any(
that: isA<RequestOptions>().having(
(options) => options.method,
'POST method',
equals('POST'),
),
),
any(),
any(),
),
).thenAnswer((_) async => httpPostResponse);
await meuEspacoApiClient.authenticate(
username: 'username',
password: 'password',
);
expect(
meuEspacoApiClient.authenticationStatus,
emitsAnyOf([AuthenticationStatus.authenticated]),
);
verify(
() => mockDioAdapter.fetch(
any(
that: isA<RequestOptions>()
.having(
(options) => options.method,
'POST method',
equals('POST'),
)
.having(
(options) => options.baseUrl,
'Base URL',
equals('http://localhost:8080'),
),
),
any(),
any(),
),
).called(1);`
But when I return a response with a status code equals to 401, then a refresh function is not called and the following DioException occurs:
DioException [bad response]: The request returned an invalid status code of 401.
If I handle the exception it not trigger the refresh token even.
I'm using this package wrong or missing something?
Please, help me.
My team and i are working on a mobile app that sometimes requires us to send multiple images to a nodejs server using FormData and MutipartFiles (part of the dio package) and recently we had to transition from sessions and cookies to Jwt tokens. and the transition was really seamless thanks to your package that honestly saved me days of work at least
but i ran into a problem and here's the scenario for simplicity
DioError [DioErrorType.other]: Bad state: Can't finalize a finalized MultipartFile.
(i will put the entire error message below)after doing some research on the matter i found out that you have to basically make a new FormData object out of the old FormData if you want to resend a request
a link of the most promising solution i found cfug/dio#482
here is how i implemented your interceptor
static final _fresh = Fresh.oAuth2(
tokenStorage: PersistantTokenStorage(),
refreshToken: (token, client) async {
print('refreshing token...');
try {
var response = await AuthService.getAcessToken(token?.refreshToken);
print("refresh response");
print(response);
String? newAccessToken = response['accessToken'];
if (newAccessToken == null)
throw RevokeTokenException();
return OAuth2Token(
accessToken: newAccessToken,
refreshToken: token?.refreshToken,
);
} catch (e) {
print(e);
throw RevokeTokenException();
}
},
);
and here's the full error code with some of the print
s that i did included (note that aside from MultipartFiles no other problem has occured
I/flutter (10036): refreshing token...
I/flutter (10036): refresh token response = {accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjEwNjk4YmUzMDUxNTEzNjg4ZTRmMWIyIiwiZW1haWwiOiJ0dW5pc2lhbmNhbXBlcnNAZ21haWwuY29tIn0sImlhdCI6MTYzMDA5MTU1NSwiZXhwIjoxNjMwMDkxNTg1fQ.mU32DL3-b-pq_OyEyA4gdnhTrlE0tgXwSGjWGN-pfug}
I/flutter (10036): refresh response
I/flutter (10036): {accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjEwNjk4YmUzMDUxNTEzNjg4ZTRmMWIyIiwiZW1haWwiOiJ0dW5pc2lhbmNhbXBlcnNAZ21haWwuY29tIn0sImlhdCI6MTYzMDA5MTU1NSwiZXhwIjoxNjMwMDkxNTg1fQ.mU32DL3-b-pq_OyEyA4gdnhTrlE0tgXwSGjWGN-pfug}
I/flutter (10036): saving token
I/flutter (10036): DioError [DioErrorType.other]: Bad state: Can't finalize a finalized MultipartFile.
I/flutter (10036): #0 FormData.finalize
package:dio/src/form_data.dart:134
I/flutter (10036): #1 DioMixin._transformData
package:dio/src/dio_mixin.dart:706
I/flutter (10036): #2 DioMixin._dispatchRequest
package:dio/src/dio_mixin.dart:631
I/flutter (10036): #3 DioMixin.fetch.<anonymous closure>
package:dio/src/dio_mixin.dart:585
I/flutter (10036): #4 DioMixin.fetch._requestInterceptorWrapper.<anonymous closure>.<anonymous closure>.<anonymous closure>
package:dio/src/dio_mixin.dart:502
I/flutter (10036): #5 DioMixin.checkIfNeedEnqueue
package:dio/src/dio_mixin.dart:795
I/flutter (10036): #6 DioMixin.fetch._requestInterceptorWrapper.<anonymous closure>.<anonymous closure>
package:dio/src/dio_mixin.dart:500
I/flutter (10036): #7 new Future.<anonymous closure> (dart:async/future.dart:174:37)
I/flutter (10036): #8 _rootRun (dart:async/zone.dart:1346:47)
I/flutter (10036): #9 _CustomZone.run (dart:async/zone.dart:1258:19)
I/flutter (10036): #10 _CustomZone.runGuarded (dart:async/zone.dart:1162:7)
I/flutter (10036): #11 _CustomZone.bindCallbackGuarded.<anonymous clos
some information about the environment and versions used
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
dio: ^4.0.0
fresh_dio: ^0.3.1
if more information could be provided i d be more than happy to provide them
Does this package support sending multiple concurrent not valid access token at the same time?
Scenario:
pubspec.yaml
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
dio: ^4.0.0
fresh: ^0.4.0
fresh_dio: ^0.3.0-nullsafety.0
Flutter doctor:
Doctor summary (to see all details, run flutter doctor -v):
[โ] Flutter (Channel stable, 2.0.4, on macOS 11.2.3 20D91 darwin-x64, locale
ru-RU)
[โ] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[โ] Xcode - develop for iOS and macOS
[โ] Chrome - develop for the web
[โ] Android Studio (version 4.1)
[โ] VS Code (version 1.54.3)
[โ] Connected device (1 available)
โข No issues found!
Trying to build project:
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:77:19: Error: The method 'Fresh.onRequest' has fewer positional arguments than those of overridden method 'Interceptor.onRequest'.
Future<dynamic> onRequest(RequestOptions options) async {
^
../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/interceptor.dart:216:8: Context: This is the overridden method ('onRequest').
void onRequest(
^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:87:19: Error: The method 'Fresh.onResponse' has fewer positional arguments than those of overridden method 'Interceptor.onResponse'.
Future<dynamic> onResponse(Response response) async {
^
../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/interceptor.dart:231:8: Context: This is the overridden method ('onResponse').
void onResponse(
^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:95:19: Error: The method 'Fresh.onError' has fewer positional arguments than those of overridden method 'Interceptor.onError'.
Future<dynamic> onError(DioError err) async {
^
../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/interceptor.dart:248:8: Context: This is the overridden method ('onError').
void onError(
^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:113:9: Error: No named parameter with the name 'request'.
request: response?.request,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/dio_error.dart:27:3: Context: Found this candidate, but the arguments don't match.
DioError({
^^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:120:46: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
_httpClient.options.baseUrl = response.request.baseUrl;
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:122:18: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
response.request.path,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:123:31: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
cancelToken: response.request.cancelToken,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:124:24: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
data: response.request.data,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:125:37: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
onReceiveProgress: response.request.onReceiveProgress,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:126:34: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
onSendProgress: response.request.onSendProgress,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:127:35: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
queryParameters: response.request.queryParameters,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:129:28: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
method: response.request.method,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:130:33: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
sendTimeout: response.request.sendTimeout,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:131:36: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
receiveTimeout: response.request.receiveTimeout,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:132:27: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
extra: response.request.extra,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:133:29: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
headers: response.request.headers,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:134:34: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
responseType: response.request.responseType,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:135:33: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
contentType: response.request.contentType,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:136:36: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
validateStatus: response.request.validateStatus,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:138:24: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
response.request.receiveDataWhenStatusError,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:139:37: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
followRedirects: response.request.followRedirects,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:140:34: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
maxRedirects: response.request.maxRedirects,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:141:36: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
requestEncoder: response.request.requestEncoder,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:142:37: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
responseDecoder: response.request.responseDecoder,
^^^^^^^
../../flutter/.pub-cache/hosted/pub.dartlang.org/fresh_dio-0.3.0-nullsafety.0/lib/src/fresh.dart:143:32: Error: The getter 'request' isn't defined for the class 'Response<dynamic>'.
- 'Response' is from 'package:dio/src/response.dart' ('../../flutter/.pub-cache/hosted/pub.dartlang.org/dio-4.0.0/lib/src/response.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'request'.
listFormat: response.request.listFormat,
^^^^^^^
Failed to compile application.
Exited (sigterm)
Hello,
I think I may have found an issue with response and error handling on token revoke with fresh_dio. The scenario:
Fresh.onError()
to be called. Right now a token is set and the status is 401 so _tryRefresh()
will be called.RevokeTokenException
is thrown (e.g. user/refresh token was deleted).RevokeTokenException
is caught inside _tryRefresh
, handles token deletion from storage and then returns the original Response
(response with the GET request, status 401).DioError
but a Response
.For example in my code this would cause an error during converting from an expected response data to a Dart type because it is missing some fields.
I would suggest to return the original DioError
in case of RevokeTokenException
, this way the error can be handled during the original call. Let me know what you think about this.
Thanks!
Hi, I have a situation when i was sure that this peace is working. However today i have realized that it is not. I checked your example and implement things similarly. However I am not sure what server has to return to make it work. Now from server I am returning pure gql error (when calling via postman)
{
"errors": [
{
"message": "Context creation failed: Authentication token is invalid, please log in",
"extensions": {
"code": "UNAUTHENTICATED",
"exception": {
"stacktrace": [
"AuthenticationError: Context creation failed: Authentication token is invalid, please log in",
" at ApolloServer.context (D:\\SenioAssist\\senioassit-api\\src\\main.ts:71:23)",
" at runMicrotasks (<anonymous>)",
" at processTicksAndRejections (internal/process/task_queues.js:93:5)"
]
}
}
}
]
}
And i am sure that previously this error was caught by fresh plugin however now it is not. And i see this error in console
Caught error: OperationException(linkException: ServerException(originalException: null, parsedResponse: Response(data: null, errors: [GraphQLError(message: Context creation failed: Authentication token is invalid, please log in, locations: null, path: null, extensions: {code: UNAUTHENTICATED, exception: {stacktrace: [AuthenticationError: Context creation failed: Authentication token is invalid, please log in, at ApolloServer.context (D:\SenioAssist\senioassit-api\src\main.ts:71:23), at runMicrotasks (<anonymous>), at processTicksAndRejections (internal/process/task_queues.js:93:5)]}})], context: Context({ResponseExtensions: Instance of 'ResponseExtensions'}))), graphqlErrors: [])
I am not sure why server error is not wrapped into linkException rather than graphqlErrors.
Maybe I am returning it incorrect from server? maybe it is because when I am throwing AuthenticationError from Apollo server it gives 400 (bad request) instead of 200 along with error mentioned above?
Hi. Thanks for your packages.
I looked through the packages and did not find handling of the case when additional requests occuring while refreshing is in progress.
Technically it could lead to multiple refresh requests...
Some servers could apply blocking policy for the cases when flood
refresh occurs.
May be I'm missing something...
Is this by design or place for improvement
Today I have upgraded flutter and packages and finally i get this error:
Because fresh_dio 0.2.0 depends on dio ^3.0.9 and no versions of fresh_dio match >0.2.0 <0.3.0, fresh_dio ^0.2.0 requires dio ^3.0.9.
So, because senio_assist depends on both dio ^4.0.0-beta6 and fresh_dio ^0.2.0, version solving failed.
pub get failed (1; So, because senio_assist depends on both dio ^4.0.0-beta6 and fresh_dio ^0.2.0, version solving fa
../../../../.pub-cache/git/mobileapp-cn-components-service-0c9a9b528758740e21a494e36669d299535ffc7e/mobileapp_cn_core_networking/lib/mobileapp_cn_core_networking.dart:11:1: Error: Can't export this file because it contains a 'part of' declaration.
export 'package:dio/src/interceptor.dart';
^^^^^^
../../../../.pub-cache/hosted/pub.dartlang.org/dio-4.0.3/lib/src/interceptor.dart: Context: This is the file that can't be exported.
What is the recommended way to log refresh and retry requests? Or adding other kinds of interceptors to those requests.
There is currently a pitfall that causes requests to stall indefinitely when passing the same httpClient into the Fresh constructor that later has the Fresh interceptor attached to it.
Could it be a good idea to rename the parameter, adding documentation or to add an assert that the Dio instance passed in should not have a Fresh interceptor?
In the lastest 0.3.0 null-safety release, the Dio client is locked from making requests before refreshing the token.
Sadly this also includes the token refresh request itself.
Also see: https://github.com/felangel/fresh/pull/39/files#r614289083
There is an open PR #45 which removes the locking.
In general I think it is a good idea to lock the instance but it should occur after the refresh request is sent and unlocked after the response is received and the token is stored.
As a workaround a new Dio instance can be constructed.
although I had reed the document and example but I cant figure out how to use the package
there is lots of complexity in the code . some one who dont know what is bloc and ... its hard to use and figure out how to use
please make simpler example to understand
Hi! First of all thanks for creating this package, this has been pretty helpful in my projects.
I've implemented the Fresh interceptor with a custom TokenStorage based on FlutterSecureStorage. After a successful login I call the write
method on the TokenStorage. When restarting the app, I would expect the token to be retrieved from the store before being returned, but the token is being returned while the Fresh is still in the idle status. This returns in an empty token, which will only later be updated after the authenticationStatus changes.
final StreamController<AuthenticationStatus> _controller =
StreamController<AuthenticationStatus>.broadcast()
..add(AuthenticationStatus.initial);
/// Setter for the [TokenStorage] instance.
set tokenStorage(TokenStorage<T> tokenStorage) {
_tokenStorage = tokenStorage..read().then(_updateStatus);
}
/// Returns the current token.
Future<T?> get token async {
if (_authenticationStatus != AuthenticationStatus.initial) return _token;
await authenticationStatus.first;
return _token;
}
The await authenticationStatus.first;
now actually awaits the AuthenticationStatus.initial
status while I would expect any status that is not an AuthenticationStatus.initial
.
In my project I fixed this by adding the following:
/// Ensures that fresh is initialized and not longer loading the token.
Future<void> ensureReady() async {
await fresh.authenticationStatus.firstWhere(
(authenticationStatus) => authenticationStatus != AuthenticationStatus.initial,
);
}
I think it would be nice if the token
getter awaits the first authenticationStatus
that is not AuthenticationStatus.initial
/// Returns the current token.
Future<T?> get token async {
if (_authenticationStatus != AuthenticationStatus.initial) return _token;
await fresh.authenticationStatus.firstWhere(
(authenticationStatus) => authenticationStatus != AuthenticationStatus.initial,
);
return _token;
}
Reproduction example: https://github.com/LukasMirbt/fresh_dio_example
Reproduction steps:
Run the app, log in and wait for the photos to load.
All changes from the default example are in the jsonplaceholder_client package.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.