Skip to content

Conversation

aryapreetam
Copy link
Contributor

@aryapreetam aryapreetam commented Jul 25, 2025

🚀 Add GraphQL subscriptions support for wasmJs target

📋 Summary

This PR implements complete WebSocket support for the wasmJs platform(in Compose Multiplatform), enabling GraphQL subscriptions to work seamlessly in WebAssembly JavaScript environments. This addresses the missing functionality preventing wasmJs applications from using real-time GraphQL subscriptions.

🔗 Fixes: #5862

✨ What's New

  • Complete wasmJs WebSocket Engine Implementation - Two comprehensive implementations covering both legacy and modern Apollo APIs
  • Browser WebSocket API Integration - Native browser WebSocket support with proper error handling
  • Binary Data Processing: Binary data uses JavaScript ArrayBuffer interop
  • Robust Error Management - Comprehensive error handling with clear messaging for unsupported features
  • Production Ready - Includes flow control, connection lifecycle management, and security considerations(Even though everything works as expected, this implementation is still experimental)

🛠️ Technical Implementation

Files Added/Modified:

  • libraries/apollo-runtime/src/wasmJsMain/kotlin/com/apollographql/apollo/network/ws/WebSocketEngine.wasmJs.kt - Legacy coroutine-based API
  • libraries/apollo-runtime/src/wasmJsMain/kotlin/com/apollographql/apollo/network/websocket/WebSocketEngine.wasmJs.kt - Modern listener-based API

Key Features:

  • Platform-Aware Design: Leverages browser WebSocket API while handling wasmJs platform limitations
  • Dual API Support: Implements both /ws/ (legacy) and /websocket/ (modern) APIs consistently
  • Smart Binary Handling: Handles binary data natively using JavaScript ArrayBuffer/Uint8Array with wasmJs interop
  • Security First: Validates headers and fails fast on unsupported features instead of silent failures
  • Memory Safe: Implements proper connection cleanup and buffer management

📹 Demo Video

web(wasmJs) android ios
desktop
apollo-kotlin-demo-720p.mp4

🔗 Demo Repository

Working Demo: apollo-kotlin-demo - Complete Compose Multiplatform app showcasing wasmJs GraphQL subscriptions

🧪 Platform Support

Platform Status Notes
wasmJs (Browser) Full Support Native WebSocket API integration
wasmJs (Node.js) ❌ Not Supported Browser-focused implementation
Other Platforms Unchanged No impact on existing functionality

Binary Data support:

apollo-kotlin-demo-with-binary_1080p.mov

⚠️ Important Limitations & Considerations

Browser WebSocket API Constraints:

  • No Custom Headers: Browser WebSocket API doesn't support custom headers (authentication should use connectionPayload or URL-based methods)
  • Binary Data Processing: Binary data uses JavaScript ArrayBuffer interop
  • Simplified Error Info: Limited error details compared to other platforms due to wasmJs constraints

Security & Performance:

  • ✅ Header validation prevents silent authentication failures
  • ✅ Flow control prevents memory exhaustion attacks
  • ✅ Proper connection lifecycle management
  • ⚠️ Byte-by-byte conversion overhead for binary data due to wasmJs interop constraints

🚀 Getting Started

// Works seamlessly with existing Apollo Kotlin code
val apolloClient = ApolloClient.Builder()
    .serverUrl("wss://your-graphql-server.com/graphql")
    .subscriptionNetworkTransport(
        WebSocketNetworkTransport.Builder()
            .serverUrl("wss://your-graphql-server.com/graphql")
            .build()
    )
    .build()

// Subscribe to real-time data
apolloClient.subscription(YourSubscription()).collect { response ->
    // Handle subscription data
}

✅ Testing

  • Compilation Testing: All wasmJs targets compile successfully
  • Integration Testing: WebSocket connections and message handling verified(manual)
  • Demo Application: Full working demo with real GraphQL subscriptions
  • Error Handling: Comprehensive error scenarios tested
  • ⚠️ Test suite: I couldn't successfully run the test suite on my machine. Might need your help.

🎯 Impact

This implementation unlocks real-time GraphQL capabilities for wasmJs applications, enabling:

  • 📈 Real-time dashboards in web applications
  • 💬 Live chat applications in Compose Web
  • 🔄 Live data synchronization across platforms
  • 📊 Real-time analytics and monitoring tools

🔄 Breaking Changes

None - This is a pure addition that doesn't affect existing functionality.

📚 Additional Context

This implementation required careful navigation of wasmJs platform constraints including:

  • JavaScript interop limitations requiring simplified js() function calls
  • Browser WebSocket API limitations affecting header support
  • Type system constraints requiring specialized error handling approaches

The solution maintains full compatibility with existing Apollo Kotlin APIs while providing robust, production-ready WebSocket support for wasmJs environments.


Ready for Review 🚀 This PR brings GraphQL subscriptions to the wasmJs ecosystem, completing Apollo Kotlin's multiplatform story!

@apollo-cla
Copy link

@aryapreetam: Thank you for submitting a pull request! Before we can merge it, you'll need to sign the Apollo Contributor License Agreement here: https://contribute.apollographql.com/

@aryapreetam aryapreetam changed the title 🚀 Add GraphQL subscriptions support for wasmJs target in Compose Multiplatform 🚀 Add GraphQL subscriptions support for wasmJs target Jul 25, 2025
Copy link
Contributor

@martinbonnin martinbonnin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks you so much for looking into this and providing a nice sample app!

A few comments from an early look at the pull request. I'll also work on adding tests in the repo.

Comment on lines 57 to 59
val stringData = data.toString()
listener.onMessage(stringData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have more details why binary data wouldn't work? I'm assuming the browser has some kind of Buffer type that we could potentially use here?

Copy link
Contributor Author

@aryapreetam aryapreetam Jul 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasmJs platform limitation - cannot handle binary WebSocket messages due to:

Limited interop with JavaScript binary types:
Kotlin/Wasm JavaScript interop documentation explains that only primitive types and strings can be passed directly between Kotlin/Wasm and JavaScript

as you can see here https://kotlinlang.org/docs/wasm-js-interop.html#type-correspondence, only premitive types are supported.
we could have considered something like Buffer type but it should have its equivalent js object as given here https://kotlinlang.org/docs/wasm-js-interop.html#jsany-type. considering Buffer to be of just JsAny may not be a good option(but we can experiment with it of course).

please let me know your inputs on this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long term, we could probably do something with ByteArray like there: https://kotlinlang.org/docs/wasm-js-interop.html#array-interoperability.

But for now, can we throw if binary data is received? There is little chance calling toString() on binarry data will work. I'd rather have a proper error message than undefined behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me do a quick experiment with binary data. I didn't look into it before since I didn't need.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@martinbonnin I have added binary data support for wasmJs. You can check the demo source and web app.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still uses Text websocket frames so it didn't test all the paths. I've just pushed a few changes to add an integration test as well as fix binary frames for both wasm and JS (turns out this wasn't working either)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried adding ByteArray as custom scalar type, but it didn't work. Do you have a sample server that uses binary data?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's implementation dependent. I'm not 100% sure I have seen one TBH, which is why it was probably fine to leave it out and throw. But if we're doing it, might as well do it right. The echo test server is using plain WebSockets, without GraphQL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the demo app, but the binary support only works for development build(wasmJsBrowserDevelopmentExecutableDistribution), and it will give error if binary data used in production(wasmJsBrowserDistribution)

Below, I ran it with wasmJsBrowserProductionRun

Screenshot 2025-07-29 at 9 44 40 PM

It fails at listener.onMessage(byteArray). I think wherever this listener is used, the binary data is not handled properly there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://youtrack.jetbrains.com/issue/KT-79673/, this seems to be a wasm optimization issue or something like so. I just relaunched the tests. If everything passes, I'll merge this PR.

@martinbonnin martinbonnin merged commit 99ad754 into apollographql:main Jul 30, 2025
6 checks passed
@martinbonnin
Copy link
Contributor

Thanks for the contribution!

@aryapreetam
Copy link
Contributor Author

thanks for all the support!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants