hlibk
November 21, 2024, 4:26pm
1
I have a necessity to attach authorization header to websocket connections (subscriptions) in my Android application. So from time to time auth token becomes expired and needs to be refreshed.
Currently I create apollo client like this:
ApolloClient.Builder()
.serverUrl(BuildConfig.DATA_BASE_URL)
.subscriptionNetworkTransport(
WebSocketNetworkTransport.Builder()
.serverUrl(BuildConfig.DATA_BASE_URL)
.addHeader(AUTHORIZATION, "$BEARER $accessToken")
.build()
)
.build()
Is there any way to update the token when it should be refreshed?
mbonnin
November 21, 2024, 5:05pm
2
Hi
Is there any way to update the token when it should be refreshed?
There are but since WebSockets are not part of the specs, there are sadly multiple ways to do so.
The main question is how do you know when the token expires? Do you get a GraphQL error from your server? Or a WebSocket error? Or something else?
hlibk
November 22, 2024, 8:23am
3
The main question is how do you know when the token expires? Do you get a GraphQL error from your server? Or a WebSocket error? Or something else?
Yes, if a token expire, our gateway redirects the request to auth page, so I receive 1002 WebSocket protocol error.
So I wonder if it is possible to change the header in, for example, the reopenWhen block?
mbonnin
November 22, 2024, 8:59am
4
Sadly there is no such thing yet. You can only update the url and/or the connection_init params. Do you mind opening an issue? I’ll look into adding this for the experimental websockets .
mbonnin
November 22, 2024, 4:24pm
5
Turns out I lied, the experimental websockets already support this
I just added some more docs: Implement ApolloWebSocketClosedException on darwin targets and update docs by martinbonnin · Pull Request #6275 · apollographql/apollo-kotlin · GitHub
The tldr; is to use retryOnErrorInterceptor()
with a custom interceptor:
private object RetryException : Exception()
private class RetryOnErrorInterceptor : ApolloInterceptor {
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
var attempt = 0
return flow {
val request = request.newBuilder()
.addHttpHeader("Authorization", "Bearer ${accessToken()}")
.build()
emitAll(chain.proceed(request))
}.onEach {
when (val exception = it.exception) {
is ApolloWebSocketClosedException -> {
if (exception.code == 1002 && exception.reason == "unauthorized") {
attempt = 0 // retry immediately
throw RetryException
}
}
is ApolloNetworkException -> {
// Retry all network exceptions
throw RetryException
}
else -> {
// Terminate the subscription
}
}
}.retryWhen { cause, _ ->
cause is RetryException
}
}
}