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

ErrorLink and RetryLLink examples and/or documentation #147

Open
AshDevFr opened this issue Oct 15, 2022 · 6 comments
Open

ErrorLink and RetryLLink examples and/or documentation #147

AshDevFr opened this issue Oct 15, 2022 · 6 comments

Comments

@AshDevFr
Copy link

Hi,
First of alll, thank you for your work on this project.

I tried to use RetryLink and no matter what I put in attempts and delay I get type errors.
I looked at the type definitions but I can't get to make it work.

Would it be possible for you to either update the EXAMPLE in the repo or in the website to provide example for both RetryLink and ErrorLink.

Thanks a lot.

@AshDevFr
Copy link
Author

AshDevFr commented Oct 16, 2022

Ok after a couple of hours I came up with this:

let makeRetryLink = ApolloClient.Link.RetryLink.make(
  ~attempts=RetryFunctionOptions({
    max: Some(3),
    retryIf: (~error, ~operation) =>
      Js.Promise.resolve(
        switch error {
        | Some(e) => e->Js.Json.decodeBoolean->Belt.Option.getWithDefault(false)
        | None => false
        },
      ),
  }),
  ~delay=DelayFunctionOptions({
    initial: Some(200),
    max: Some(2000),
    jitter: Some(1),
  }),
)

I don't know if it's correct.
According to the documentation jitter seems to be a bool

@AshDevFr
Copy link
Author

AshDevFr commented Oct 16, 2022

No sure if it will help anybody but I was able to make my errorLink but it's pretty ugly.

First of all, two things were missing from react-apollo-client to make it work for me so I added them:

@module("@apollo/client")
external fromPromise: Promise.t<'t> => ApolloClient__Link_Core_Types.Observable.Js_.t<
  ApolloClient__Link_Core_Types.FetchResult.Js_.t<Js.Json.t>,
  Js.Exn.t,
> = "fromPromise"
@send
external flatMap: (
  ApolloClient__Link_Core_Types.Observable.Js_.t<'t, 'error>,
  't => ApolloClient__Link_Core_Types.Observable.Js_.t<'r, 'error>,
) => ApolloClient__Link_Core_Types.Observable.Js_.t<'r, 'error> = "flatMap"

Here is the code I'm using to refresh the tooken on 401 errors and forward the operation (DISCLAIMER: it's super ugly and dirty code)

In order to refresh the token inside the errorLink I had to create a small client

let simpleClient = {
  open ApolloClient
  make(~cache=Cache.InMemoryCache.make(), ~connectToDevTools=true, ~link=httpLink, ())
}

Then in the errorLink

let handleCode = code =>
  switch code->Js.Json.decodeString {
  | Some(code') =>
    switch code' {
    | "UNAUTHENTICATED" =>
      let refreshToken = getTokenFromSessioon()

      Some(
        simpleClient.mutate(
          ~mutation=module(RefreshTokenMutation),
          {token: refreshToken},
        )->Promise.thenResolve(result =>
          switch result {
          | Ok({data: {tokens}}) =>
            saveTokens()
            true
          | Error(_) =>
            logout()
            false
          }
        ),
      )
    | _ => None
    }
  | None => None
  }

let errorLink = ApolloClient.Link.ErrorLink.make(({
  networkError,
  graphQLErrors,
  operation,
  response,
  forward,
}) => {
  let promise = switch graphQLErrors {
  | Some(e') =>
    e'->Belt.Array.reduce(None, (errP, error) => {
      switch errP {
      | Some(_) => errP
      | None =>
        switch error.extensions {
        | Some(ext) =>
          switch ext->Js.Dict.get("code") {
          | Some(code) => handleCode(code)
          | None => None
          }
        | None => None
        }
      }
    })
  | None => None
  }

  let _ = switch networkError {
  | Some(networkError') => Js.log2("Network error", networkError')
  | None => ()
  }

  let _ = switch response {
  | Some(response') => Js.log2("Error Link response", response')
  | None => ()
  }

  switch promise {
  | Some(p) => Some(flatMap(fromPromise(p), _ => forward(operation)))
  | None => None
  }
})

@jeddeloh
Copy link
Owner

Excellent! I'm glad you got things resolved. I have a possible alternative solution, but it's been a minute since I've actually used Apollo, so take it with a grain of salt. If I remember correctly, returning out of the error link essentially says "retry one more time". If you had a context link that builds up your auth headers from state or storage (ApolloClient.Link.ContextLink.makeAsync), the only thing the error link would need to be responsible for is invalidating the token on a 401 and forwarding the operation. A new token would get fetched asynchronously on the retry triggered from forwarding in the error link.

That aside, there are a bunch of good improvements here. Jitter is definitely a bool, observable.flatMap and fromPromise seem like reasonable bindings to add, and documentation was only ever partially fleshed out. If you're interested in doing any of that, PRs welcome!

@AshDevFr
Copy link
Author

Hi,
I think I could make a PR for flatMap since it's pretty straightforward but having started ReScript yesterday, some of the syntax and the way the library is built it still too complex at the moment for me.

@AshDevFr
Copy link
Author

I realized today that errorLink expect a return of an observable when using forward(operation) but can also get null as a return value if there is no forward operation.

I tried to changed errorLLink but I don't know how we can say that this method gets either observable or nothing.

Any idea?

@jeddeloh
Copy link
Owner

jeddeloh commented Oct 17, 2022

having started ReScript yesterday, some of the syntax and the way the library is built it still too complex at the moment for me.

No worries, then. I'll handle the changes.

I realized today that errorLink expect a return of an observable when using forward(operation) but can also get null as a return value if there is no forward operation.

I'm not sure I understand what you're saying here. It sounds like you're asking how to return null inside the error link. Are the bindings wrong? At the time of writing the typescript types were:

   interface ErrorHandler {
       (error: ErrorResponse): Observable<FetchResult> | void;
  }

Which means you should either return Some(forward(operation)) or None?

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