[](https://badge.fury.io/js/subscriptions-transport-ws) [](https://github.com/apollostack/subscriptions-transport-ws/blob/license/LICENSE)
# subscriptions-transport-ws
**(Work in progress!)**
A GraphQL WebSocket server and client to facilitate GraphQL queries, mutations and subscriptions over WebSocket.
> `subscriptions-transport-ws` is an extension for GraphQL, and you can use it with any GraphQL client and server (not only Apollo).
See [GitHunt-API](https://github.com/apollostack/GitHunt-API) and [GitHunt-React](https://github.com/apollostack/GitHunt-React) for an example server and client integration.
# Getting Started
Start by installing the package, using Yarn or NPM.
Using Yarn:
$ yarn add subscriptions-transport-ws
Or, using NPM:
$ npm install --save subscriptions-transport-ws
> Note that you need to use this package on both GraphQL client and server.
> This command also installs this package's dependencies, including `graphql-subscriptions`.
## Server
Starting with the server, create a new simple `PubSub` instance. We will later use this `PubSub` to publish and subscribe to data changes.
```js
import { PubSub } from 'graphql-subscriptions';
export const pubsub = new PubSub();
```
Now, create `SubscriptionServer` instance, with your GraphQL `schema`, `execute` and `subscribe` (from `graphql-js` package):
```js
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { execute, subscribe } from 'graphql';
import { schema } from './my-schema';
const WS_PORT = 5000;
// Create WebSocket listener server
const websocketServer = createServer((request, response) => {
response.writeHead(404);
response.end();
});
// Bind it to port and start listening
websocketServer.listen(WS_PORT, () => console.log(
`Websocket Server is now running on http://localhost:${WS_PORT}`
));
const subscriptionServer = SubscriptionServer.create(
{
schema,
execute,
subscribe,
},
{
server: websocketServer,
path: '/graphql',
},
);
```
### Creating Your Subscriptions
Please refer to [`graphql-subscriptions`](https://github.com/apollographql/graphql-subscriptions) documentation for how to create your GraphQL subscriptions, and how to publish data.
## Client (browser)
When using this package for client side, you can choose either use HTTP request for Queries and Mutation and use the WebSocket for subscriptions only, or create a full transport that handles all type of GraphQL operations over the socket.
### Full WebSocket Transport
To start with a full WebSocket transport, that handles all types of GraphQL operations, import and create an instance of `SubscriptionClient`.
Then, create your `ApolloClient` instance and use the `SubscriptionsClient` instance as network interface:
```js
import { SubscriptionClient } from 'subscriptions-transport-ws';
import ApolloClient from 'apollo-client';
const GRAPHQL_ENDPOINT = 'ws://localhost:3000/graphql';
const client = new SubscriptionClient(GRAPHQL_ENDPOINT, {
reconnect: true,
});
const apolloClient = new ApolloClient({
networkInterface: client,
});
```
### Hybrid WebSocket Transport
To start with a hybrid WebSocket transport, that handles only `subscription`s over WebSocket, create your `SubscriptionClient` and a regular HTTP network interface, then extend your network interface to use the WebSocket client for GraphQL subscriptions:
```js
import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws';
import ApolloClient, {createNetworkInterface} from 'apollo-client';
// Create regular NetworkInterface by using apollo-client's API:
const networkInterface = createNetworkInterface({
uri: 'http://localhost:3000' // Your GraphQL endpoint
});
// Create WebSocket client
const wsClient = new SubscriptionClient(`ws://localhost:5000/`, {
reconnect: true,
connectionParams: {
// Pass any arguments you want for initialization
}
});
// Extend the network interface with the WebSocket
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
networkInterface,
wsClient
);
// Finally, create your ApolloClient instance with the modified network interface
const apolloClient = new ApolloClient({
networkInterface: networkInterfaceWithSubscriptions
});
```
Now, when you want to use subscriptions in client side, use your `ApolloClient` instance, with [`subscribe`](http://dev.apollodata.com/core/apollo-client-api.html#ObservableQuery\.subscribe) or `query` [`subscribeToMore`](http://dev.apollodata.com/react/subscriptions.html#subscribe-to-more):
```js
apolloClient.subscribe({
query: gql`
subscription onNewItem {
newItemCreated {
id
}
}`,
variables: {}
}).subscribe({
next (data) {
// Notify your application with the new arrived data
}
});
```
```js
apolloClient.query({
query: ITEM_LIST_QUERY,
variables: {}
}).subscribeToMore({
document: gql`
subscription onNewItem {
newItemCreated {
id
}
}`,
variables: {},
updateQuery: (prev, { subscriptionData, variables }) => {
// Perform updates on previousResult with subscriptionData
return updatedResult;
}
});
```
If you don't use any package/modules loader, you can still use this package, by using `unpkg` service, and get the client side package from:
```
https://unpkg.com/subscriptions-transport-ws@VERSION/browser/client.js
```
> Replace VERSION with the latest version of the package.
## Use it with GraphiQL
You can use this package's power with GraphiQL, and subscribe to live-data stream inside GraphiQL.
If you are using the latest version of `graphql-server` flavors (`graphql-server-express`, `graphql-server-koa`, etc...), you already can use it! Make sure to specify `subscriptionsEndpoint` in GraphiQL configuration, and that's it!
For example, `graphql-server-express` users need to add the following:
```js
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: `YOUR_SUBSCRIPTION_ENDPOINT_HERE`,
}));
```
If you are using older version, or another GraphQL server, start by modifying GraphiQL static HTML, and add this package and it's fetcher from CDN:
```html
<script src="//unpkg.com/subscriptions-transport-ws@0.5.4/browser/client.js"></script>
<script src="//unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>
```
Then, create `SubscriptionClient` and define the fetcher:
```js
let subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient('SUBSCRIPTION_WS_URL_HERE', {
reconnect: true
});
let myCustomFetcher = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLFetcher);
```
> `graphQLFetcher` is the default fetcher, and we use it as fallback for non-subscription GraphQL operations.
And replace your GraphiQL creation logic to use the new fetcher:
```js
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: myCustomFetcher, // <-- here
onEditQuery: onEditQuery,
onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName,
query: ${safeSerialize(queryString)},
response: ${safeSerialize(resultString)},
variables: ${safeSerialize(variablesString)},
operationName: ${safeSerialize(operationName)},
}),
document.body
);
```
# API Docs
## SubscriptionClient
### `Constructor(url, options, connectionCallback)`
- `url: string` : url that the client will connect to, starts with `ws://` or `wss://`
- `options?: Object` : optional, object to modify default client behavior
* `timeout?: number` : how long the client should wait in ms for a keep-alive message from the server (default 30000 ms), this parameter is ignored if the server does not send keep-alive messages. This will also be used to calculate the max connection time per connect/reconnect
* `lazy?: boolean` : use to set lazy mode - connects only when first subscription created, and delay the socket initialization
* `connectionParams?: Object | Function` : object that will be available as first argument of `onConnect` (in server side), if passed a function - it will call it and send the return value
* `reconnect?: boolean` : automatic reconnect in case of connection error
* `reconnectionAttempts?: number` : how much reconnect attempts
* `connectionCallback?: (error) => {}` : optional, callback that called after the first init message, with the error (if there is one)
* `inactivityTimeout?: number` : how long the client should wait in ms, when there are no active subscriptions, before disconnecting from the server. Set to 0 to disable this behavior. (default 0)
- `webSocketImpl?: Object` - optional, WebSocket implementation. use this when your environment does not have a built-in native WebSocket (for example, with NodeJS client)
### Methods
#### `request(options) => Observable<ExecutionResult>`: returns observable to execute the operation.
- `options: {OperationOptions}`
* `query: string` : GraphQL subscription
* `variables: Object` : GraphQL subscription variables
* `operationName: string` : operation name of the subscription
* `context: Object` : use to override context for a specific call
#### `unsubscribeAll() => void` - unsubscribes from all active subscriptions.
#### `on(eventName, callback, thisContext) => Function`
- `eventName: string`: the name of the event, available events are: `connecting`, `connected`, `reconnecting`, `reconnected`, `disconnected` and `error`
- `callback: Function`: function to be called when websocket connects and initialized.
- `thisContext: any`: `this` context to use when calling the callback function.
- => Returns an `off` method to cancel the event subscription.
#### `onConnected(callback, thisContext) => Function` - shorthand for `.on('connected', ...)`
- `callback: Function`: function to be called when websocket connects and initialized, after ACK message returned from the server
- `thisContext: any`: `this` context to use when calling the callback function.
- => Returns an `off` method to cancel the event subscription.
#### `onReconnected(callback, thisContext) => Function` - shorthand for `.on('reconnected', ...)`
- `callback: Function`: function to be called when websocket reconnects and initialized, after ACK message returned from the server
- `thisContext: any`: `this` context to use when calling the callback function.
- => Returns an `off` method to cancel the event subscription.
#### `onConnecting(callback, thisContext) => Function` - shorthand for `.on('connecting', ...)`
- `callback: Function`: function to be called when websocket starts it's connection
- `thisContext: any`: `this` context to use when calling the callback function.
- => Returns an `off` method to cancel the event subscription.
#### `onReconnecting(callback, thisContext) => Function` - shorthand for `.on('reconnecting', ...)`
- `callback: Function`: function to be called when websocket starts it's reconnection
- `thisContext: any`: `this` context to use when calling the callback function.
- => Returns an `off` method to cancel the event subscription.
#### `onDisconnected(callback, thisContext) => Function` - shorthand for `.on('disconnected', ...)`
- `callback: Function`: function to be called when websocket disconnected.
- `thisContext: any`: `this` context to use when calling the callback function.
- => Returns an `off` method to cancel the event subscription.
#### `onError(callback, thisContext) => Function` - shorthand for `.on('error', ...)`
- `callback: Function`: function to be called when an error occurs.
- `thisContext: any`: `this` context to use when calling the callback function.
- => Returns an `off` method to cancel the event subscription.
### `close() => void` - closes the WebSocket connection manually, and ignores `reconnect` logic if it was set to `true`.
### `use(middlewares: MiddlewareInterface[]) => SubscriptionClient` - adds middleware to modify `OperationOptions` per each request
- `middlewares: MiddlewareInterface[]` - Array contains list of middlewares (implemented `applyMiddleware` method) implementation, the `SubscriptionClient` will use the middlewares to modify `OperationOptions` for every operation
### `status: number` : returns the current socket's `readyState`
## SubscriptionServer
### `Constructor(options, socketOptions)`
- `options: {ServerOptions}`
* `rootValue?: any` : Root value to use when executing GraphQL root operations
* `schema?: GraphQLSchema` : GraphQL schema object
* `execute?: (schema, document, rootValue, contextValue, variableValues, operationName) => Promise<ExecutionResult> | AsyncIterator<ExecutionResult>` : GraphQL `execute` function, provide the default one from `graphql` package. Return value of `AsyncItrator` is also valid since this package also support reactive `execute` methods.
* `subscribe?: (schema, document, rootValue, contextValue, variableValues, operationName) => Promise<ExecutionResult | AsyncIterator<ExecutionResult>>` : GraphQL `subscribe` function, provide the default one from `graphql` package.
* `onOperation?: (message: SubscribeMessage, params: SubscriptionOptions, webSocket: WebSocket)` : optional method to create custom params that will be used when resolving this operation
* `onOperationComplete?: (webSocket: WebSocket, opId: string)` : optional method that called when a GraphQL operation is done (for query and mutation it's immeditaly, and for subscriptions when unsubscribing)
* `onConnect?: (connectionParams: Object, webSocket: WebSocket, context: ConnectionContext)` : optional method that called when a client connects to the socket, called with the `connectionParams` from the client, if the return value is an object, its elements will be added to the context. return `false` or throw an exception to reject the connection. May return a Promise.
* `onDisconnect?: (webSocket: WebSocket, context: ConnectionContext)` : optional method that called when a client disconnects
* `keepAlive?: number` : optional interval in ms to send `KEEPALIVE` messages to all clients
- `socketOptions: {WebSocket.IServerOptions}` : options to pass to the WebSocket object (full docs [here](https://github.com/websockets/ws/blob/master/doc/ws.md))
* `server?: HttpServer` - existing HTTP server to use (use without `host`/`port`)
* `host?: string` - server host
* `port?: number` - server port
* `path?: string` - endpoint path
## How it works?
* For GraphQL WebSocket protocol docs, [click here](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md)
* This package also uses `AsyncIterator` internally using [iterall](https://github.com/leebyron/iterall), for more information [click here](https://github.com/ReactiveX/IxJS), or [the proposal](https://github.com/tc39/proposal-async-iteration)
The current version of this transport, also support a previous version of the protocol.
[You can find the old protocol docs here](https://github.com/apollographql/subscriptions-transport-ws/blob/cacb8692f3601344a4101d802443d046d73f8b23/README.md#client-server-communication)