Coder Social home page Coder Social logo

felangel / fresh Goto Github PK

View Code? Open in Web Editor NEW
300.0 8.0 53.0 438 KB

๐Ÿ‹ A token refresh library for Dart.

Home Page: https://github.com/felangel/fresh

Kotlin 0.15% Swift 0.49% Objective-C 0.05% Dart 94.59% HTML 4.73%
dart dartlang dart-package dart-library http oauth oauth2 token-refresh dio interceptor

fresh's Introduction

Fresh ๐Ÿ‹

ci coverage License: MIT


An token refresh library for dart. This library consists of a collection of packages which specialize in a particular aspect of token refresh.

Packages

Package Pub
fresh pub package
fresh_dio pub package
fresh_graphql pub package

fresh's People

Contributors

2shrestha22 avatar cmengler avatar daniellampl avatar dricholm avatar fabiancrx avatar felangel avatar narcodico avatar pbissonho avatar rexios80 avatar rtviwe avatar sebastianklaiber avatar tuanvugoodmoney avatar vytautas-pranskunas- avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fresh's Issues

[fresh_grahpql] Multiple request

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?

refreshToken not called at all?

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?

[fresh_graphql] wrong auth status

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 !

Can't finalize a finalized MultipartFile

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

  • i send a post request where data is of type FormData that contains multiple files or MultipartFiles
  • the token has expired so your package requests a new accessToken (as far as i understand)
  • a new accessToken is received and the request is resent
  • a Dio error is thrown 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 prints 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

fresh_dio - Dio instance locked

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.

Exception on non-response Dio error

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.

can't figure out how to use package!

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

should be able to inject error into response if token refreshing fails

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.

store token at local storage

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?

Can you upgrade dependencies for dio

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

Package dependency should be updated

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."

fresh_graphql is incompatible with graphql_flutter 4.0.0-beta.3

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.

[fresh_graphql] Link for Subscription

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;
  }
}

[fresh_graphql] wrong Bearer case

  1. OAuth2Token has "bearer" token type by default
    but the proper name is "Bearer"
    Case is crucial here, I cannot perform the request with "bearer". I debugged for an hour to find a reason!
  2. Auth header name is "authorization", but it should be "Authorization". It doesn't really matter, header "authorization" has been accepted too, but it's still not correct.

[fresh_dio] Recommended way to log refresh and retry requests

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?

Can't run the example app

Hi Felix,
When I try to run the app I get the following errors :
image

image

So unfortunately the app can't run at all. I think in the Pull Request you did but not merge yet there might a fix to the problem since it seems to come from package dart:html mostly.
Lmk if I can help in any way!

Auto retry after refresh token?

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?

fresh_dio: Response on RevokeTokenException

Hello,

I think I may have found an issue with response and error handling on token revoke with fresh_dio. The scenario:

  1. A GET request fails with 401. This will result in Fresh.onError() to be called. Right now a token is set and the status is 401 so _tryRefresh() will be called.
  2. When refreshing the token a RevokeTokenException is thrown (e.g. user/refresh token was deleted).
  3. RevokeTokenException is caught inside _tryRefresh, handles token deletion from storage and then returns the original Response (response with the GET request, status 401).
  4. The original GET request will get the error response as a successful response, not a 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!

not enough examples

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.

How to test fresh_dio with a Mock HttpClientAdapter?

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.

token returns null when awaited right after initializing

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.

headers are not passed to server

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:

image

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

when dio version from 4.0.0 to 4.0.3 interceptor error

../../../../.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.

Request new Feature

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" }

Request headers bug

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?

Incorrect usage of updateContextEntry

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.

Unhandled DioError on HTTP code 401

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 :)

Migrate to dio 4.0.0

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)


Dumping and re-sending requests which occurred while refreshing

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

Fresh should export a Storage interface which means it can work with any storage provider. Out of the box, it should come with its own implementation: FreshTokenStorage, based on Hive.

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.

[ Concurrent API calls ]

Hi,
Thanks for the work,

What about concurrent calls ? I mean let's say for the exemple :

  • I have a home_page.dart with 3 sections.
  • Each section is filled with datas coming from separate / specific API calls.
  • We then have smtg like this :
@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 (^_^)

Please bump version of fresh

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.

What server has to return to make fresh work?

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.