apollo-kotlin icon indicating copy to clipboard operation
apollo-kotlin copied to clipboard

Subscription Error: Cannot convert undefined or null to object

Open chris-guidry opened this issue 1 year ago • 3 comments

Version

3.8.2

Summary

I am setting up a new subscription using a localhost GraphQL server. When calling ApolloClient.subscription, this error occurs: Cannot convert undefined or null to object. The ApolloClient works for queries and mutations, but not subscriptions. This subscription works in GraphiQL.

Steps to reproduce the behavior

Here is the ApolloClient setup:

    val ApolloClient = ApolloClient.Builder()
        .serverUrl("http://10.0.2.2:8000/graphql")
        .webSocketServerUrl("ws://10.0.2.2:8000/graphql")
        .webSocketReopenWhen { throwable, attempt ->
            Log.d("ApolloClient", "WebSocket got disconnected, reopening after a delay", throwable)
            delay(attempt * 1000)
            true
        }
        .okHttpClient(
                OkHttpClient.Builder()
                    .addInterceptor(AuthorizationInterceptor())
                    .build()
        )
        .build()

Here is the subscription:

subscription RoleSampleSubscription {
    listen(topic: "RoleSample") {
        query {
            allRoles {
                nodes {
                    id
                    name
                }
            }
        }
    }
}

Here is the call to the subscription, which triggers the error:

val roleSubscription = ApolloClient.subscription(RoleSampleSubscription()).toFlow()

When the same subscription is initiated in GraphiQL, it initially shows, "Waiting for subscription to yield data…". Then, when this call is made in the PostgreSQL database, "SELECT pg_notify('postgraphile:RoleSample', '');", the rows are returned.

Note the GraphQL server is setup using Postgraphile.

Logs

WebSocket got disconnected, reopening after a delay
	com.apollographql.apollo3.exception.ApolloNetworkException: Connection error:
	{type=connection_error, payload={message=Cannot convert undefined or null to object}}
		at com.apollographql.apollo3.network.ws.SubscriptionWsProtocol$connectionInit$2.invokeSuspend(SubscriptionWsProtocol.kt:42)
		at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
		at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
		at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
		at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
		at java.lang.Thread.run(Thread.java:1012)

chris-guidry avatar Feb 02 '24 22:02 chris-guidry

Hi 👋

This error ({message=Cannot convert undefined or null to object}) is returned by your server in response to the connection_init message.

There are different possible websockets protocols for subscriptions and I'm suspecting the server you are talking to expects a different protocol than the Apollo Kotlin default. Can you try using graphql-ws?

val apolloClient = ApolloClient.Builder()
    .subscriptionNetworkTransport(
        WebSocketNetworkTransport.Builder()
            .protocol(GraphQLWsProtocol.Factory()) 
            .serverUrl("ws://10.0.2.2:8000/graphql")
            .build()
    )
    .build()

If that doesn't work, you have more options in that page of the documentation.

Something else worth trying is double check you url. Sometimes servers use a different path for subscriptions vs queries/mutation.

Hope this helps. Let me know how that goes!

martinbonnin avatar Feb 02 '24 23:02 martinbonnin

Hi @chris-guidry any news here? Where you able to fin a solution?

martinbonnin avatar Feb 08 '24 10:02 martinbonnin

@martinbonnin I apologize I have not had a chance to get back to testing those suggestions, but I really appreciate your input and I will try changing the protocol parameter soon!

chris-guidry avatar Feb 09 '24 02:02 chris-guidry

@chris-guidry I'll close this one for now. Please let us know if you need anything from us and I'll reopen.

martinbonnin avatar Apr 29 '24 15:04 martinbonnin

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Kotlin usage and allow us to serve you better.

github-actions[bot] avatar Apr 29 '24 15:04 github-actions[bot]

Hi @martinbonnin, I finally had a chance to test a few changes, but the issue is still not resolved :-(

To verify the server's URL and protocol, I reviewed the settings in GraphiQL since subscriptions are working there. In Chrome the websocket Request URL matches the settings I am using so I don't think that's the problem. Under Sec-Websocket-Protocol, it shows "graphql-transport-ws". Is that the same as graphql-ws?

On the Kotlin client side, I tried adding the subscriptionNetworkTransport parameter to the ApolloClient configuration as suggested, but then when the subscription is initiated, this error occurs: IllegalStateException: Apollo: 'webSocketEngine' has no effect if 'subscriptionNetworkTransport' is set. I have not found any references in my code to "webSocketEngine" so I'm not sure how to resolve that error.

Also, I believe my GraphQL server supports both subscriptions-transport-ws and graphql-ws protocols based on this post: https://github.com/graphile/crystal/issues/1525

chris-guidry avatar May 01 '24 16:05 chris-guidry

it shows "graphql-transport-ws". Is that the same as graphql-ws?

This is super confusing but most likely yes, see https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#communication

IllegalStateException: Apollo: 'webSocketEngine' has no effect if 'subscriptionNetworkTransport' is set. I have not found any references in my code to "webSocketEngine" so I'm not sure how to resolve that error.

This is unexpected. Can you try from a plain builder?

val apolloClient = ApolloClient.Builder()
        .serverUrl("http://localhost:9090/graphql")
        .subscriptionNetworkTransport(
            WebSocketNetworkTransport.Builder().serverUrl(
                serverUrl = "http://localhost:9090/graphql",
            ).protocol(
                protocolFactory = GraphQLWsProtocol.Factory()
            ).build()
        )
        .build()

martinbonnin avatar May 01 '24 19:05 martinbonnin

Well, it's mostly working now. I thought the websocket URL needed to use the "ws://" protocol, but it's working with "http://".

The lingering issue is that enabling the parameters webSocketReopenWhen and okHttpClient seem to be the cause of the error, 'webSocketEngine' has no effect if 'subscriptionNetworkTransport' is set. Do those two parameters need to be applied differently in this case?

Here is what it looks like right now:

    val ApolloClient = ApolloClient.Builder()
        .serverUrl(BuildConfig.GRAPHQL_URL)
        .subscriptionNetworkTransport(
            WebSocketNetworkTransport
                .Builder()
                .serverUrl(BuildConfig.GRAPHQL_URL)
                .protocol(GraphQLWsProtocol.Factory())
                .build()
        )
//        .webSocketReopenWhen { throwable, attempt ->
//            Log.d("ApolloClient", "WebSocket got disconnected, reopening after a delay", throwable)
//            delay(attempt * 1000)
//            true
//        }
//        .okHttpClient(
//                OkHttpClient.Builder()
//                    .addInterceptor(AuthorizationInterceptor())
//                    .build()
//        )
        .build()

chris-guidry avatar May 01 '24 19:05 chris-guidry

You can set both of those parameters on WebSocketNetworkTransport.Builder directly:

  val ApolloClient = ApolloClient.Builder()
    .serverUrl(BuildConfig.GRAPHQL_URL)
    .subscriptionNetworkTransport(
      WebSocketNetworkTransport
        .Builder()
        .okHttpClient(okHttpClient)
        .reopenWhen { throwable, attempt ->  }
        .serverUrl(BuildConfig.GRAPHQL_URL)
        .protocol(GraphQLWsProtocol.Factory())
        .build()
    )

ApolloClient.Builder.okHttpClient(OkHttpClient) is a shortcut that configures the engine but that doesn't work if you use the "full" WebSocketNetworkTransport API

martinbonnin avatar May 01 '24 20:05 martinbonnin

@martinbonnin thank you so much for your help! The subscription is working well now.

chris-guidry avatar May 01 '24 21:05 chris-guidry

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Kotlin usage and allow us to serve you better.

github-actions[bot] avatar May 01 '24 21:05 github-actions[bot]

@martinbonnin I don't need this issue re-opened, but I wanted to mention for anyone reading this later that adding the okHttpClient as part of the WebSocketNetworkTransport builder compiled, but didn't work when using addInterceptor with the OkHttpClient builder to append a JWT to server requests (queries and mutations).

Instead, I added it to the ApolloClient builder this way: .httpEngine(DefaultHttpEngine(okHttpClient)).

chris-guidry avatar May 14 '24 16:05 chris-guidry