diff --git a/docs/source/migration/5.0.mdx b/docs/source/migration/5.0.mdx index 5b83d532a63..b6175177c8f 100644 --- a/docs/source/migration/5.0.mdx +++ b/docs/source/migration/5.0.mdx @@ -126,6 +126,105 @@ query GetUser { You can read more in the ["handling nullability" page](https://www.apollographql.com/docs/kotlin/advanced/nullability). +## `apollo-runtime` + +### New WebSockets + +In Apollo Kotlin 5, the WebSocket code has been rewritten to simplify it and clarify the error and retry semantics. + +All the classes in the `com.apollographql.apollo.network.ws` package have been deprecated and a new implementation is available in `com.apollographql.apollo.network.websocket`. + +To migrate, replace all your instances of `com.apollographql.apollo.network.ws` with `com.apollographql.apollo.network.websocket`: + +```kotlin +// Replace +import com.apollographql.apollo.network.ws.AppSyncWsProtocol +import com.apollographql.apollo.network.ws.WebSocketNetworkTransport + +// With +import com.apollographql.apollo.network.websocket.AppSyncWsProtocol +import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport +``` + +Some shorthand methods on `ApolloClient.Builder` have been deprecated and replaced by explicit configuration. In those cases, you can use `subscriptionNetworkTransport` directly. + +For an example, you can replace `ApolloClient.Builder.webSocketEngine()` as follows: + +```kotlin +// Replace +ApolloClient.Builder() + .webSocketEngine(webSocketEngine) + .build() +// With +ApolloClient.Builder() + .subscriptionNetworkTransport( + WebSocketNetworkTransport.Builder() + .webSocketEngine(webSocketEngine) + .build() + ) + .build() +``` + +The default WebSocket protocol has changed to [graphql-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md). + +If you were already using it before, you may remove the call to `protocol()` or define it explicitly using `subscriptionNetworkTransport()`: + +```kotlin +// Replace +val apolloClient = ApolloClient.Builder() + .protocol(GraphQLWsProtocol.Factory()) + .build() + +// With +val apolloClient = ApolloClient.Builder() + .subscriptionNetworkTransport( + WebSocketNetworkTransport.Builder() + .serverUrl(url) + .wsProtocol(GraphQLWsProtocol()) + .build() + ) + .build() + +``` + +If you are still relying on [the (now deprecated) transport](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md), you can use `SubscriptionWsProtocol`: + +```kotlin +val apolloClient = ApolloClient.Builder() + .subscriptionNetworkTransport( + WebSocketNetworkTransport.Builder() + .serverUrl(url) + .wsProtocol(SubscriptionsWsProtocol()) + .build() + ) + .build() +``` + +The retry management is now moved to `retryOnErrorInterceptor`: + +```kotlin +// Replace +val apolloClient = ApolloClient.Builder() + .webSocketServerUrl("http://localhost:8080/subscriptions") + .webSocketReopenWhen { e, attempt -> + delay(2.0.pow(attempt.toDouble()).toLong()) + // retry after the delay + true + } + +// With +val apolloClient = ApolloClient.Builder() + .webSocketServerUrl("http://localhost:8080/subscriptions") + .retryOnErrorInterceptor(RetryOnErrorInterceptor { context -> + if (context.request.operation is Subscription<*>) { + delay(2.0.pow(context.attempt.toDouble()).toLong()) + true + } else { + false + } + }) +``` + ## `apollo-http-cache` `apollo-http-cache` is now deprecated. Instead, it uses the existing OkHttp cache using [cacheUrlOverride](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-request/-builder/cache-url-override.html). diff --git a/libraries/apollo-engine-tests/src/commonMain/kotlin/com/apollographql/apollo/engine/tests/WebSocketEngineTest.kt b/libraries/apollo-engine-tests/src/commonMain/kotlin/com/apollographql/apollo/engine/tests/WebSocketEngineTest.kt index 5d6ab28a004..a3a088ad08e 100644 --- a/libraries/apollo-engine-tests/src/commonMain/kotlin/com/apollographql/apollo/engine/tests/WebSocketEngineTest.kt +++ b/libraries/apollo-engine-tests/src/commonMain/kotlin/com/apollographql/apollo/engine/tests/WebSocketEngineTest.kt @@ -1,3 +1,4 @@ +@file:Suppress("DEPRECATION") package com.apollographql.apollo.engine.tests import com.apollographql.apollo.annotations.ApolloInternal diff --git a/libraries/apollo-engine-tests/src/commonMain/kotlin/com/apollographql/apollo/engine/tests/all-tests.kt b/libraries/apollo-engine-tests/src/commonMain/kotlin/com/apollographql/apollo/engine/tests/all-tests.kt index 29ac81b564f..d0b122838a9 100644 --- a/libraries/apollo-engine-tests/src/commonMain/kotlin/com/apollographql/apollo/engine/tests/all-tests.kt +++ b/libraries/apollo-engine-tests/src/commonMain/kotlin/com/apollographql/apollo/engine/tests/all-tests.kt @@ -1,3 +1,4 @@ +@file:Suppress("DEPRECATION") package com.apollographql.apollo.engine.tests import com.apollographql.apollo.annotations.ApolloInternal diff --git a/libraries/apollo-engine-tests/src/commonTest/kotlin/AllTests.kt b/libraries/apollo-engine-tests/src/commonTest/kotlin/AllTests.kt index c1bd3eb8c71..2586ab6e46a 100644 --- a/libraries/apollo-engine-tests/src/commonTest/kotlin/AllTests.kt +++ b/libraries/apollo-engine-tests/src/commonTest/kotlin/AllTests.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + import com.apollographql.apollo.engine.tests.Platform import com.apollographql.apollo.engine.tests.platform import com.apollographql.apollo.engine.tests.runAllTests diff --git a/libraries/apollo-runtime/api/android/apollo-runtime.api b/libraries/apollo-runtime/api/android/apollo-runtime.api index f2229f3f23b..6eeb9eb48c5 100644 --- a/libraries/apollo-runtime/api/android/apollo-runtime.api +++ b/libraries/apollo-runtime/api/android/apollo-runtime.api @@ -231,20 +231,25 @@ public final class com/apollographql/apollo/interceptor/AutoPersistedQueryInterc public final class com/apollographql/apollo/interceptor/AutoPersistedQueryInterceptor$Companion { } +public final class com/apollographql/apollo/interceptor/RetryContext { + public fun (Lcom/apollographql/apollo/network/NetworkMonitor;Lcom/apollographql/apollo/api/ApolloRequest;)V + public final fun getAttempt ()I + public final fun getNetworkMonitor ()Lcom/apollographql/apollo/network/NetworkMonitor; + public final fun getRequest ()Lcom/apollographql/apollo/api/ApolloRequest; + public final fun getResponse ()Lcom/apollographql/apollo/api/ApolloResponse; + public final fun resetAttempt ()V +} + public final class com/apollographql/apollo/interceptor/RetryOnErrorInterceptorKt { + public static final fun RetryOnErrorInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor; public static final fun RetryOnErrorInterceptor (Lcom/apollographql/apollo/network/NetworkMonitor;)Lcom/apollographql/apollo/interceptor/ApolloInterceptor; public static final fun RetryOnErrorInterceptor (Lcom/apollographql/apollo/network/NetworkMonitor;Lcom/apollographql/apollo/interceptor/RetryStrategy;)Lcom/apollographql/apollo/interceptor/ApolloInterceptor; -} - -public final class com/apollographql/apollo/interceptor/RetryState { - public fun (Lcom/apollographql/apollo/network/NetworkMonitor;)V - public final fun getAttempt ()I - public final fun getNetworkMonitor ()Lcom/apollographql/apollo/network/NetworkMonitor; - public final fun setAttempt (I)V + public static synthetic fun RetryOnErrorInterceptor$default (Lcom/apollographql/apollo/network/NetworkMonitor;Lcom/apollographql/apollo/interceptor/RetryStrategy;ILjava/lang/Object;)Lcom/apollographql/apollo/interceptor/ApolloInterceptor; + public static final fun getDefaultRetryStrategy ()Lcom/apollographql/apollo/interceptor/RetryStrategy; } public abstract interface class com/apollographql/apollo/interceptor/RetryStrategy { - public abstract fun shouldRetry (Lcom/apollographql/apollo/interceptor/RetryState;Lcom/apollographql/apollo/api/ApolloRequest;Lcom/apollographql/apollo/api/ApolloResponse;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun shouldRetry (Lcom/apollographql/apollo/interceptor/RetryContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/apollographql/apollo/network/IncrementalDeliveryProtocol : java/lang/Enum { @@ -538,6 +543,7 @@ public abstract interface class com/apollographql/apollo/network/websocket/WebSo } public final class com/apollographql/apollo/network/websocket/WebSocketEngine_jvmKt { + public static final fun DefaultWebSocketEngine (Lokhttp3/WebSocket$Factory;)Lcom/apollographql/apollo/network/websocket/WebSocketEngine; public static final fun WebSocketEngine ()Lcom/apollographql/apollo/network/websocket/WebSocketEngine; public static final fun WebSocketEngine (Lkotlin/jvm/functions/Function0;)Lcom/apollographql/apollo/network/websocket/WebSocketEngine; public static final fun WebSocketEngine (Lokhttp3/WebSocket$Factory;)Lcom/apollographql/apollo/network/websocket/WebSocketEngine; @@ -556,6 +562,7 @@ public final class com/apollographql/apollo/network/websocket/WebSocketNetworkTr public final fun closeConnection (Lcom/apollographql/apollo/exception/ApolloException;)V public fun dispose ()V public fun execute (Lcom/apollographql/apollo/api/ApolloRequest;)Lkotlinx/coroutines/flow/Flow; + public final fun getSubscriptionCount ()Lkotlinx/coroutines/flow/StateFlow; } public final class com/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder { @@ -563,9 +570,11 @@ public final class com/apollographql/apollo/network/websocket/WebSocketNetworkTr public final fun build ()Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport; public final fun connectionAcknowledgeTimeout-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun idleTimeout-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; + public final fun idleTimeoutMillis (Ljava/lang/Long;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun incrementalDeliveryProtocol (Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun parserFactory (Lcom/apollographql/apollo/network/websocket/SubscriptionParserFactory;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun pingInterval-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; + public final fun protocol (Lcom/apollographql/apollo/network/websocket/WsProtocol;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun serverUrl (Ljava/lang/String;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun webSocketEngine (Lcom/apollographql/apollo/network/websocket/WebSocketEngine;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun wsProtocol (Lcom/apollographql/apollo/network/websocket/WsProtocol;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; diff --git a/libraries/apollo-runtime/api/apollo-runtime.klib.api b/libraries/apollo-runtime/api/apollo-runtime.klib.api index 4025cb7f8d0..0d8cf7a6b13 100644 --- a/libraries/apollo-runtime/api/apollo-runtime.klib.api +++ b/libraries/apollo-runtime/api/apollo-runtime.klib.api @@ -33,7 +33,7 @@ final enum class com.apollographql.apollo.network/IncrementalDeliveryProtocol : } abstract fun interface com.apollographql.apollo.interceptor/RetryStrategy { // com.apollographql.apollo.interceptor/RetryStrategy|null[0] - abstract suspend fun shouldRetry(com.apollographql.apollo.interceptor/RetryState, com.apollographql.apollo.api/ApolloRequest<*>, com.apollographql.apollo.api/ApolloResponse<*>): kotlin/Boolean // com.apollographql.apollo.interceptor/RetryStrategy.shouldRetry|shouldRetry(com.apollographql.apollo.interceptor.RetryState;com.apollographql.apollo.api.ApolloRequest<*>;com.apollographql.apollo.api.ApolloResponse<*>){}[0] + abstract suspend fun shouldRetry(com.apollographql.apollo.interceptor/RetryContext): kotlin/Boolean // com.apollographql.apollo.interceptor/RetryStrategy.shouldRetry|shouldRetry(com.apollographql.apollo.interceptor.RetryContext){}[0] } abstract interface <#A: com.apollographql.apollo.api/Operation.Data> com.apollographql.apollo.network.websocket/SubscriptionParser { // com.apollographql.apollo.network.websocket/SubscriptionParser|null[0] @@ -231,15 +231,19 @@ final class com.apollographql.apollo.interceptor/AutoPersistedQueryInterceptor : } } -final class com.apollographql.apollo.interceptor/RetryState { // com.apollographql.apollo.interceptor/RetryState|null[0] - constructor (com.apollographql.apollo.network/NetworkMonitor?) // com.apollographql.apollo.interceptor/RetryState.|(com.apollographql.apollo.network.NetworkMonitor?){}[0] +final class com.apollographql.apollo.interceptor/RetryContext { // com.apollographql.apollo.interceptor/RetryContext|null[0] + constructor (com.apollographql.apollo.network/NetworkMonitor?, com.apollographql.apollo.api/ApolloRequest<*>) // com.apollographql.apollo.interceptor/RetryContext.|(com.apollographql.apollo.network.NetworkMonitor?;com.apollographql.apollo.api.ApolloRequest<*>){}[0] - final val networkMonitor // com.apollographql.apollo.interceptor/RetryState.networkMonitor|{}networkMonitor[0] - final fun (): com.apollographql.apollo.network/NetworkMonitor? // com.apollographql.apollo.interceptor/RetryState.networkMonitor.|(){}[0] + final val attempt // com.apollographql.apollo.interceptor/RetryContext.attempt|{}attempt[0] + final fun (): kotlin/Int // com.apollographql.apollo.interceptor/RetryContext.attempt.|(){}[0] + final val networkMonitor // com.apollographql.apollo.interceptor/RetryContext.networkMonitor|{}networkMonitor[0] + final fun (): com.apollographql.apollo.network/NetworkMonitor? // com.apollographql.apollo.interceptor/RetryContext.networkMonitor.|(){}[0] + final val request // com.apollographql.apollo.interceptor/RetryContext.request|{}request[0] + final fun (): com.apollographql.apollo.api/ApolloRequest<*> // com.apollographql.apollo.interceptor/RetryContext.request.|(){}[0] + final val response // com.apollographql.apollo.interceptor/RetryContext.response|{}response[0] + final fun (): com.apollographql.apollo.api/ApolloResponse<*> // com.apollographql.apollo.interceptor/RetryContext.response.|(){}[0] - final var attempt // com.apollographql.apollo.interceptor/RetryState.attempt|{}attempt[0] - final fun (): kotlin/Int // com.apollographql.apollo.interceptor/RetryState.attempt.|(){}[0] - final fun (kotlin/Int) // com.apollographql.apollo.interceptor/RetryState.attempt.|(kotlin.Int){}[0] + final fun resetAttempt() // com.apollographql.apollo.interceptor/RetryContext.resetAttempt|resetAttempt(){}[0] } final class com.apollographql.apollo.network.http/ApolloClientAwarenessInterceptor : com.apollographql.apollo.network.http/HttpInterceptor { // com.apollographql.apollo.network.http/ApolloClientAwarenessInterceptor|null[0] @@ -367,8 +371,8 @@ final class com.apollographql.apollo.network.websocket/AppSyncWsProtocol : com.a final fun <#A1: com.apollographql.apollo.api/Operation.Data> operationStop(com.apollographql.apollo.api/ApolloRequest<#A1>): com.apollographql.apollo.network.websocket/ClientMessage // com.apollographql.apollo.network.websocket/AppSyncWsProtocol.operationStop|operationStop(com.apollographql.apollo.api.ApolloRequest<0:0>){0§}[0] final fun parseServerMessage(kotlin/String): com.apollographql.apollo.network.websocket/ServerMessage // com.apollographql.apollo.network.websocket/AppSyncWsProtocol.parseServerMessage|parseServerMessage(kotlin.String){}[0] - final fun ping(): com.apollographql.apollo.network.websocket/ClientMessage? // com.apollographql.apollo.network.websocket/AppSyncWsProtocol.ping|ping(){}[0] - final fun pong(): com.apollographql.apollo.network.websocket/ClientMessage? // com.apollographql.apollo.network.websocket/AppSyncWsProtocol.pong|pong(){}[0] + final fun ping(): com.apollographql.apollo.network.websocket/ClientMessage // com.apollographql.apollo.network.websocket/AppSyncWsProtocol.ping|ping(){}[0] + final fun pong(): com.apollographql.apollo.network.websocket/ClientMessage // com.apollographql.apollo.network.websocket/AppSyncWsProtocol.pong|pong(){}[0] final suspend fun <#A1: com.apollographql.apollo.api/Operation.Data> operationStart(com.apollographql.apollo.api/ApolloRequest<#A1>): com.apollographql.apollo.network.websocket/ClientMessage // com.apollographql.apollo.network.websocket/AppSyncWsProtocol.operationStart|operationStart(com.apollographql.apollo.api.ApolloRequest<0:0>){0§}[0] final suspend fun connectionInit(): com.apollographql.apollo.network.websocket/ClientMessage // com.apollographql.apollo.network.websocket/AppSyncWsProtocol.connectionInit|connectionInit(){}[0] @@ -463,6 +467,9 @@ final class com.apollographql.apollo.network.websocket/TextClientMessage : com.a } final class com.apollographql.apollo.network.websocket/WebSocketNetworkTransport : com.apollographql.apollo.network/NetworkTransport { // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport|null[0] + final val subscriptionCount // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.subscriptionCount|{}subscriptionCount[0] + final fun (): kotlinx.coroutines.flow/StateFlow // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.subscriptionCount.|(){}[0] + final fun <#A1: com.apollographql.apollo.api/Operation.Data> execute(com.apollographql.apollo.api/ApolloRequest<#A1>): kotlinx.coroutines.flow/Flow> // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.execute|execute(com.apollographql.apollo.api.ApolloRequest<0:0>){0§}[0] final fun closeConnection(com.apollographql.apollo.exception/ApolloException) // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.closeConnection|closeConnection(com.apollographql.apollo.exception.ApolloException){}[0] final fun dispose() // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.dispose|dispose(){}[0] @@ -473,9 +480,11 @@ final class com.apollographql.apollo.network.websocket/WebSocketNetworkTransport final fun build(): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.build|build(){}[0] final fun connectionAcknowledgeTimeout(kotlin.time/Duration?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.connectionAcknowledgeTimeout|connectionAcknowledgeTimeout(kotlin.time.Duration?){}[0] final fun idleTimeout(kotlin.time/Duration?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.idleTimeout|idleTimeout(kotlin.time.Duration?){}[0] + final fun idleTimeoutMillis(kotlin/Long?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.idleTimeoutMillis|idleTimeoutMillis(kotlin.Long?){}[0] final fun incrementalDeliveryProtocol(com.apollographql.apollo.network/IncrementalDeliveryProtocol): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.incrementalDeliveryProtocol|incrementalDeliveryProtocol(com.apollographql.apollo.network.IncrementalDeliveryProtocol){}[0] final fun parserFactory(com.apollographql.apollo.network.websocket/SubscriptionParserFactory?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.parserFactory|parserFactory(com.apollographql.apollo.network.websocket.SubscriptionParserFactory?){}[0] final fun pingInterval(kotlin.time/Duration?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.pingInterval|pingInterval(kotlin.time.Duration?){}[0] + final fun protocol(com.apollographql.apollo.network.websocket/WsProtocol?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.protocol|protocol(com.apollographql.apollo.network.websocket.WsProtocol?){}[0] final fun serverUrl(kotlin/String?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.serverUrl|serverUrl(kotlin.String?){}[0] final fun webSocketEngine(com.apollographql.apollo.network.websocket/WebSocketEngine?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.webSocketEngine|webSocketEngine(com.apollographql.apollo.network.websocket.WebSocketEngine?){}[0] final fun wsProtocol(com.apollographql.apollo.network.websocket/WsProtocol?): com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder // com.apollographql.apollo.network.websocket/WebSocketNetworkTransport.Builder.wsProtocol|wsProtocol(com.apollographql.apollo.network.websocket.WsProtocol?){}[0] @@ -791,6 +800,8 @@ final const val com.apollographql.apollo.network.ws/CLOSE_GOING_AWAY // com.apol final const val com.apollographql.apollo.network.ws/CLOSE_NORMAL // com.apollographql.apollo.network.ws/CLOSE_NORMAL|{}CLOSE_NORMAL[0] final fun (): kotlin/Int // com.apollographql.apollo.network.ws/CLOSE_NORMAL.|(){}[0] +final val com.apollographql.apollo.interceptor/defaultRetryStrategy // com.apollographql.apollo.interceptor/defaultRetryStrategy|{}defaultRetryStrategy[0] + final fun (): com.apollographql.apollo.interceptor/RetryStrategy // com.apollographql.apollo.interceptor/defaultRetryStrategy.|(){}[0] final val com.apollographql.apollo/autoPersistedQueryInfo // com.apollographql.apollo/autoPersistedQueryInfo|@com.apollographql.apollo.api.ApolloResponse<0:0>{0§}autoPersistedQueryInfo[0] final fun <#A1: com.apollographql.apollo.api/Operation.Data> (com.apollographql.apollo.api/ApolloResponse<#A1>).(): com.apollographql.apollo/AutoPersistedQueryInfo? // com.apollographql.apollo/autoPersistedQueryInfo.|@com.apollographql.apollo.api.ApolloResponse<0:0>(){0§}[0] final val com.apollographql.apollo/conflateFetchPolicyInterceptorResponses // com.apollographql.apollo/conflateFetchPolicyInterceptorResponses|@com.apollographql.apollo.api.ApolloRequest<0:0>{0§}conflateFetchPolicyInterceptorResponses[0] @@ -802,8 +813,7 @@ final fun (com.apollographql.apollo.network/NetworkTransport).com.apollographql. final fun (com.apollographql.apollo.network/NetworkTransport).com.apollographql.apollo.network.websocket/closeConnection(com.apollographql.apollo.exception/ApolloException) // com.apollographql.apollo.network.websocket/closeConnection|closeConnection@com.apollographql.apollo.network.NetworkTransport(com.apollographql.apollo.exception.ApolloException){}[0] final fun (com.apollographql.apollo.network/NetworkTransport).com.apollographql.apollo.network.ws/closeConnection(kotlin/Throwable) // com.apollographql.apollo.network.ws/closeConnection|closeConnection@com.apollographql.apollo.network.NetworkTransport(kotlin.Throwable){}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.apollo/conflateFetchPolicyInterceptorResponses(kotlin/Boolean): #A // com.apollographql.apollo/conflateFetchPolicyInterceptorResponses|conflateFetchPolicyInterceptorResponses@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] -final fun com.apollographql.apollo.interceptor/RetryOnErrorInterceptor(com.apollographql.apollo.network/NetworkMonitor): com.apollographql.apollo.interceptor/ApolloInterceptor // com.apollographql.apollo.interceptor/RetryOnErrorInterceptor|RetryOnErrorInterceptor(com.apollographql.apollo.network.NetworkMonitor){}[0] -final fun com.apollographql.apollo.interceptor/RetryOnErrorInterceptor(com.apollographql.apollo.network/NetworkMonitor, com.apollographql.apollo.interceptor/RetryStrategy): com.apollographql.apollo.interceptor/ApolloInterceptor // com.apollographql.apollo.interceptor/RetryOnErrorInterceptor|RetryOnErrorInterceptor(com.apollographql.apollo.network.NetworkMonitor;com.apollographql.apollo.interceptor.RetryStrategy){}[0] +final fun com.apollographql.apollo.interceptor/RetryOnErrorInterceptor(com.apollographql.apollo.network/NetworkMonitor? = ..., com.apollographql.apollo.interceptor/RetryStrategy = ...): com.apollographql.apollo.interceptor/ApolloInterceptor // com.apollographql.apollo.interceptor/RetryOnErrorInterceptor|RetryOnErrorInterceptor(com.apollographql.apollo.network.NetworkMonitor?;com.apollographql.apollo.interceptor.RetryStrategy){}[0] final fun com.apollographql.apollo.network.http/DefaultHttpEngine(kotlin/Long = ...): com.apollographql.apollo.network.http/HttpEngine // com.apollographql.apollo.network.http/DefaultHttpEngine|DefaultHttpEngine(kotlin.Long){}[0] final fun com.apollographql.apollo.network.websocket/WebSocketEngine(): com.apollographql.apollo.network.websocket/WebSocketEngine // com.apollographql.apollo.network.websocket/WebSocketEngine|WebSocketEngine(){}[0] diff --git a/libraries/apollo-runtime/api/jvm/apollo-runtime.api b/libraries/apollo-runtime/api/jvm/apollo-runtime.api index b21a1bf9636..3fbc216a2b1 100644 --- a/libraries/apollo-runtime/api/jvm/apollo-runtime.api +++ b/libraries/apollo-runtime/api/jvm/apollo-runtime.api @@ -231,20 +231,25 @@ public final class com/apollographql/apollo/interceptor/AutoPersistedQueryInterc public final class com/apollographql/apollo/interceptor/AutoPersistedQueryInterceptor$Companion { } +public final class com/apollographql/apollo/interceptor/RetryContext { + public fun (Lcom/apollographql/apollo/network/NetworkMonitor;Lcom/apollographql/apollo/api/ApolloRequest;)V + public final fun getAttempt ()I + public final fun getNetworkMonitor ()Lcom/apollographql/apollo/network/NetworkMonitor; + public final fun getRequest ()Lcom/apollographql/apollo/api/ApolloRequest; + public final fun getResponse ()Lcom/apollographql/apollo/api/ApolloResponse; + public final fun resetAttempt ()V +} + public final class com/apollographql/apollo/interceptor/RetryOnErrorInterceptorKt { + public static final fun RetryOnErrorInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor; public static final fun RetryOnErrorInterceptor (Lcom/apollographql/apollo/network/NetworkMonitor;)Lcom/apollographql/apollo/interceptor/ApolloInterceptor; public static final fun RetryOnErrorInterceptor (Lcom/apollographql/apollo/network/NetworkMonitor;Lcom/apollographql/apollo/interceptor/RetryStrategy;)Lcom/apollographql/apollo/interceptor/ApolloInterceptor; -} - -public final class com/apollographql/apollo/interceptor/RetryState { - public fun (Lcom/apollographql/apollo/network/NetworkMonitor;)V - public final fun getAttempt ()I - public final fun getNetworkMonitor ()Lcom/apollographql/apollo/network/NetworkMonitor; - public final fun setAttempt (I)V + public static synthetic fun RetryOnErrorInterceptor$default (Lcom/apollographql/apollo/network/NetworkMonitor;Lcom/apollographql/apollo/interceptor/RetryStrategy;ILjava/lang/Object;)Lcom/apollographql/apollo/interceptor/ApolloInterceptor; + public static final fun getDefaultRetryStrategy ()Lcom/apollographql/apollo/interceptor/RetryStrategy; } public abstract interface class com/apollographql/apollo/interceptor/RetryStrategy { - public abstract fun shouldRetry (Lcom/apollographql/apollo/interceptor/RetryState;Lcom/apollographql/apollo/api/ApolloRequest;Lcom/apollographql/apollo/api/ApolloResponse;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun shouldRetry (Lcom/apollographql/apollo/interceptor/RetryContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/apollographql/apollo/network/IncrementalDeliveryProtocol : java/lang/Enum { @@ -534,6 +539,7 @@ public abstract interface class com/apollographql/apollo/network/websocket/WebSo } public final class com/apollographql/apollo/network/websocket/WebSocketEngine_jvmKt { + public static final fun DefaultWebSocketEngine (Lokhttp3/WebSocket$Factory;)Lcom/apollographql/apollo/network/websocket/WebSocketEngine; public static final fun WebSocketEngine ()Lcom/apollographql/apollo/network/websocket/WebSocketEngine; public static final fun WebSocketEngine (Lkotlin/jvm/functions/Function0;)Lcom/apollographql/apollo/network/websocket/WebSocketEngine; public static final fun WebSocketEngine (Lokhttp3/WebSocket$Factory;)Lcom/apollographql/apollo/network/websocket/WebSocketEngine; @@ -552,6 +558,7 @@ public final class com/apollographql/apollo/network/websocket/WebSocketNetworkTr public final fun closeConnection (Lcom/apollographql/apollo/exception/ApolloException;)V public fun dispose ()V public fun execute (Lcom/apollographql/apollo/api/ApolloRequest;)Lkotlinx/coroutines/flow/Flow; + public final fun getSubscriptionCount ()Lkotlinx/coroutines/flow/StateFlow; } public final class com/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder { @@ -559,9 +566,11 @@ public final class com/apollographql/apollo/network/websocket/WebSocketNetworkTr public final fun build ()Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport; public final fun connectionAcknowledgeTimeout-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun idleTimeout-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; + public final fun idleTimeoutMillis (Ljava/lang/Long;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun incrementalDeliveryProtocol (Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun parserFactory (Lcom/apollographql/apollo/network/websocket/SubscriptionParserFactory;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun pingInterval-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; + public final fun protocol (Lcom/apollographql/apollo/network/websocket/WsProtocol;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun serverUrl (Ljava/lang/String;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun webSocketEngine (Lcom/apollographql/apollo/network/websocket/WebSocketEngine;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; public final fun wsProtocol (Lcom/apollographql/apollo/network/websocket/WsProtocol;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder; diff --git a/libraries/apollo-runtime/src/appleMain/kotlin/com/apollographql/apollo/network/ws/NSURLSessionWebSocketEngine.kt b/libraries/apollo-runtime/src/appleMain/kotlin/com/apollographql/apollo/network/ws/NSURLSessionWebSocketEngine.kt index e99f46a4402..8761e88fef5 100644 --- a/libraries/apollo-runtime/src/appleMain/kotlin/com/apollographql/apollo/network/ws/NSURLSessionWebSocketEngine.kt +++ b/libraries/apollo-runtime/src/appleMain/kotlin/com/apollographql/apollo/network/ws/NSURLSessionWebSocketEngine.kt @@ -1,5 +1,7 @@ +@file:Suppress("DEPRECATION") package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.http.HttpHeader import com.apollographql.apollo.exception.ApolloNetworkException import com.apollographql.apollo.exception.ApolloWebSocketClosedException @@ -43,6 +45,8 @@ interface WebSocketConnectionListener { typealias NSWebSocketFactory = (NSURLRequest, WebSocketConnectionListener) -> NSURLSessionWebSocketTask +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) actual class DefaultWebSocketEngine( private val webSocketFactory: NSWebSocketFactory, ) : WebSocketEngine { diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/ApolloClient.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/ApolloClient.kt index dd3b2c2988e..acd3927e875 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/ApolloClient.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/ApolloClient.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo import com.apollographql.apollo.annotations.ApolloDeprecatedSince @@ -28,9 +30,7 @@ import com.apollographql.apollo.network.http.BatchingHttpInterceptor import com.apollographql.apollo.network.http.HttpEngine import com.apollographql.apollo.network.http.HttpInterceptor import com.apollographql.apollo.network.http.HttpNetworkTransport -import com.apollographql.apollo.network.ws.WebSocketEngine -import com.apollographql.apollo.network.ws.WebSocketNetworkTransport -import com.apollographql.apollo.network.ws.WsProtocol +import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel @@ -44,9 +44,13 @@ import okio.Closeable import kotlin.collections.mutableListOf import kotlin.collections.plusAssign import kotlin.jvm.JvmOverloads +import kotlin.time.Duration.Companion.milliseconds +import com.apollographql.apollo.network.ws.WebSocketEngine as DeprecatedWebSocketEngine +import com.apollographql.apollo.network.ws.WebSocketNetworkTransport as DeprecatedWebSocketNetworkTransport +import com.apollographql.apollo.network.ws.WsProtocol as DeprecatedWsProtocol /** - * The main entry point for the Apollo runtime. An [ApolloClient] is responsible for executing queries, mutations and subscriptions + * The main entry point for the Apollo runtime. An [ApolloClient] is responsible for executing queries, mutations, and subscriptions * * Use [ApolloClient.Builder] to create a new [ApolloClient]: * @@ -149,11 +153,11 @@ private constructor( } else { val url = builder.webSocketServerUrl ?: builder.httpServerUrl if (url == null) { - // Fallback to the regular [NetworkTransport]. This is unlikely to work but chances are - // that the user is not going to use subscription, so it's better than failing + // Fallback to the regular [NetworkTransport]. This is unlikely to work, but chances are + // that the user is not going to use a subscription, so it's better than failing networkTransport - } else { - WebSocketNetworkTransport.Builder() + } else if(builder.webSocketEngine != null || builder.wsProtocolFactory != null || builder.webSocketReopenWhen != null || builder.webSocketReopenServerUrl != null) { + DeprecatedWebSocketNetworkTransport.Builder() .serverUrl(url) .apply { if (builder.webSocketEngine != null) { @@ -173,6 +177,15 @@ private constructor( } } .build() + } else { + WebSocketNetworkTransport.Builder() + .serverUrl(url) + .apply { + if (builder.webSocketIdleTimeoutMillis != null) { + idleTimeout(builder.webSocketIdleTimeoutMillis!!.milliseconds) + } + } + .build() } } @@ -206,7 +219,7 @@ private constructor( /** * Disposes resources held by this [ApolloClient]. On JVM platforms, resources are ultimately garbage collected but calling [close] is necessary - * on other platform or to reclaim those resources earlier. + * on another platform or to reclaim those resources earlier. */ override fun close() { concurrencyInfo.coroutineScope.cancel() @@ -287,6 +300,7 @@ private constructor( failFastIfOffline(failFastIfOffline ?: apolloClient.failFastIfOffline) ignoreUnknownKeys(ignoreUnknownKeys ?: apolloClient.ignoreUnknownKeys) sendEnhancedClientAwareness(apolloClient.sendEnhancedClientAwareness) + url(url ?: apolloClient.url) }.build() val allInterceptors = buildList { @@ -378,11 +392,11 @@ private constructor( private set var webSocketIdleTimeoutMillis: Long? = null private set - var wsProtocolFactory: WsProtocol.Factory? = null + var wsProtocolFactory: DeprecatedWsProtocol.Factory? = null private set var httpExposeErrorBody: Boolean? = null private set - var webSocketEngine: WebSocketEngine? = null + var webSocketEngine: DeprecatedWebSocketEngine? = null private set var webSocketReopenWhen: (suspend (Throwable, attempt: Long) -> Boolean)? = null private set @@ -554,7 +568,7 @@ private constructor( * } * ``` * - * To configure APQs in general, including retry behaviour, use [autoPersistedQueries] and [enableAutoPersistedQueries]. + * To configure APQs in general, including retry behavior, use [autoPersistedQueries] and [enableAutoPersistedQueries]. * * @see autoPersistedQueries * @see enableAutoPersistedQueries @@ -572,7 +586,7 @@ private constructor( * Set [sendDocument] to `false` if your server supports [persisted queries](https://www.apollographql.com/docs/kotlin/advanced/persisted-queries/) and * can execute an operation base on an id instead. * - * To configure APQs in general, including retry behaviour, use [autoPersistedQueries] and [enableAutoPersistedQueries]. + * To configure APQs in general, including retry behavior, use [autoPersistedQueries] and [enableAutoPersistedQueries]. * * @see autoPersistedQueries * @see enableAutoPersistedQueries @@ -663,7 +677,7 @@ private constructor( } /** - * Adds [httpInterceptor] to the list of HTTP interceptors. + * Adds [httpInterceptors] to the list of HTTP interceptors. * * This is a convenience function that configures the underlying [HttpNetworkTransport]. See also [networkTransport] for more customization. * @@ -722,6 +736,8 @@ private constructor( * @see url * @see subscriptionNetworkTransport */ + @Deprecated("Use subscriptionNetworkTransport() directly. See https://go.apollo.dev/ak-v5-websockets for more details.") + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) fun webSocketServerUrl(webSocketServerUrl: (suspend () -> String)?) = apply { this.webSocketReopenServerUrl = webSocketServerUrl } @@ -740,24 +756,28 @@ private constructor( } /** - * The [WsProtocol.Factory] to use for websockets + * The [DeprecatedWsProtocol.Factory] to use for websockets * - * This is a convenience function that configures the underlying [WebSocketNetworkTransport]. See also [subscriptionNetworkTransport] for more customization. + * This is a convenience function that configures the underlying [DeprecatedWebSocketNetworkTransport]. See also [subscriptionNetworkTransport] for more customization. * * @see subscriptionNetworkTransport */ - fun wsProtocol(wsProtocolFactory: WsProtocol.Factory?) = apply { + @Deprecated("Use subscriptionNetworkTransport() directly. See https://go.apollo.dev/ak-v5-websockets for more details.") + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) + fun wsProtocol(wsProtocolFactory: DeprecatedWsProtocol.Factory?) = apply { this.wsProtocolFactory = wsProtocolFactory } /** - * The [WebSocketEngine] to use for WebSocket requests + * The [DeprecatedWebSocketEngine] to use for WebSocket requests * - * This is a convenience function that configures the underlying [WebSocketNetworkTransport]. See also [subscriptionNetworkTransport] for more customization. + * This is a convenience function that configures the underlying [DeprecatedWebSocketNetworkTransport]. See also [subscriptionNetworkTransport] for more customization. * * @see subscriptionNetworkTransport */ - fun webSocketEngine(webSocketEngine: WebSocketEngine?) = apply { + @Deprecated("Use subscriptionNetworkTransport() directly. See https://go.apollo.dev/ak-v5-websockets for more details.") + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) + fun webSocketEngine(webSocketEngine: DeprecatedWebSocketEngine?) = apply { this.webSocketEngine = webSocketEngine } @@ -768,13 +788,15 @@ private constructor( * @param webSocketReopenWhen a function taking the error and attempt index (starting from zero) as parameters * and returning 'true' to reopen automatically or 'false' to forward the error to all listening [Flow]. * - * It is a suspending function, so it can be used to introduce delay before retry (e.g. backoff strategy). - * attempt is reset after a successful connection. + * It is a suspending function, so it can be used to introduce delay before retry (e.g., backoff strategy). + * `attempt` is reset after a successful connection. * * This is a convenience function that configures the underlying [WebSocketNetworkTransport]. See also [subscriptionNetworkTransport] for more customization. * * @see subscriptionNetworkTransport */ + @Deprecated("Use subscriptionNetworkTransport() directly. See https://go.apollo.dev/ak-v5-websockets for more details.") + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) fun webSocketReopenWhen(webSocketReopenWhen: (suspend (Throwable, attempt: Long) -> Boolean)?) = apply { this.webSocketReopenWhen = webSocketReopenWhen } @@ -873,7 +895,7 @@ private constructor( /** * Adds several [ApolloInterceptor]s to this [ApolloClient]. * - * [ApolloInterceptor]s monitor, rewrite and retry an [ApolloCall]. Internally, [ApolloInterceptor] is used for features + * [ApolloInterceptor]s monitor, rewrite, and retry an [ApolloCall]. Internally, [ApolloInterceptor] is used for features * such as normalized cache and auto persisted queries. [ApolloClient] also inserts a terminating [ApolloInterceptor] that * executes the request. * @@ -889,7 +911,7 @@ private constructor( /** * Sets the [ApolloInterceptor]s on this [ApolloClient]. * - * [ApolloInterceptor]s monitor, rewrite and retry an [ApolloCall]. Internally, [ApolloInterceptor] is used for features + * [ApolloInterceptor]s monitor, rewrite, and retry an [ApolloCall]. Internally, [ApolloInterceptor] is used for features * such as normalized cache and auto persisted queries. [ApolloClient] also inserts a terminating [ApolloInterceptor] that * executes the request. * @@ -954,7 +976,7 @@ private constructor( * Batch HTTP queries to execute multiple at once. * This reduces the number of HTTP round trips at the price of increased latency as * every request in the batch is now as slow as the slowest one. - * Some servers might have a per-HTTP-call cache making it faster to resolve 1 big array + * Some servers might have a per-HTTP-call cache, making it faster to resolve 1 big array * of n queries compared to resolving the n queries separately. * * See also [BatchingHttpInterceptor] @@ -977,8 +999,8 @@ private constructor( * Creates an [ApolloClient] from this [Builder] */ fun build(): ApolloClient { - // Copy the builder so that any subsequent modifications of the builder - // doesn't change the ApolloClient owned one + // Copy the builder so that any later modifications of the builder + // don't change the ApolloClient owned one return ApolloClient(copy()) } diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/interceptor/RetryOnErrorInterceptor.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/interceptor/RetryOnErrorInterceptor.kt index aa2a77483e8..1e39482a8a2 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/interceptor/RetryOnErrorInterceptor.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/interceptor/RetryOnErrorInterceptor.kt @@ -16,20 +16,12 @@ import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.retryWhen +import kotlin.jvm.JvmOverloads import kotlin.math.pow import kotlin.time.Duration.Companion.seconds /** - * Returns a default [ApolloInterceptor] that monitors exceptions and possibly retries the [Flow]. - * - * The returned [RetryOnErrorInterceptor] uses default behavior depending on whether the current request is a subscription - * (long-lived request) or not (short-lived request): - * - for subscriptions: - * - if [ApolloResponse.exception] is an instance of [ApolloOfflineException], wait for the network to become available again and retry the request. - * - else if [ApolloResponse.exception] is recoverable, uses exponential backoff with a max delay of 64 seconds. - * - for queries and mutations: - * - if [ApolloResponse.exception] is an instance of [ApolloOfflineException], let the exception through. - * - else if [ApolloResponse.exception] is recoverable, retries after 1, 3 and 7 seconds and stops retrying. + * Returns a default [ApolloInterceptor] that monitors exceptions and possibly retries the [Flow] according to [retryStrategy]. * * Use with [com.apollographql.apollo.ApolloClient.Builder.retryOnErrorInterceptor]: * @@ -46,64 +38,69 @@ import kotlin.time.Duration.Companion.seconds * @see [com.apollographql.apollo.ApolloClient.Builder.retryOnErrorInterceptor] * @see [ApolloRequest.retryOnError] */ -fun RetryOnErrorInterceptor(networkMonitor: NetworkMonitor): ApolloInterceptor = - DefaultRetryOnErrorInterceptorImpl(networkMonitor, defaultRetryStrategy) - -/** - * Returns a default [ApolloInterceptor] that monitors exceptions and possibly retries the [Flow] according to [retryStrategy]. - * - * Use with [com.apollographql.apollo.ApolloClient.Builder.retryOnErrorInterceptor]: - * - * ```kotlin - * apolloClient = ApolloClient.Builder() - * .serverUrl("https://...") - * .retryOnErrorInterceptor(RetryOnErrorInterceptor(NetworkMonitor(context))) - * .build() - * ``` - * - * @see [com.apollographql.apollo.ApolloClient.Builder.retryOnErrorInterceptor] - * @see [ApolloRequest.retryOnError] - */ -fun RetryOnErrorInterceptor(networkMonitor: NetworkMonitor, retryStrategy: RetryStrategy): ApolloInterceptor = +@JvmOverloads +fun RetryOnErrorInterceptor( + networkMonitor: NetworkMonitor? = null, + retryStrategy: RetryStrategy = defaultRetryStrategy, +): ApolloInterceptor = DefaultRetryOnErrorInterceptorImpl(networkMonitor, retryStrategy) fun interface RetryStrategy { /** - * Determines whether [request] should be retried. + * Determines whether the request should be retried. * This function may suspend. * + * @param context the retry state for a given request. * @return true if this request should be retried. */ - suspend fun shouldRetry(state: RetryState, request: ApolloRequest<*>, response: ApolloResponse<*>): Boolean + suspend fun shouldRetry(context: RetryContext): Boolean } /** - * The state of this request + * The retry context of this request. */ -class RetryState( +class RetryContext( val networkMonitor: NetworkMonitor?, + /** + * The request that needs to be retried + */ + val request: ApolloRequest<*>, ) { + internal var _attempt: Int = 0 + + internal lateinit var _response: ApolloResponse<*> + /** - * The current attempt, starting at 0. - * [RetryStrategy] implementations may update this value, for an example to reset exponential backoff. + * The last response that was seen */ - var attempt = 0 -} + val response: ApolloResponse<*> + get() = _response -internal fun RetryOnErrorInterceptor(): ApolloInterceptor = DefaultRetryOnErrorInterceptorImpl(null, defaultRetryStrategy) + /** + * Reset the value of [attempt] + */ + fun resetAttempt() { + _attempt = 0 + } -private val defaultRetryStrategy = RetryStrategy { state: RetryState, request: ApolloRequest<*>, response: ApolloResponse<*> -> - val exception = response.exception - if (exception == null) { - // success: continue + /** + * The attempt number, starting from 0 + */ + val attempt: Int + get() = _attempt +} + +val defaultRetryStrategy = RetryStrategy { state: RetryContext -> + val request = state.request + val exception = state.response.exception + if (request.retryOnError != true || exception == null) { return@RetryStrategy false } - if (exception is ApolloOfflineException) { // We are offline if (request.operation is Subscription<*>) { state.networkMonitor!!.waitForNetwork() - state.attempt = 0 + state.resetAttempt() return@RetryStrategy true } else { return@RetryStrategy false @@ -112,14 +109,13 @@ private val defaultRetryStrategy = RetryStrategy { state: RetryState, request: A if (exception.isRecoverable()) { if (request.operation !is Subscription && state.attempt >= 3) { - // We have waited 1 + 2 + 4 = 7 seconds - // Give up and return the error + // We have waited 1 + 2 + 4 = 7 seconds. + // Give up and return the error. return@RetryStrategy false } - // Cap the delay at 60s + // Cap the delay at 60 seconds delay(2.0.pow(state.attempt).coerceAtMost(60.0).seconds) - state.attempt++ return@RetryStrategy true } @@ -134,7 +130,7 @@ private class DefaultRetryOnErrorInterceptorImpl( override fun intercept(request: ApolloRequest, chain: ApolloInterceptorChain): Flow> { val failFastIfOffline = request.failFastIfOffline ?: false - val state = RetryState(networkMonitor) + val state = RetryContext(networkMonitor, request) // Do not move this down into flow{} because WebSocketNetworkTransport saves some state in there val downstream = chain.proceed(request) @@ -146,10 +142,10 @@ private class DefaultRetryOnErrorInterceptorImpl( emitAll(downstream) } }.onEach { - if (request.retryOnError == true && retryStrategy.shouldRetry(state, request, it)) { + state._response = it + if (retryStrategy.shouldRetry(state)) { + state._attempt++ throw RetryException() - } else { - state.attempt = 0 } }.retryWhen { cause, _ -> if (cause is RetryException) { diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/AppSyncWsProtocol.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/AppSyncWsProtocol.kt index 32f503bcd3d..2ac1d8c49d6 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/AppSyncWsProtocol.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/AppSyncWsProtocol.kt @@ -1,6 +1,5 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.NullableAnyAdapter import com.apollographql.apollo.api.Operation @@ -15,8 +14,9 @@ import okio.Buffer /** * A [WsProtocol] for https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html + * + * @param authorization the value of the "authorization" extension in the "start" payload. */ -@ApolloExperimental class AppSyncWsProtocol( val authorization: suspend () -> Any? = { null }, ) : WsProtocol { @@ -31,17 +31,16 @@ class AppSyncWsProtocol( // AppSync encodes the data as a String val data = NullableAnyAdapter.toJsonString(DefaultHttpRequestComposer.composePayload(request)) - - return mapOf( - "type" to "start", - "id" to request.requestUuid.toString(), - "payload" to mapOf( - "data" to data, - "extensions" to mapOf( - "authorization" to authorization() - ) + return mapOf( + "type" to "start", + "id" to request.requestUuid.toString(), + "payload" to mapOf( + "data" to data, + "extensions" to mapOf( + "authorization" to authorization() ) - ).toClientMessage() + ) + ).toClientMessage() } override fun operationStop(request: ApolloRequest): ClientMessage { @@ -51,11 +50,11 @@ class AppSyncWsProtocol( ).toClientMessage() } - override fun ping(): ClientMessage? { + override fun ping(): ClientMessage { return mapOf("type" to "ping").toClientMessage() } - override fun pong(): ClientMessage? { + override fun pong(): ClientMessage { return mapOf("type" to "pong").toClientMessage() } @@ -63,7 +62,7 @@ class AppSyncWsProtocol( val map = try { @Suppress("UNCHECKED_CAST") Buffer().writeUtf8(text).jsonReader().readAny() as Map - } catch (e: Exception) { + } catch (_: Exception) { return ParseErrorServerMessage("Invalid JSON: '$this'") } @@ -85,6 +84,7 @@ class AppSyncWsProtocol( else -> error("") // make the compiler happy } } + "error" -> { val id = map["id"] as? String if (id != null) { @@ -98,11 +98,10 @@ class AppSyncWsProtocol( } } - @ApolloExperimental companion object { /** * Helper method that builds the final URL. It will append the authorization and payload arguments as query parameters. - * This method can be used for both the HTTP URL as well as the WebSocket URL + * This method can be used for both the HTTP URL and the WebSocket URL * * Example: * ``` @@ -125,11 +124,12 @@ class AppSyncWsProtocol( authorization: Map, payload: Map = emptyMap(), ): String = - baseUrl - .appendQueryParameters(mapOf( - "header" to authorization.base64Encode(), - "payload" to payload.base64Encode(), - )) + baseUrl + .appendQueryParameters(mapOf( + "header" to authorization.base64Encode(), + "payload" to payload.base64Encode(), + ) + ) private fun Map.base64Encode(): String { return buildJsonByteString { diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/ClientMessage.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/ClientMessage.kt index ca235a969cb..360060d58b1 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/ClientMessage.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/ClientMessage.kt @@ -1,24 +1,20 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.json.buildJsonString import com.apollographql.apollo.api.json.writeAny /** * A WebSocket [message](https://datatracker.ietf.org/doc/html/rfc6455#section-1.2) sent by the client */ -@ApolloExperimental sealed interface ClientMessage /** * A WebSocket text message */ -@ApolloExperimental class TextClientMessage(val text: String): ClientMessage /** * A WebSocket data message */ -@ApolloExperimental class DataClientMessage(val data: ByteArray): ClientMessage internal fun Any?.toClientMessage(): ClientMessage { diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/GraphQLWsProtocol.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/GraphQLWsProtocol.kt index c4c8f048249..37f67e167c4 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/GraphQLWsProtocol.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/GraphQLWsProtocol.kt @@ -1,6 +1,5 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.http.DefaultHttpRequestComposer @@ -13,7 +12,6 @@ import okio.Buffer * * [GraphQLWsProtocol] can execute queries and mutations in addition to subscriptions */ -@ApolloExperimental class GraphQLWsProtocol( val connectionPayload: suspend () -> Any? = { null }, ) : WsProtocol { @@ -58,7 +56,7 @@ class GraphQLWsProtocol( val map = try { @Suppress("UNCHECKED_CAST") Buffer().writeUtf8(text).jsonReader().readAny() as Map - } catch (e: Exception) { + } catch (_: Exception) { return ParseErrorServerMessage("Invalid JSON: '$text'") } diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/ServerMessage.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/ServerMessage.kt index 2585ddce7b8..2c2aa936ba6 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/ServerMessage.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/ServerMessage.kt @@ -1,22 +1,15 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.json.ApolloJsonElement /** * A WebSocket [message](https://datatracker.ietf.org/doc/html/rfc6455#section-1.2) sent by the server */ -@ApolloExperimental sealed interface ServerMessage -@ApolloExperimental object ConnectionAckServerMessage : ServerMessage -@ApolloExperimental object ConnectionKeepAliveServerMessage : ServerMessage -@ApolloExperimental object PingServerMessage : ServerMessage -@ApolloExperimental object PongServerMessage : ServerMessage -@ApolloExperimental class ConnectionErrorServerMessage(val payload: ApolloJsonElement) : ServerMessage /** @@ -24,14 +17,12 @@ class ConnectionErrorServerMessage(val payload: ApolloJsonElement) : ServerMessa * * @param response, a GraphQL response, possibly containing errors. */ -@ApolloExperimental class ResponseServerMessage(val id: String, val response: ApolloJsonElement) : ServerMessage /** * The subscription completed normally * This is a terminal message for the given operation. */ -@ApolloExperimental class CompleteServerMessage(val id: String) : ServerMessage /** @@ -41,11 +32,9 @@ class CompleteServerMessage(val id: String) : ServerMessage * @param payload additional information regarding the error. * It may represent a GraphQL error, but it doesn't have to. */ -@ApolloExperimental class OperationErrorServerMessage(val id: String, val payload: ApolloJsonElement) : ServerMessage /** * Special Server message that indicates a malformed message */ -@ApolloExperimental class ParseErrorServerMessage(val errorMessage: String) : ServerMessage diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/SubscriptionParser.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/SubscriptionParser.kt index 633bff431e1..b62d5ee001f 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/SubscriptionParser.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/SubscriptionParser.kt @@ -1,6 +1,5 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.ApolloResponse import com.apollographql.apollo.api.Operation @@ -9,7 +8,6 @@ import com.apollographql.apollo.api.json.ApolloJsonElement /** * A [SubscriptionParser] transforms JSON responses contained in WebSocket messages into parsed [ApolloResponse] */ -@ApolloExperimental interface SubscriptionParser { fun parse(response: ApolloJsonElement): ApolloResponse? } @@ -17,7 +15,6 @@ interface SubscriptionParser { /** * A factory for [SubscriptionParser] */ -@ApolloExperimental interface SubscriptionParserFactory { fun createParser(request: ApolloRequest): SubscriptionParser } \ No newline at end of file diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/SubscriptionWsProtocol.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/SubscriptionWsProtocol.kt index d6655bfbe8d..2e36f23f426 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/SubscriptionWsProtocol.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/SubscriptionWsProtocol.kt @@ -1,13 +1,11 @@ package com.apollographql.apollo.network.websocket import com.apollographql.apollo.annotations.ApolloDeprecatedSince -import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.http.DefaultHttpRequestComposer import com.apollographql.apollo.api.json.jsonReader import com.apollographql.apollo.api.json.readAny -import com.apollographql.apollo.network.ws.GraphQLWsProtocol import okio.Buffer /** @@ -15,7 +13,6 @@ import okio.Buffer * * Note: This protocol is no longer actively maintained, and [GraphQLWsProtocol] should be favored instead. */ -@ApolloExperimental @Deprecated("Migrate your server to GraphQLWsProtocol instead") @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_0_0) class SubscriptionWsProtocol( diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.kt index e72c02d3e62..94bc76cb6a9 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.kt @@ -1,6 +1,5 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.http.HttpHeader import com.apollographql.apollo.exception.ApolloException import okio.Closeable @@ -8,7 +7,6 @@ import okio.Closeable /** * The low-level WebSocket API. Implement this interface to customize how WebSockets are handled */ -@ApolloExperimental interface WebSocketEngine: Closeable { /** * Creates a new [WebSocket]. @@ -33,7 +31,6 @@ interface WebSocketEngine: Closeable { ): WebSocket } -@ApolloExperimental interface WebSocketListener { /** * The HTTP 101 Switching Protocols response has been received and is valid. @@ -62,7 +59,6 @@ interface WebSocketListener { fun onClosed(code: Int?, reason: String?) } -@ApolloExperimental interface WebSocket { /** * Sends a binary message asynchronously. @@ -91,7 +87,6 @@ interface WebSocket { fun close(code: Int, reason: String) } -@ApolloExperimental expect fun WebSocketEngine() : WebSocketEngine internal const val CLOSE_NORMAL = 1000 diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketNetworkTransport.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketNetworkTransport.kt index 01e8c1d2cdd..be848baf5ea 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketNetworkTransport.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketNetworkTransport.kt @@ -1,6 +1,6 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.ApolloResponse import com.apollographql.apollo.api.CustomScalarAdapters @@ -23,9 +23,11 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.callbackFlow import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds /** @@ -35,7 +37,6 @@ import kotlin.time.Duration.Companion.seconds * * @see [WebSocketNetworkTransport.Builder] */ -@ApolloExperimental class WebSocketNetworkTransport private constructor( private val webSocketEngine: WebSocketEngine, private val serverUrl: String?, @@ -46,6 +47,11 @@ class WebSocketNetworkTransport private constructor( private val parserFactory: SubscriptionParserFactory, ) : NetworkTransport { + @Deprecated("This is not exposed anymore. If you have a use case that requires knowing the number of subscriptions, please open an issue.") + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) + val subscriptionCount: StateFlow + get() = TODO() + private val pool = WebSocketPool( webSocketEngine = webSocketEngine, serverUrl = serverUrl, @@ -107,7 +113,6 @@ class WebSocketNetworkTransport private constructor( pool.closeAllConnections(reason) } - @ApolloExperimental class Builder { private var serverUrl: String? = null private var webSocketEngine: WebSocketEngine? = null @@ -146,6 +151,15 @@ class WebSocketNetworkTransport private constructor( this.idleTimeout = idleTimeout } + /** + * This is a convenience function to ease migration from v4 webSockets. + * + * @see idleTimeout + */ + @Deprecated("Use idleTimeout instead", ReplaceWith("idleTimeout(idleTimeoutMillis.milliseconds)", "kotlin.time.Duration.Companion.milliseconds")) + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) + fun idleTimeoutMillis(idleTimeoutMillis: Long?) = idleTimeout(idleTimeoutMillis?.milliseconds) + /** * The [WsProtocol] to use for this [WebSocketNetworkTransport] * @@ -159,6 +173,15 @@ class WebSocketNetworkTransport private constructor( this.wsProtocol = wsProtocol } + /** + * Convenience function to help migrate from the v4 websockets + * + * @see wsProtocol + */ + @Deprecated("Use wsProtocol instead", ReplaceWith("wsProtocol(protocol)")) + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) + fun protocol(protocol: WsProtocol?) = wsProtocol(protocol) + /** * @param pingInterval the interval between two client pings or null to disable client pings. * The [WsProtocol] used must also support client pings. @@ -175,7 +198,6 @@ class WebSocketNetworkTransport private constructor( this.connectionAcknowledgeTimeout = connectionAcknowledgeTimeout } - @ApolloExperimental fun parserFactory(parserFactory: SubscriptionParserFactory?) = apply { this.parserFactory = parserFactory } @@ -185,7 +207,6 @@ class WebSocketNetworkTransport private constructor( * * Default: [IncrementalDeliveryProtocol.V0_1] */ - @ApolloExperimental fun incrementalDeliveryProtocol(incrementalDeliveryProtocol: IncrementalDeliveryProtocol) = apply { this.incrementalDeliveryProtocol = incrementalDeliveryProtocol } @@ -304,7 +325,6 @@ private fun Map.isDeferred(): Boolean { * @throws IllegalArgumentException if transport is not a [WebSocketNetworkTransport] * @see DefaultApolloException */ -@ApolloExperimental fun NetworkTransport.closeConnection(exception: ApolloException) { val webSocketNetworkTransport = (this as? WebSocketNetworkTransport) ?: throw IllegalArgumentException("'$this' is not an instance of com.apollographql.apollo.websocket.WebSocketNetworkTransport") @@ -317,7 +337,6 @@ fun NetworkTransport.closeConnection(exception: ApolloException) { * * A response is emitted with [ApolloResponse.exception] set to [ApolloWebSocketForceCloseException]. */ -@ApolloExperimental fun NetworkTransport.closeConnection() { val webSocketNetworkTransport = (this as? WebSocketNetworkTransport) ?: throw IllegalArgumentException("'$this' is not an instance of com.apollographql.apollo.websocket.WebSocketNetworkTransport") diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WsProtocol.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WsProtocol.kt index deaf2e94650..a29283d5c5d 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WsProtocol.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/websocket/WsProtocol.kt @@ -1,15 +1,13 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.Operation /** - * A [WsProtocol] manages different flavours of WebSocket protocols. + * A [WsProtocol] manages different flavors of WebSocket protocols. * * See [GraphQLWsProtocol], [AppSyncWsProtocol] and [SubscriptionWsProtocol] */ -@ApolloExperimental interface WsProtocol { val name: String diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/AppSyncWsProtocol.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/AppSyncWsProtocol.kt index 1daac9ffc94..127c3d2cb93 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/AppSyncWsProtocol.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/AppSyncWsProtocol.kt @@ -1,5 +1,8 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.NullableAnyAdapter import com.apollographql.apollo.api.Operation @@ -15,6 +18,8 @@ import kotlinx.coroutines.withTimeout /** * A [WsProtocol] for https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html */ +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) class AppSyncWsProtocol( private val connectionAcknowledgeTimeoutMs: Long, private val connectionPayload: suspend () -> Map? = { null }, diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/GraphQLWsProtocol.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/GraphQLWsProtocol.kt index 8d31d952f65..ddd5f33f169 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/GraphQLWsProtocol.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/GraphQLWsProtocol.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws import com.apollographql.apollo.annotations.ApolloDeprecatedSince @@ -17,6 +19,8 @@ import kotlinx.coroutines.withTimeout * An [WsProtocol] that uses https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md * It can carry queries in addition to subscriptions over the websocket */ +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) class GraphQLWsProtocol internal constructor( private val connectionPayload: suspend () -> Map? = { null }, private val pingPayload: Map? = null, diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/SubscriptionWsProtocol.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/SubscriptionWsProtocol.kt index b5437a485a8..f2e07f43817 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/SubscriptionWsProtocol.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/SubscriptionWsProtocol.kt @@ -1,5 +1,8 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.http.DefaultHttpRequestComposer @@ -13,6 +16,8 @@ import kotlin.jvm.JvmOverloads * * Note: This protocol is no longer actively maintained, and [GraphQLWsProtocol] should be favored instead. */ +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) class SubscriptionWsProtocol @JvmOverloads constructor( diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/SubscriptionWsProtocolAdapter.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/SubscriptionWsProtocolAdapter.kt index f5685d5e9be..a1d1ec3022b 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/SubscriptionWsProtocolAdapter.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/SubscriptionWsProtocolAdapter.kt @@ -1,8 +1,13 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.Operation +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) open class SubscriptionWsProtocolAdapter(webSocketConnection: WebSocketConnection, listener: Listener): WsProtocol(webSocketConnection, listener) { private val delegate = SubscriptionWsProtocol(webSocketConnection, listener) diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.kt index bbf6154d8e6..2cf43df78f7 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.kt @@ -1,11 +1,16 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.http.HttpHeader import okio.ByteString /** * The low-level WebSocket API. Implement this interface to customize how WebSockets are handled */ +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) interface WebSocketEngine { /** * Open the websocket. Suspends until the handshake is done @@ -16,6 +21,8 @@ interface WebSocketEngine { ): WebSocketConnection } +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) interface WebSocketConnection { /** * Suspends until a message is available and return it. If the message was binary, it is converted to a String @@ -48,6 +55,8 @@ interface WebSocketConnection { fun close() } +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) expect class DefaultWebSocketEngine() : WebSocketEngine{ override suspend fun open(url: String, headers: List): WebSocketConnection } diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WebSocketNetworkTransport.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WebSocketNetworkTransport.kt index 5c93a5ca2dc..f5f836c59d1 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WebSocketNetworkTransport.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WebSocketNetworkTransport.kt @@ -1,5 +1,8 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.ApolloResponse @@ -56,6 +59,8 @@ import kotlinx.coroutines.launch * The [WebSocketConnection] is opened when the first subscription is started and closed if there are no active subscriptions * after a given timeout. */ +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) class WebSocketNetworkTransport private constructor( private val serverUrl: (suspend () -> String), @@ -367,6 +372,8 @@ private constructor( messages.trySend(NetworkError(reason)) } + @Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) class Builder { private var serverUrl: (suspend () -> String)? = null private var headers: MutableList = mutableListOf() diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WsProtocol.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WsProtocol.kt index 833fd0c4507..4d08610b9a2 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WsProtocol.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/network/ws/WsProtocol.kt @@ -1,5 +1,8 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.AnyAdapter import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.CustomScalarAdapters @@ -23,6 +26,8 @@ import okio.Buffer * @param webSocketConnection the connection * @param listener a listener */ +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) abstract class WsProtocol( protected val webSocketConnection: WebSocketConnection, protected val listener: Listener, diff --git a/libraries/apollo-runtime/src/jsMain/kotlin/com/apollographql/apollo/network/ws/JsWebSocketEngine.kt b/libraries/apollo-runtime/src/jsMain/kotlin/com/apollographql/apollo/network/ws/JsWebSocketEngine.kt index 3de2aed55f8..ad25f01da66 100644 --- a/libraries/apollo-runtime/src/jsMain/kotlin/com/apollographql/apollo/network/ws/JsWebSocketEngine.kt +++ b/libraries/apollo-runtime/src/jsMain/kotlin/com/apollographql/apollo/network/ws/JsWebSocketEngine.kt @@ -1,5 +1,8 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.http.HttpHeader import io.ktor.http.Headers import io.ktor.http.URLBuilder @@ -16,6 +19,8 @@ import org.w3c.dom.events.Event import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") actual class DefaultWebSocketEngine : WebSocketEngine { actual override suspend fun open( diff --git a/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/OkHttpExtensions.kt b/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/OkHttpExtensions.kt index 50321e7f491..a02e6ba8e13 100644 --- a/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/OkHttpExtensions.kt +++ b/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/OkHttpExtensions.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network import com.apollographql.apollo.ApolloClient @@ -5,8 +7,10 @@ import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.http.HttpHeader import com.apollographql.apollo.network.http.DefaultHttpEngine import com.apollographql.apollo.network.http.HttpNetworkTransport +import com.apollographql.apollo.network.websocket.WebSocketEngine +import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport import com.apollographql.apollo.network.ws.DefaultWebSocketEngine -import com.apollographql.apollo.network.ws.WebSocketNetworkTransport +import com.apollographql.apollo.network.ws.WebSocketNetworkTransport as DeprecatedWebSocketNetworkTransport import okhttp3.Call import okhttp3.Headers import okhttp3.OkHttpClient @@ -17,11 +21,13 @@ import okhttp3.OkHttpClient * * See also [ApolloClient.Builder.httpEngine] and [ApolloClient.Builder.webSocketEngine] */ - fun ApolloClient.Builder.okHttpClient(okHttpClient: OkHttpClient) = apply { - @Suppress("DEPRECATION") httpEngine(DefaultHttpEngine(okHttpClient)) - webSocketEngine(DefaultWebSocketEngine(okHttpClient)) + subscriptionNetworkTransport( + WebSocketNetworkTransport.Builder() + .webSocketEngine(WebSocketEngine(okHttpClient)) + .build() + ) } /** @@ -36,7 +42,6 @@ fun ApolloClient.Builder.okHttpCallFactory(callFactory: Call.Factory) = apply { * Configures the [ApolloClient] to use the lazily initialized [callFactory] for network requests. */ fun ApolloClient.Builder.okHttpCallFactory(callFactory: () -> Call.Factory) = apply { - @Suppress("DEPRECATION") httpEngine(DefaultHttpEngine(callFactory)) } @@ -57,9 +62,11 @@ fun HttpNetworkTransport.Builder.okHttpCallFactory(okHttpCallFactory: Call.Facto } /** - * Configures the [WebSocketNetworkTransport] to use the [okHttpCallFactory] for network requests. + * Configures the [DeprecatedWebSocketNetworkTransport] to use the [okHttpCallFactory] for network requests. */ -fun WebSocketNetworkTransport.Builder.okHttpClient(okHttpClient: OkHttpClient) = apply { +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) +fun DeprecatedWebSocketNetworkTransport.Builder.okHttpClient(okHttpClient: OkHttpClient) = apply { webSocketEngine(DefaultWebSocketEngine(okHttpClient)) } diff --git a/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.jvm.kt b/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.jvm.kt index c1640db02f5..aa787fcb875 100644 --- a/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.jvm.kt +++ b/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.jvm.kt @@ -1,11 +1,10 @@ package com.apollographql.apollo.network.websocket -import com.apollographql.apollo.annotations.ApolloExperimental +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.http.HttpHeader import com.apollographql.apollo.exception.ApolloNetworkException import com.apollographql.apollo.network.defaultOkHttpClientBuilder import okhttp3.Headers -import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okio.ByteString @@ -121,7 +120,6 @@ actual fun WebSocketEngine(): WebSocketEngine = JvmWebSocketEngine { defaultOkHt * * This factory function accepts a function so that OkHttp is initialized from a background thread */ -@ApolloExperimental fun WebSocketEngine(webSocketFactory: () -> PlatformWebSocket.Factory): WebSocketEngine = JvmWebSocketEngine(webSocketFactory) /** @@ -130,5 +128,11 @@ fun WebSocketEngine(webSocketFactory: () -> PlatformWebSocket.Factory): WebSocke * Prefer using the factory function accepting a function so that OkHttp is initialized from a background thread. * See https://github.com/square/okhttp/pull/8248 */ -@ApolloExperimental -fun WebSocketEngine(webSocketFactory: PlatformWebSocket.Factory): WebSocketEngine = JvmWebSocketEngine(webSocketFactory) \ No newline at end of file +fun WebSocketEngine(webSocketFactory: PlatformWebSocket.Factory): WebSocketEngine = JvmWebSocketEngine(webSocketFactory) + +/** + * This is a convenience function to ease migration from v4 webSockets. + */ +@Deprecated("Use WebSocketEngine instead", ReplaceWith("WebSocketEngine(webSocketFactory)")) +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) +fun DefaultWebSocketEngine(webSocketFactory: PlatformWebSocket.Factory): WebSocketEngine = JvmWebSocketEngine(webSocketFactory) \ No newline at end of file diff --git a/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/ws/OkHttpWebSocketEngine.kt b/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/ws/OkHttpWebSocketEngine.kt index da06d06fae4..12c8ee8ae86 100644 --- a/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/ws/OkHttpWebSocketEngine.kt +++ b/libraries/apollo-runtime/src/jvmCommonMain/kotlin/com/apollographql/apollo/network/ws/OkHttpWebSocketEngine.kt @@ -1,5 +1,8 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.http.HttpHeader import com.apollographql.apollo.exception.ApolloNetworkException import com.apollographql.apollo.exception.ApolloWebSocketClosedException @@ -18,6 +21,8 @@ import okio.ByteString * * This constructor accepts a function so that OkHttp is initialized from a background thread */ +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) actual class DefaultWebSocketEngine( webSocketFactory: () -> WebSocket.Factory, ) : WebSocketEngine { diff --git a/libraries/apollo-runtime/src/jvmTest/kotlin/RetryWebSocketsTest.kt b/libraries/apollo-runtime/src/jvmTest/kotlin/RetryWebSocketsTest.kt index 2b8d3d8da4c..a5a3eb98414 100644 --- a/libraries/apollo-runtime/src/jvmTest/kotlin/RetryWebSocketsTest.kt +++ b/libraries/apollo-runtime/src/jvmTest/kotlin/RetryWebSocketsTest.kt @@ -1,13 +1,9 @@ import app.cash.turbine.test import com.apollographql.apollo.ApolloClient -import com.apollographql.apollo.api.ApolloRequest -import com.apollographql.apollo.api.ApolloResponse -import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.Subscription import com.apollographql.apollo.exception.ApolloHttpException import com.apollographql.apollo.exception.ApolloNetworkException -import com.apollographql.apollo.interceptor.ApolloInterceptor -import com.apollographql.apollo.interceptor.ApolloInterceptorChain +import com.apollographql.apollo.interceptor.RetryOnErrorInterceptor import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport import com.apollographql.apollo.testing.internal.runTest import com.apollographql.mockserver.MockResponse @@ -16,11 +12,7 @@ import com.apollographql.mockserver.awaitWebSocketRequest import com.apollographql.mockserver.enqueueWebSocket import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout @@ -160,21 +152,6 @@ class RetryWebSocketsTest { } } - class MyRetryOnErrorInterceptor : ApolloInterceptor { - data object RetryException : Exception() - - override fun intercept(request: ApolloRequest, chain: ApolloInterceptorChain): Flow> { - return chain.proceed(request).onEach { - if (request.retryOnError == true && it.exception != null && it.exception is ApolloNetworkException) { - throw RetryException - } - }.retryWhen { cause, attempt -> - cause is RetryException && attempt < 2 - }.catch { - if (it !is RetryException) throw it - } - } - } @Test fun customRetryOnErrorInterceptor() = runTest { @@ -186,10 +163,15 @@ class RetryWebSocketsTest { .serverUrl(mockServer.url()) .build() ) - .retryOnError { - it.operation is Subscription - } - .retryOnErrorInterceptor(MyRetryOnErrorInterceptor()) + .retryOnErrorInterceptor(RetryOnErrorInterceptor { context -> + if (context.request.operation is Subscription<*> + && context.response.exception is ApolloNetworkException + && context.attempt < 2) { + true + } else { + false + } + }) .build().use { apolloClient -> var serverWriter = mockServer.enqueueWebSocket(keepAlive = false) @@ -219,6 +201,10 @@ class RetryWebSocketsTest { // Make sure that no retry is done mockServer.awaitAnyRequest(timeout = 1.seconds) } + // The terminal item + awaitItem().exception.apply { + assertIs(this) + } serverWriter.close() awaitComplete() } diff --git a/libraries/apollo-runtime/src/linuxMain/kotlin/com/apollographql/apollo/network/ws/LinuxWebSocketEngine.kt b/libraries/apollo-runtime/src/linuxMain/kotlin/com/apollographql/apollo/network/ws/LinuxWebSocketEngine.kt index 7dcfc973a19..0104b6ae9d2 100644 --- a/libraries/apollo-runtime/src/linuxMain/kotlin/com/apollographql/apollo/network/ws/LinuxWebSocketEngine.kt +++ b/libraries/apollo-runtime/src/linuxMain/kotlin/com/apollographql/apollo/network/ws/LinuxWebSocketEngine.kt @@ -1,7 +1,11 @@ +@file:Suppress("DEPRECATION") package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.http.HttpHeader +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") actual class DefaultWebSocketEngine : WebSocketEngine { actual override suspend fun open( diff --git a/libraries/apollo-runtime/src/wasmJsMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.wasmJs.kt b/libraries/apollo-runtime/src/wasmJsMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.wasmJs.kt index 95fc51a57b5..294a86be758 100644 --- a/libraries/apollo-runtime/src/wasmJsMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.wasmJs.kt +++ b/libraries/apollo-runtime/src/wasmJsMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.wasmJs.kt @@ -1,7 +1,12 @@ +@file:Suppress("DEPRECATION") + package com.apollographql.apollo.network.ws +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.api.http.HttpHeader +@Deprecated("The websocket implementation has moved to 'com.apollographql.apollo.network.websocket'. See https://go.apollo.dev/ak-v5-websockets for more details.") +@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0) actual class DefaultWebSocketEngine actual constructor() : WebSocketEngine { actual override suspend fun open( url: String, diff --git a/tests/no-runtime/build.gradle.kts b/tests/no-runtime/build.gradle.kts index 36007c30494..0a2b24487f5 100644 --- a/tests/no-runtime/build.gradle.kts +++ b/tests/no-runtime/build.gradle.kts @@ -16,6 +16,6 @@ dependencies { apollo { service("service") { packageName.set("com.example") - schemaFiles.from(file("../sample-server/src/main/resources/schema.graphqls")) + schemaFiles.from(file("../sample-server/graphql/schema.graphqls")) } } diff --git a/tests/runtime/build.gradle.kts b/tests/runtime/build.gradle.kts index 2caf746ee87..49be5a65af0 100644 --- a/tests/runtime/build.gradle.kts +++ b/tests/runtime/build.gradle.kts @@ -21,6 +21,6 @@ apollo { @OptIn(ApolloExperimental::class) generateDataBuilders.set(true) packageName.set("com.example") - schemaFiles.from(file("../sample-server/src/main/resources/schema.graphqls")) + schemaFiles.from(file("../sample-server/graphql/schema.graphqls")) } } diff --git a/tests/sample-server/graphql/schema.graphqls b/tests/sample-server/graphql/schema.graphqls index 9d856f6e059..fc4f3b3109a 100644 --- a/tests/sample-server/graphql/schema.graphqls +++ b/tests/sample-server/graphql/schema.graphqls @@ -8,6 +8,10 @@ type MutationRoot { closeAllWebSockets: String! } +type Nested { + foo: Int +} + type QueryRoot { random: Int! @@ -33,7 +37,7 @@ type SubscriptionRoot { operationError: String! - graphqlAccessError(after: Int!): Int + graphqlAccessError(after: Int!): Nested! closeWebSocket: String! diff --git a/tests/sample-server/src/main/kotlin/com/apollographql/apollo/sample/server/graphql/SubscriptionRoot.kt b/tests/sample-server/src/main/kotlin/com/apollographql/apollo/sample/server/graphql/SubscriptionRoot.kt index 0b057a4ae75..1ced3740573 100644 --- a/tests/sample-server/src/main/kotlin/com/apollographql/apollo/sample/server/graphql/SubscriptionRoot.kt +++ b/tests/sample-server/src/main/kotlin/com/apollographql/apollo/sample/server/graphql/SubscriptionRoot.kt @@ -40,12 +40,12 @@ class SubscriptionRoot(private val tag: String) { throw Exception("Woops") } - fun graphqlAccessError(after: Int): Flow = flow { + fun graphqlAccessError(after: Int): Flow = flow { repeat(after) { - emit(it) + emit(Nested(false)) } - error("Woops") + emit(Nested(true)) } fun closeWebSocket(executionContext: ExecutionContext): Flow = flow { @@ -72,4 +72,15 @@ class SubscriptionRoot(private val tag: String) { class State( val tag: String, val subscriptionId: String -) \ No newline at end of file +) + +class Nested(private val shouldThrow: Boolean) { + val foo: Int? // TODO: not sure why this needs to be nullable + get() { + return if (shouldThrow) { + error("Cannot get 'foo' value") + } else { + 42 + } + } +} diff --git a/tests/sample-server/src/main/resources/schema.graphqls b/tests/sample-server/src/main/resources/schema.graphqls deleted file mode 100644 index 8e341a10d8f..00000000000 --- a/tests/sample-server/src/main/resources/schema.graphqls +++ /dev/null @@ -1,41 +0,0 @@ -type Query { - random: Int! - zero: Int! - secondsSinceEpoch: Float! - valueSharedWithSubscriptions: Int! -} - -type Mutation { - "Force close all websockets" - closeAllWebSockets: String! -} - -type Subscription { - "Count from 0 until 'to', waiting 'delayMillis' after each response" - count(intervalMillis: Int!, to: Int!): Int! - "Count from 0 until 'to', waiting 'delayMillis' after each response and returns each result as a String" - countString(intervalMillis: Int!, to: Int!): String! - "Trigger an error when accessed" - operationError: String! - "Returns a GraphQL error after 'after' items" - graphqlAccessError(after: Int! = 1): Int - - "Force close the websocket this subscription is executing on" - closeWebSocket: String! - - "Returns the current seconds every intervalMillis" - secondsSinceEpoch(intervalMillis: Int! = 1000): Float! - - "Returns the subscription state" - state(intervalMillis: Int! = 1000): State - - valueSharedWithSubscriptions: Int! -} - -type State { - "The tag of this server" - tag: String! - - "The subscription id" - subscriptionId: String! -} diff --git a/tests/websockets/build.gradle.kts b/tests/websockets/build.gradle.kts index 1bb36fde90b..cf0a6f8d000 100644 --- a/tests/websockets/build.gradle.kts +++ b/tests/websockets/build.gradle.kts @@ -36,7 +36,7 @@ apollo { }?.forEach { service(it.name) { if (it.name == "sample-server") { - schemaFiles.from(file("../sample-server/src/main/resources/schema.graphqls")) + schemaFiles.from(file("../sample-server/graphql/schema.graphqls")) } srcDir(it) packageName.set(it.name.replace("-", ".")) diff --git a/tests/websockets/src/commonMain/graphql/sample-server/operations.graphql b/tests/websockets/src/commonMain/graphql/sample-server/operations.graphql index a77423d3137..5ad5fec7aad 100644 --- a/tests/websockets/src/commonMain/graphql/sample-server/operations.graphql +++ b/tests/websockets/src/commonMain/graphql/sample-server/operations.graphql @@ -35,7 +35,9 @@ subscription CloseSocketSubscription { } subscription GraphqlAccessError($after: Int!) { - graphqlAccessError(after: $after) + graphqlAccessError(after: $after) { + foo + } } subscription Tag($intervalMillis: Int!) { diff --git a/tests/websockets/src/commonTest/kotlin/AppSyncTest.kt b/tests/websockets/src/commonTest/kotlin/AppSyncTest.kt index 58d4ee27f28..b5e36709086 100644 --- a/tests/websockets/src/commonTest/kotlin/AppSyncTest.kt +++ b/tests/websockets/src/commonTest/kotlin/AppSyncTest.kt @@ -1,7 +1,7 @@ import appsync.CommentsSubscription import com.apollographql.apollo.ApolloClient -import com.apollographql.apollo.network.ws.AppSyncWsProtocol -import com.apollographql.apollo.network.ws.WebSocketNetworkTransport +import com.apollographql.apollo.network.websocket.AppSyncWsProtocol +import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport import com.apollographql.apollo.testing.internal.runTest import kotlin.test.Ignore import kotlin.test.Test @@ -61,10 +61,8 @@ class AppSyncTest { val apolloClient = ApolloClient.Builder().networkTransport( networkTransport = WebSocketNetworkTransport.Builder().serverUrl( serverUrl = url, - ).protocol( - protocolFactory = AppSyncWsProtocol.Factory( - connectionPayload = { generateAuth() } - ) + ).wsProtocol( + AppSyncWsProtocol(authorization = { generateAuth() }) ).build() ).build() diff --git a/tests/websockets/src/commonTest/kotlin/GraphQLWsTest.kt b/tests/websockets/src/commonTest/kotlin/GraphQLWsTest.kt index 42711eb2b47..892c797d722 100644 --- a/tests/websockets/src/commonTest/kotlin/GraphQLWsTest.kt +++ b/tests/websockets/src/commonTest/kotlin/GraphQLWsTest.kt @@ -1,7 +1,7 @@ import com.apollographql.apollo.ApolloClient -import com.apollographql.apollo.network.ws.GraphQLWsProtocol -import com.apollographql.apollo.network.ws.WebSocketNetworkTransport +import com.apollographql.apollo.network.websocket.GraphQLWsProtocol +import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport import com.apollographql.apollo.testing.internal.runTest import graphql.ws.GreetingsSubscription import graphql.ws.HelloQuery @@ -25,8 +25,6 @@ class GraphQLWsTest { .networkTransport( WebSocketNetworkTransport.Builder().serverUrl( serverUrl = "http://localhost:9090/graphql", - ).protocol( - protocolFactory = GraphQLWsProtocol.Factory() ).build() ) .build() diff --git a/tests/websockets/src/jvmTest/kotlin/CachedSubscriptionTest.kt b/tests/websockets/src/jvmTest/kotlin/CachedSubscriptionTest.kt index 84c40eae978..098556610fb 100644 --- a/tests/websockets/src/jvmTest/kotlin/CachedSubscriptionTest.kt +++ b/tests/websockets/src/jvmTest/kotlin/CachedSubscriptionTest.kt @@ -1,3 +1,4 @@ +@file:Suppress("DEPRECATION") import com.apollographql.apollo.sample.server.SampleServer import com.apollographql.apollo.ApolloClient @@ -5,6 +6,8 @@ import com.apollographql.apollo.cache.normalized.ApolloStore import com.apollographql.apollo.cache.normalized.api.MemoryCacheFactory import com.apollographql.apollo.cache.normalized.store import com.apollographql.apollo.cache.normalized.watch +import com.apollographql.apollo.network.websocket.SubscriptionWsProtocol +import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.filter @@ -46,7 +49,12 @@ class CachedSubscriptionTest { val apolloClient = ApolloClient.Builder() .httpServerUrl(sampleServer.graphqlUrl()) - .webSocketServerUrl(sampleServer.subscriptionsUrl()) + .subscriptionNetworkTransport( + WebSocketNetworkTransport.Builder() + .serverUrl(sampleServer.subscriptionsUrl()) + .wsProtocol(SubscriptionWsProtocol()) + .build() + ) .store(store) .build() diff --git a/tests/websockets/src/jvmTest/kotlin/legacy/SampleServerTest.kt b/tests/websockets/src/jvmTest/kotlin/legacy/SampleServerTest.kt index 3a54af358c5..f825626a0c0 100644 --- a/tests/websockets/src/jvmTest/kotlin/legacy/SampleServerTest.kt +++ b/tests/websockets/src/jvmTest/kotlin/legacy/SampleServerTest.kt @@ -1,13 +1,14 @@ +@file:Suppress("DEPRECATION") + package legacy -import com.apollographql.apollo.sample.server.SampleServer import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.exception.SubscriptionOperationException -import com.apollographql.apollo.network.ws.SubscriptionWsProtocolAdapter -import com.apollographql.apollo.network.ws.WebSocketConnection -import com.apollographql.apollo.network.ws.WebSocketNetworkTransport -import com.apollographql.apollo.network.ws.WsProtocol -import kotlinx.coroutines.CoroutineScope +import com.apollographql.apollo.interceptor.RetryOnErrorInterceptor +import com.apollographql.apollo.network.websocket.SubscriptionWsProtocol +import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport +import com.apollographql.apollo.sample.server.SampleServer +import com.apollographql.apollo.testing.internal.runTest import kotlinx.coroutines.delay import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.first @@ -17,8 +18,6 @@ import kotlinx.coroutines.flow.single import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout import org.junit.AfterClass import org.junit.BeforeClass import org.junit.Test @@ -27,6 +26,7 @@ import sample.server.GraphqlAccessErrorSubscription import sample.server.OperationErrorSubscription import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.time.Duration.Companion.milliseconds class SampleServerTest { companion object { @@ -46,12 +46,8 @@ class SampleServerTest { } @Test - fun simple() { - val apolloClient = ApolloClient.Builder() - .serverUrl(sampleServer.subscriptionsUrl()) - .build() - - runBlocking { + fun simple() = runTest { + apolloClient().use { apolloClient -> val list = apolloClient.subscription(CountSubscription(5, 0)) .toFlow() .map { @@ -62,13 +58,22 @@ class SampleServerTest { } } - @Test - fun interleavedSubscriptions() { - val apolloClient = ApolloClient.Builder() - .serverUrl(sampleServer.subscriptionsUrl()) - .build() + private fun apolloClientBuilder(block: WebSocketNetworkTransport.Builder.() -> Unit = {}) = ApolloClient.Builder() + .subscriptionNetworkTransport( + WebSocketNetworkTransport.Builder() + .wsProtocol(SubscriptionWsProtocol()) + .serverUrl(sampleServer.subscriptionsUrl()) + .apply(block) + .build() + ) + + private fun apolloClient(block: WebSocketNetworkTransport.Builder.() -> Unit = {}): ApolloClient { + return apolloClientBuilder(block).build() + } - runBlocking { + @Test + fun interleavedSubscriptions() = runTest { + apolloClient().use { apolloClient -> val items = mutableListOf() launch { apolloClient.subscription(CountSubscription(5, 1000)) @@ -88,24 +93,12 @@ class SampleServerTest { } @Test - fun idleTimeout() { - val transport = WebSocketNetworkTransport.Builder().serverUrl( - serverUrl = sampleServer.subscriptionsUrl(), - ).idleTimeoutMillis( - idleTimeoutMillis = 1000 - ).build() - - val apolloClient = ApolloClient.Builder() - .networkTransport(transport) - .build() - - runBlocking { + fun idleTimeout() = runTest { + apolloClient { + idleTimeout(1000.milliseconds) + }.use { apolloClient -> apolloClient.subscription(CountSubscription(50, 1000)).toFlow().first() - withTimeout(500) { - transport.subscriptionCount.first { it == 0 } - } - delay(1500) val number = apolloClient.subscription(CountSubscription(50, 0)).toFlow().drop(3).first().data?.count assertEquals(3, number) @@ -113,10 +106,8 @@ class SampleServerTest { } @Test - fun slowConsumer() { - val apolloClient = ApolloClient.Builder().serverUrl(serverUrl = sampleServer.subscriptionsUrl()).build() - - runBlocking { + fun slowConsumer() = runTest { + apolloClient().use { apolloClient -> /** * Take 3 items, delaying the first items by 100ms in total. * During that time, the server should continue sending. Then resume reading as fast as we can @@ -138,38 +129,18 @@ class SampleServerTest { } @Test - fun serverTermination() { - val transport = WebSocketNetworkTransport.Builder().serverUrl( - serverUrl = sampleServer.subscriptionsUrl(), - ).idleTimeoutMillis( - idleTimeoutMillis = 1000 - ).build() - - val apolloClient = ApolloClient.Builder() - .networkTransport(transport) - .build() - runBlocking { + fun serverTermination() = runTest { + apolloClient().use { apolloClient -> /** * Collect all items the server sends us */ apolloClient.subscription(CountSubscription(50, 0)).toFlow().toList() - - /** - * Make sure we're unsubscribed - */ - withTimeout(500) { - transport.subscriptionCount.first { it == 0 } - } } } @Test - fun operationError() { - val apolloClient = ApolloClient.Builder() - .serverUrl(sampleServer.subscriptionsUrl()) - .build() - - runBlocking { + fun operationError() = runTest { + apolloClient().use { apolloClient -> val response = apolloClient.subscription(OperationErrorSubscription()) .toFlow() .single() @@ -183,70 +154,27 @@ class SampleServerTest { private inline fun Any?.cast() = this as T - private object AuthorizationException : Exception() - - private class AuthorizationAwareWsProtocol( - webSocketConnection: WebSocketConnection, - listener: Listener, - ) : SubscriptionWsProtocolAdapter(webSocketConnection, listener) { - @Suppress("UNCHECKED_CAST") - private fun Any?.asMap() = this as? Map - - @Suppress("UNCHECKED_CAST") - private fun Any?.asList() = this as? List - - override fun handleServerMessage(messageMap: Map) { - /** - * For this test, we use the sample server and I haven't figured out a way to make it out errors yet so we just check - * if the value is null. A more real life example would do something like below - * val isError = messageMap.get("payload") - * ?.asMap() - * ?.get("errors") - * ?.asList() - * ?.first() - * ?.asMap() - * ?.get("message") == "Unauthorized error" - */ - val isError = messageMap.get("payload")?.asMap()?.get("data")?.asMap()?.get("graphqlAccessError") == null - if (isError) { - /** - * The server returned a message with an error and no data. Send a general error upstream - * so that the WebSocket is restarted - */ - listener.networkError(AuthorizationException) - } else { - super.handleServerMessage(messageMap) - } - } - } - - class AuthorizationAwareWsProtocolFactory : WsProtocol.Factory { - override val name: String - get() = "graphql-ws" - - override fun create(webSocketConnection: WebSocketConnection, listener: WsProtocol.Listener, scope: CoroutineScope): WsProtocol { - return AuthorizationAwareWsProtocol(webSocketConnection, listener) - } - } @Test - fun canResumeAfterGraphQLError() { - val wsFactory = AuthorizationAwareWsProtocolFactory() - val apolloClient = ApolloClient.Builder() - .serverUrl(sampleServer.subscriptionsUrl()) - .wsProtocol(wsFactory) - .webSocketReopenWhen { e, _ -> - e is AuthorizationException + fun canResumeAfterGraphQLError() = runTest { + apolloClientBuilder() + .retryOnErrorInterceptor(RetryOnErrorInterceptor { + val error = it.response.errors.orEmpty().firstOrNull() + if (error != null) { + return@RetryOnErrorInterceptor true + } else { + return@RetryOnErrorInterceptor false + } + }) + .build().use { apolloClient -> + val list = apolloClient.subscription(GraphqlAccessErrorSubscription(1)) + .toFlow() + .map { + it.data?.graphqlAccessError?.foo + } + .take(2) + .toList() + assertEquals(listOf(42, 42), list) } - .build() - - runBlocking { - val list = apolloClient.subscription(GraphqlAccessErrorSubscription(1)) - .toFlow() - .map { it.data!!.graphqlAccessError } - .take(2) - .toList() - assertEquals(listOf(0, 0), list) - } } } \ No newline at end of file