Implementation of the subscriptions-transport-ws graphql subscription protocol for Absinthe. Instead of using Absinthe subscriptions over Phoenix channels it exposes a websocket directly. This allows you to use
the Apollo and Urql Graphql clients without using a translation layer to Phoenix channels such as @absinthe/socket
.
Has been tested with Apollo iOS/ Apollo JS and Urql with subscriptions-transport-ws.
subscriptions-transport-ws
is an older protocol. A newer one has been written named graphql_ws. The grapqhl_ws protocol is more robust, and the way to go in the future.
At the time of writing the major libraries support one, the other or both. E.g. Apollo Swift currently only supports subscriptions-transport-ws
, v3 of Apollo Android supports graphql_ws
. The Urlq/Apollo JS libraries support either one.
If you need to support graphql_ws
on the backend in Elixir, you can use the absinthe_graphql_ws library. You can set up multiple websocket endpoints to support both protocols.
The package can be installed by adding subscriptions_transport_ws
to your list of dependencies in mix.exs
:
def deps do
[
{:subscriptions_transport_ws, "~> 1.0.0"}
]
end
There are several steps to use this library.
You need to have a working phoenix pubsub configured. Here is what the default looks like if you create a new phoenix project:
config :my_app, MyAppWeb.Endpoint,
# ... other config
pubsub_server: MyApp.PubSub
In your application supervisor add a line AFTER your existing endpoint supervision line:
[
# other children ...
MyAppWeb.Endpoint, # this line should already exist
{Absinthe.Subscription, MyAppWeb.Endpoint}, # add this line
# other children ...
]
Where MyAppWeb.Endpoint is the name of your application's phoenix endpoint.
Add a module in your app lib/web/channels/absinthe_socket.ex
defmodule AbsintheSocket do
# App.GraphqlSchema is your graphql schema
use SubscriptionsTransportWS.Socket, schema: App.GraphqlSchema, keep_alive: 1000
# Callback similar to default Phoenix UserSocket
@impl true
def connect(params, socket) do
{:ok, socket}
end
# Callback to authenticate the user
@impl true
def gql_connection_init(message, socket) do
{:ok, socket}
end
end
In your MyAppWeb.Endpoint module add:
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
use Absinthe.Phoenix.Endpoint
socket("/absinthe-ws", AbsintheSocket, websocket: [subprotocols: ["graphql-ws"]])
# ...
end
Now if you start your app you can connect to the socket on ws://localhost:4000/absinthe-ws/websocket
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
useSubscription,
} from "@apollo/client";
import { split, HttpLink } from "@apollo/client";
import { getMainDefinition } from "@apollo/client/utilities";
import { WebSocketLink } from "@apollo/client/link/ws";
const wsLink = new WebSocketLink({
uri: "ws://localhost:4000/absinthe-ws/websocket",
options: {
reconnect: true,
},
});
const httpLink = new HttpLink({
uri: "http://localhost:4000/api",
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
const client = new ApolloClient({
uri: "http://localhost:4000/api",
cache: new InMemoryCache(),
link: splitLink,
});
See the Apollo documentation for more information
import { SubscriptionClient } from "subscriptions-transport-ws";
import {
useSubscription,
Provider,
defaultExchanges,
subscriptionExchange,
} from "urql";
const subscriptionClient = new SubscriptionClient(
"ws://localhost:4000/absinthe-ws/websocket",
{
reconnect: true,
}
);
const client = new Client({
url: "http://localhost:4000/api",
exchanges: [
subscriptionExchange({
forwardSubscription(operation) {
return subscriptionClient.request(operation);
},
}),
...defaultExchanges,
],
});
See the Urql documentation for more information.
import Apollo
import ApolloSQLite
import ApolloWebSocket
import Foundation
import Combine
class ApolloService {
static let shared = ApolloService()
static let url = Config.host.appendingPathComponent("api")
private(set) lazy var client: ApolloClient = {
let store = ApolloStore()
let requestChainTransport = RequestChainNetworkTransport(
interceptorProvider: DefaultInterceptorProvider(store: store),
endpointURL: "https://localhost:4000/api"
)
// The Normal Apollo Web Socket Implementation which uses an Apollo adapter server side
let wsUrl = "wss://localhost:4000/absinthe-ws/websocket"
let wsRequest = URLRequest(url: wsUrl)
let wsClient = WebSocket(request: wsRequest)
let apolloWebSocketTransport = WebSocketTransport(websocket: wsClient)
let splitNetworkTransport = SplitNetworkTransport(
uploadingNetworkTransport: requestChainTransport,
webSocketNetworkTransport: apolloWebSocketTransport
)
// Remember to give the store you already created to the client so it
// doesn't create one on its own
let client = ApolloClient(
networkTransport: splitNetworkTransport,
store: store
)
return client
}()
}
Or see here https://www.apollographql.com/docs/ios/subscriptions/#subscriptions-and-authorization-tokens
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/subscriptions_transport_ws.