Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@httptoolkit/subscriptions-transport-ws

apollostack321.7kMIT0.11.2TypeScript support: included

A websocket transport for GraphQL subscriptions

readme

@httptoolkit/subscriptions-transport-ws

This is a fork of subscriptions-transport-ws, updating ws to fix security issues. This is a quick fix, and won't be maintained for general use outside Mockttp in future

npm version GitHub license

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

Getting Started

Start by installing the package, using Yarn or NPM.

Using Yarn:
$ yarn add @httptoolkit/subscriptions-transport-ws

Or, using NPM:
$ npm install --save @httptoolkit/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.

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

import { createServer } from 'http';
import { SubscriptionServer } from '@httptoolkit/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 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:

import { SubscriptionClient } from '@httptoolkit/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 subscriptions over WebSocket, create your SubscriptionClient and a regular HTTP network interface, then extend your network interface to use the WebSocket client for GraphQL subscriptions:

import {SubscriptionClient, addGraphQLSubscriptions} from '@httptoolkit/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 or query subscribeToMore:

apolloClient.subscribe({
  query: gql`
    subscription onNewItem {
        newItemCreated {
            id
        }
    }`,
  variables: {}
}).subscribe({
  next (data) {
    // Notify your application with the new arrived data
  }
});
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/@httptoolkit/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:

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:

    <script src="//unpkg.com/@httptoolkit/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:

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:

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, webSocketImpl)

  • 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
    • minTimeout?: number: the minimum amount of time the client should wait for a connection to be made (default 1000 ms)
    • lazy?: boolean : use to set lazy mode - connects only when first subscription created, and delay the socket initialization
    • connectionParams?: Object | Function | Promise<Object> : 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, if function returns as promise - it will wait until it resolves and send the resolved 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, constructor for W3C compliant 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(payload): function to be called when websocket connects and initialized, after ACK message returned from the server. Includes payload from server, if any.
  • 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(payload): function to be called when websocket reconnects and initialized, after ACK message returned from the server. Includes payload from server, if any.
  • 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 | socketServer)

  • options: {ServerOptions}

    • rootValue?: any : Root value to use when executing GraphQL root operations
    • schema?: GraphQLSchema : GraphQL schema object. If not provided, you have to return the schema as a property on the object returned from onOperation.
    • 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: ExecutionParams, webSocket: WebSocket) : optional method to create custom params that will be used when resolving this operation. It can also be used to dynamically resolve the schema that will be used for the particular operation.
    • onOperationComplete?: (webSocket: WebSocket, opId: string) : optional method that called when a GraphQL operation is done (for query and mutation it's immediately, 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)

    • server?: HttpServer - existing HTTP server to use (use without host/port)
    • host?: string - server host
    • port?: number - server port
    • path?: string - endpoint path
  • socketServer: {WebSocket.Server} : a configured server if you need more control. Can be used for integration testing with in-memory WebSocket implementation.

How it works?

The current version of this transport, also support a previous version of the protocol.

You can find the old protocol docs here

changelog

Changelog

v0.11.0 (2021-11-02)

  • Support for graphql@16 and bump minimal supported version to be `graphql@15.7.2. As part of this change signatures forExecuteFunctionandSubscribeFunction` were changed.
    @IvanGoncharov in #902

v0.10.0 (2021-06-08)

  • Same contents as v0.9.19 (published before v0.9.19 before realizing it would be helpful if the new version was picked up by packages looking for ^0.9).

v0.9.19 (2021-06-08)

  • Bump ws dependency to allow v6 and v7. Note that there are breaking changes in ws 6.0.0 and 7.0.0; for example, messages over 100MiB are rejected, and (in v7) the behavior of sending messages while the connection is starting or ending has changed. We are publishing this package to allow users of Apollo Server 2 to avoid seeing this CVE in their npm audit. However, note that (a) this CVE does not affect the subscriptions client, just the server and (b) Apollo Server 3 will remove its superficial integration with this package entirely. We encourage users of Apollo Server 2 to disable the integration with this unmaintained package via new ApolloServer({subscriptions: false}), and consider packages such as graphql-ws to power GraphQL subscriptions until such time as Apollo Server has more fully integrated subscriptions support.

v0.9.18 (2020-08-17)

Bug Fixes

  • Do not send GQL_STOP when unsubscribing after GQL_COMPLETE is received.
    @onhate in #775
  • Clear WebSocket event listeners on close.
    @tretne in #615
  • Fix MessageTypes TS import errors.
    @sneko in #412
  • Ensure promisedParams errors are not handled twice.
    @benjie in #514
  • Fix invalid formatResponse console error.
    @renatorib in #761
  • Destructure the correct error object in MessageTypes.GQL_START.
    @gregbty in #588
  • Inline source in sourcemap files to fix broken source lookups.
    @alexkirsz in #513

New Features

v0.9.17

v0.9.16

  • Add ability to set custom WebSocket protocols for client.
    @pkosiec in #477

v0.9.15

v0.9.14

  • Allow dynamically specifying/overriding the schema in the object returned from onOperation PR #447

v0.9.13

  • Allow connectionParams to be a Promise PR #443

v0.9.12

  • use lightweight lodash alternatives Issue #430
  • applies fix suggested by @pandemosth regarding "No subscription is made on reconnect" and "Duplicate subscription made on reconnect" described in Issue #295

v0.9.11

  • allow using custom WebSocket server implementation PR #374

v0.9.10

  • upgrade ws and eventemitter3

v0.9.9

v0.9.8

  • added error event to handle connection errors and debug network troubles PR #341.
  • added feature inactivityTimeout PR #390

v0.9.7

  • change default timeout from 10s to 30s PR #368
  • pass request (upgradeReq) to ConnectionContext PR #369
  • pass ConnectionContext to onDisconnect() as second argument PR #369

v0.9.6

  • fix shallow cloning on contexts which are classes
  • upgrade to support graphql 0.13.X
  • bump iterall version PR #362

v0.9.5

  • docs(setup): Fix dead link to subscriptions-to-schema
  • upgrade to support graphql 0.12.X

v0.9.4

  • fix unhandledRejection error in GQL_START handling if initPromise rejected PR #310

v0.9.3

  • fix unhandledRejection error in GQL_STOP handling if initPromise rejected PR #309
  • fix return of init error message to legacy clients PR #309

v0.9.2

  • fix format of keep alive message sent to legacy clients. PR #297
  • fix(isPromise): Made checks for promises in server.ts loose to allow for augmented and polyfilled promises. PR #304

v0.9.1

  • docs(KEEP_ALIVE): Updated protocol docs to explain the correct server implementation of GQL_CONNECTION_INIT, GQL_CONNECTION_ACK and GQL_CONNECTION_KEEP_ALIVE PR #279
  • docs(language-typos): Update documentation to remove some language typos PR #282
  • fix(typescript-2.5.x-typings): Fix a couple of typing changes required by latest typing files with TypeScript 2.5.X. PR #285
  • test(NA): fixed run condition on tests for gql_data with errors PR #289

v0.9.0

  • docs(README): Fix example for subscribe and subscribeToMore PR #273
  • Add support for GraphQL 0.11.0 PR #261
  • BREAKING CHANGE: Remove support for Subscription Manager PR #261
  • BREAKING CHANGE: Remove support for all deprecated API PR #272

v0.8.3

  • docs(README): Fix options example for subscribe methods PR #266
  • Gracefully unsubscribe to all pending operations before a requested close by the user PR #245
  • Add close method to server PR #257
  • Bugfix: Observer callbacks should be optional PR #256

v0.8.2

  • Add request interface as a preparation for Apollo 2.0 PR #242
  • Add Validation step to server PR #241
  • Call operation handler before delete the operation on operation complete PR #239

v0.8.1

  • Send first keep alive message right after the ack PR #223
  • Return after first post-install when it should install dev dependencies PR #218
  • On installing from branch install dev dependencies only if dist folder isn't found PR #219

v0.8.0

  • Expose opId onOperationComplete method PR #211
  • Fix to make library able to be installed from a branch PR #208
  • Fix for non forced closes (now it wont send connection_terminate) PR #197
  • A lot of connection's flow improvements (on connect, on disconnect and on reconnect) PR #197
  • Require specific lodash/assign module instead of entire package, so memory impact is reduced PR #196
  • docs(README): Fix onEvent(eventName, callback, thisContext) list of eventName PR #205

v0.7.3

  • Fix for first subscription is never unsubscribed PR #179

v0.7.2

  • Increase default keep-alive timeout to 30s PR #177
  • Operation key is now string instead of number PR #176

v0.7.1

  • Fix for reconnect after manual close PR #164
  • test(disconnect): added tests for client-server flow for unsubscribe and disconnect PR #163
  • Various dependencies updates PR #152 PR #162
  • docs(README): fix docs PR #151

v0.7.0

  • Client exposes new asyncronous middleware to modify OperationOptions PR #78
  • Added WebSocketServer error handler to prevent uncaught exceptions. Fixes Issue #94
  • Updated ws dependency to the lastest.
  • Introduce lazy mode for connection, and accept function as connectionParams PR #131
  • Extend transport protocol to support GraphQL queries and mutations over WebSocket PR #108
  • Added built-in support for subscribe from graphql-js PR #133
  • Fixed infinity reconnects when server accepts connections but its in an error state. PR #135
  • Force close client-side socket when using close(), and ignore reconnect logic. PR #137
  • Added new connection events to give a more accurate control over the connection state PR #139. Fixes Issue #136.
  • Replaced Object.assign by lodash.assign to extend browser support PR #144. Fixes Issue #141

v0.6.0

  • Enabled Greenkeeper and updated dependencies, includes major version bump of ws PR #90

v0.6.0

  • Protocol update to support queries, mutations and also subscriptions. PR #108
  • Added support in the server for GraphQL Executor. PR #108
  • Added support in the server executor for graphql-js subscribe. PR #846

v0.5.5

v0.5.4

  • Ensure INIT is sent before SUBSCRIPTION_START even when client reconnects PR #85
  • Allow data and errors in payload of SUBSCRIPTION_DATA PR #84
  • Expose index.js as entrypoint for server/NodeJS application to allow NodeJS clients to use SubscriptionClient PR #91
  • Fixed a bug with missing error message on INIT_FAIL message #88

v0.5.3

  • Fixed a bug with browser declaration on package.json (Issue #79)

v0.5.2

  • Updated dependencies versions
  • Fixed typings issue with missing index.d.ts file. PR #73
  • Transpiling client.js to target browsers using webpack. PR #77

v0.5.1

  • Only attempt reconnect on closed connection. Fixes Issue #70

v0.5.0

  • Updated `graphql-subscriptions@0.3.0`.
  • Added addGraphQLSubscriptions - use it to extend your network interface to work with SubscriptionsClient instance. PR #64
  • Client now uses native WebSocket by default, and has optional field to provide another implementation (for NodeJS clients)PR #53
  • Client now support INIT with custom object, so you can use if for authorization, or any other init params. PR #53
  • Server and client are now separated with browser and main fields of package.json. PR #53
  • Client exposes workflow events for connect, disconnect and reconnect. PR #53
  • Server exposes new events: onUnsubscribe, onSubscribe, onConnect and onDisconnect. PR #53
  • Use ws package on server side, and expose it's options from server constructor. PR #53

v0.4.0

  • Don't throw in the server on certain unsub messages. PR #54
  • Moved typings to @types/graphql. PR #60

v0.3.1

  • Server now passes back subscriptionManager errors encountered during publish. PR #42

v0.3.0

  • (SEMVER-MINOR) Bump graphql-subscriptions dependency to ^0.2.0 which changes the setupFunctions format
  • Fix missing unsubscription from first (id = 0) subscription

v0.2.6

  • Add reconnect and reconnectionAttempts options to the constructor which will enable reconnection with exponential backoff.

v0.2.5

  • Pass WebSocketRequest to onSubscribe to support reading HTTP headers when creating a subscription

v0.2.4

  • Server reports back an error on an unparsable client message
  • Server reports back an error on an unsupported client message type
  • Fix intermittent failure in timeout test case
  • Standardize server and client errors handling to always create an array of errors with a message property