Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Subscription Error: Cannot convert undefined or null to object #5593

Closed
chris-guidry opened this issue Feb 2, 2024 · 12 comments
Closed

Subscription Error: Cannot convert undefined or null to object #5593

chris-guidry opened this issue Feb 2, 2024 · 12 comments
Labels
🐛 Type: Bug ⌛ Waiting for info More information is required

Comments

@chris-guidry
Copy link

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)
@martinbonnin
Copy link
Contributor

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
Copy link
Contributor

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

@martinbonnin martinbonnin added the ⌛ Waiting for info More information is required label Feb 8, 2024
@chris-guidry
Copy link
Author

@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!

@martinbonnin
Copy link
Contributor

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

Copy link
Contributor

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.

@chris-guidry
Copy link
Author

chris-guidry commented May 1, 2024

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: graphile/crystal#1525

@martinbonnin martinbonnin reopened this May 1, 2024
@martinbonnin
Copy link
Contributor

martinbonnin commented May 1, 2024

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()

@chris-guidry
Copy link
Author

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()

@martinbonnin
Copy link
Contributor

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

@chris-guidry
Copy link
Author

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

Copy link
Contributor

github-actions bot commented May 1, 2024

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.

@chris-guidry
Copy link
Author

@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)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 Type: Bug ⌛ Waiting for info More information is required
Projects
None yet
Development

No branches or pull requests

2 participants