Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to catch errors with flutter grpc Interceptor #745

Open
VladislavYakonyuk opened this issue Nov 26, 2024 · 2 comments
Open

How to catch errors with flutter grpc Interceptor #745

VladislavYakonyuk opened this issue Nov 26, 2024 · 2 comments

Comments

@VladislavYakonyuk
Copy link

I am trying to handle the token error when it has expired and perform refreshToken. But my provider that receives network responses from the grpc client receives the response before the token is refreshed. I can see in devtools that the token has been refreshed and the request has been sent

class RefreshTokenInterceptors extends ClientInterceptor {
  final Ref ref;

  RefreshTokenInterceptors({required this.ref});

  @override
  ResponseFuture<R> interceptUnary<Q, R>(
    ClientMethod<Q, R> method,
    Q request,
    CallOptions options,
    invoker,
  ) {
    return super.interceptUnary(method, request, options, invoker)
      // ignore: discarded_futures
      ..catchError((e) async {
        final error = e as GrpcError;
        final currentToken =
            ref.read(storageProvider).getString(StorageKeys.accessToken);

        if (currentToken.isNotEmpty) {
          if (error.code == 7) {
            final res = await ref.read(authProvider.notifier).refreshToken();

            return res.fold(
              (l) => null,
              (r) async {
                if (r.accessToken.isNotEmpty) {
                  return invoker(
                    method,
                    request,
                    CallOptions(
                      providers: [
                        (Map<String, String> metadata, String uri) async {
                          try {
                            metadata["authorization"] = r.accessToken;
                          } catch (e) {
                            log("interceptor error ${e.toString()}");
                          }
                        }
                      ],
                    ),
                  );
                }
              },
            );
          }
        }
        throw e;
      });
  }
}
@armanafat
Copy link

this is also my issue too , but I couldn't resolve it as well , what I wanted to achieve is to call refresh token and then re-invoke the method which cause the error 16 which I my case server gives the "token is expired .. !" error message , here is my code and I would really appreciate it ANY help :

class GrpcTokenInterceptor extends ClientInterceptor {
@OverRide
ResponseFuture interceptUnary<Q, R>(
ClientMethod<Q, R> method, Q request, CallOptions options, ClientUnaryInvoker<Q, R> invoker) {
late final ResponseFuture interceptorOP;

Future<void> tokenProvider(Map<String, String> metadata, String uri) async {
  TokenLocalDataSource tokenLocalDataSource = getIt<TokenLocalDataSource>();
  final currentToken = await tokenLocalDataSource.readToken();
  final accessToken = currentToken.data?.accessToken;
  metadata['authorization'] = "bearer $accessToken";
}


Future<void> testUnaryReCall()async{
  super.interceptUnary(
    method,
    request,
    options.mergedWith(
      CallOptions(providers: [
        tokenProvider,
      ]),
    ),
    invoker,
  );
}

Future<void> refreshAccessToken() async {
  final ClientChannel tokenChannel = getIt<ClientChannel>();

  final grpcInterceptor = getIt<GrpcTokenInterceptor>();

  final authClient = AuthClient(tokenChannel, interceptors: [grpcInterceptor]);

  TokenLocalDataSource tokenLocalDataSource = getIt<TokenLocalDataSource>();
  final currentToken = await tokenLocalDataSource.readToken();
  final refreshToken = currentToken.data?.refreshToken;
  try {
    final refreshTokenResponse = await authClient.refreshAccessToken(
      RefreshAccessTokenInput(refreshToken: refreshToken));
    if (refreshTokenResponse.accessToken.isNotEmpty) {
      final newToken = TokenModel(
        accessToken: refreshTokenResponse.accessToken.replaceAll('bearer ', ''),
        refreshToken: currentToken.data?.refreshToken?.replaceAll('bearer ', ''),
      );
      // await tokenLocalDataSource.saveToken(newToken).whenComplete(() {
        //todo :check value on success and check if invoker method get called
        testUnaryReCall();
      // });
    }
  } catch (e) {}
}

Future<void> mainHandler(Map<String, String> metadata, String uri) async {
  interceptorOP.then((response) async {
     await testUnaryReCall();
  }).catchError(
    (error) async {
      if (error is GrpcError && error.message == 'invalid token: token has invalid claims: token is expired') {
        refreshAccessToken();
      } else{
        throw error;
      }
    },
  );
}
interceptorOP = super.interceptUnary(
  method,
  request,
  options.mergedWith(
    CallOptions(providers: [
      tokenProvider,
      mainHandler,
    ]),
  ),
  invoker,
);
return interceptorOP;

}
}

@armanafat
Copy link

armanafat commented Feb 4, 2025

ok i have found a work around which works great , all I did was to create a global wrapper function which wrap all your requests to server in data source layer , in my case I wanted to catch error and don't let it throw in case its a token expiration error and well to call _refreshAccessToken() function and re-invoke the last method , here is the code :

1-Global wrapper function

Future<T> interceptHelper<T>(Future<T> Function() request, [int? current]) async {
  late final T res;
  int count = current ?? 0;
  const maxCall = 5;

  try {
    res = await request();
  } on GrpcError catch (e) {
    if (count >= maxCall) rethrow;
    if (e.code != 16) rethrow;

    if (e.message == 'invalid token: token has invalid claims: token is expired') {
     await _refreshAccessToken();
    }

    return interceptHelper(request, ++count);
  }

  return res;
}

2- use it where you want to call an api

final result = await interceptHelper(()=>TestClient.methodName(SingleObjectInput(param: paramValue)));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants