[![npm version](https://badge.fury.io/js/subscriptions-transport-ws.svg)](https://badge.fury.io/js/subscriptions-transport-ws) [![GitHub license](https://img.shields.io/github/license/apollostack/subscriptions-transport-ws.svg)](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)