Skip to content

Commit ce90c47

Browse files
authored
Backport PR #9369 to main branch for GraphQLWsLink (#9453)
New `GraphQLWsLink` and `@apollo/client/link/subscriptions` sub-package entry point for `graphql-ws` subscriptions library.
1 parent a8da797 commit ce90c47

File tree

14 files changed

+366
-46
lines changed

14 files changed

+366
-46
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
2+
## Apollo Client 3.5.10 (unreleased)
3+
4+
### Improvements
5+
6+
- Add `GraphQLWsLink` in `@apollo/client/link/subscriptions`. This link is similar to the existing `WebSocketLink` in `@apollo/client/link/ws`, but uses the newer [`graphql-ws`](https://www.npmjs.com/package/graphql-ws) package and protocol instead of the older `subscriptions-transport-ws` implementation. <br/>
7+
[@glasser](https://github.com/glasser) in [#9369](https://github.com/apollographql/apollo-client/pull/9369)
8+
9+
> Note from [@benjamn](https://github.com/benjamn): since `GraphQLWsLink` is new functionality, we would normally wait for the next minor version (v3.6), but we were asked to expedite this release. These changes are strictly additive/opt-in/backwards-compatible, so shipping them in a patch release (3.5.10) seems safe, if unusual.
10+
111
## Apollo Client 3.5.9 (2022-02-15)
212

313
### Improvements

config/entryPoints.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const entryPoints = [
1212
{ dirs: ['link', 'persisted-queries'] },
1313
{ dirs: ['link', 'retry'] },
1414
{ dirs: ['link', 'schema'] },
15+
{ dirs: ['link', 'subscriptions'] },
1516
{ dirs: ['link', 'utils'] },
1617
{ dirs: ['link', 'ws'] },
1718
{ dirs: ['react'] },

docs/gatsby-config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ module.exports = {
109109
'api/link/apollo-link-rest',
110110
'api/link/apollo-link-retry',
111111
'api/link/apollo-link-schema',
112+
'api/link/apollo-link-subscriptions',
112113
'api/link/apollo-link-ws',
113114
'api/link/community-links'
114115
],
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
title: Subscriptions Link
3+
sidebar_title: Subscriptions (newer protocol)
4+
description: Execute subscriptions (or other GraphQL operations) over WebSocket with the `graphql-ws` library
5+
api_reference: true
6+
---
7+
8+
> We recommend reading [Apollo Link overview](./introduction/) before learning about individual links.
9+
10+
The `GraphQLWsLink` is a [terminating link](./introduction/#the-terminating-link) that's used most commonly with GraphQL [subscriptions](../../data/subscriptions/) (which usually communicate over WebSocket), although you can send queries and mutations over WebSocket as well.
11+
12+
`GraphQLWsLink` requires the [`graphql-ws`](https://www.npmjs.com/package/graphql-ws) library. Install it in your project like so:
13+
14+
```shell
15+
npm install graphql-ws
16+
```
17+
18+
> **Note**: This link works with the newer `graphql-ws` library. If your server uses the older `subscriptions-transport-ws`, you should use the [`WebSocketLink` link from `@apollo/client/link/ws](./apollo-link-ws) instead.
19+
20+
## Constructor
21+
22+
```js
23+
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
24+
import { createClient } from "graphql-ws";
25+
26+
const link = new GraphQLWsLink(createClient({
27+
url: "ws://localhost:3000/subscriptions",
28+
}));
29+
```
30+
31+
### Options
32+
33+
The `GraphQLWsLink` constructor takes a single argument, which is a `Client` returned from the `graphql-ws` `createClient` function.
34+
35+
The `createClient` function can take many options; full details can be found in [the `graphql-ws` docs for `ClientOptions`](https://github.com/enisdenjo/graphql-ws/blob/master/docs/interfaces/client.ClientOptions.md). The one required option is `url`, which is the URL (typically starting with `ws://` or `wss://`, which are the equivalents of `http://` and `https://` respectively) to your WebSocket server. (Note that this differs from the [older link's URL option](./apollo-link-ws) which is called `uri` rather than `url`.)
36+
37+
## Usage
38+
39+
See [Subscriptions](../../data/subscriptions/).

docs/source/api/link/apollo-link-ws.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: WebSocket Link
3-
sidebar_title: WebSocket
4-
description: Execute subscriptions (or other GraphQL operations) over WebSocket
3+
sidebar_title: WebSocket (older protocol)
4+
description: Execute subscriptions (or other GraphQL operations) over WebSocket with the `subscriptions-transport-ws` library
55
api_reference: true
66
---
77

@@ -15,22 +15,27 @@ The `WebSocketLink` is a [terminating link](./introduction/#the-terminating-link
1515
npm install subscriptions-transport-ws
1616
```
1717

18+
> **Note**: The `subscriptions-transport-ws` library is not actively maintained. We recommend the use of the `graphql-ws` library instead. These libraries layer different protocols on top of WebSockets, so you do need to ensure you are using the same library in your server and any clients that you support. To use `graphql-ws` from Apollo Client, use the [`GraphQLWsLink` link from `@apollo/client/link/subscriptions](./apollo-link-subscriptions) instead.
19+
1820
## Constructor
1921

2022
```js
2123
import { WebSocketLink } from "@apollo/client/link/ws";
22-
23-
const link = new WebSocketLink({
24-
uri: "ws://localhost:3000/subscriptions",
25-
options: {
26-
reconnect: true
27-
}
24+
import { SubscriptionClient } from "subscriptions-transport-ws";
25+
26+
const link = new WebSocketLink(
27+
new SubscriptionClient({
28+
uri: "ws://localhost:3000/subscriptions",
29+
options: {
30+
reconnect: true,
31+
},
32+
}),
2833
});
2934
```
3035

3136
### Options
3237

33-
The `WebSocketLink` constructor takes an options object with the following fields:
38+
The `WebSocketLink` constructor takes either a `SubscriptionClient` object or an options object with the following fields. (These options are passed directly to the `SubscriptionClient` constructor.)
3439

3540
<table class="field-table">
3641
<thead>

docs/source/api/react/hoc.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ data.fetchMore({
471471

472472
### `data.subscribeToMore(options)`
473473

474-
This function will set up a subscription, triggering updates whenever the server sends a subscription publication. This requires subscriptions to be set up on the server to properly work. Check out the [subscriptions guide](../../data/subscriptions/) and the [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws) and [graphql-subscriptions](https://github.com/apollographql/graphql-subscriptions) for more information on getting this set up.
474+
This function will set up a subscription, triggering updates whenever the server sends a subscription publication. This requires subscriptions to be set up on the server to properly work. Check out the [subscriptions guide](../../data/subscriptions/) for more information on getting this set up.
475475

476476
This function returns an `unsubscribe` function handler which can be used to unsubscribe later.
477477

docs/source/data/subscriptions.mdx

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ You _should_ use subscriptions for the following:
2222

2323
* **Low-latency, real-time updates**. For example, a chat application's client wants to receive new messages as soon as they're available.
2424

25+
## Choice of subscription protocol
26+
27+
The GraphQL spec does not define a specific way to send subscription requests. The first popular JavaScript library to implement subscriptions over WebSocket is called `subscriptions-transport-ws`. This library is no longer actively maintained; its successor is a library called `graphql-ws`. The two packages _do not use the same protocol_, so you need to make sure that your server and clients all use the same library.
28+
29+
Apollo Client supports both `graphql-ws` and `subscriptions-transport-ws`. We recommend you use the newer library `graphql-ws` and this page shows how to use it. If you need to use `subscriptions-transport-ws` because your server still uses that protocol, the differences are described [at the bottom of this page](#the-older-subscriptions-transport-ws-library).
30+
31+
> **Note**: When looking at the source code of an implementation to determine which protocol it supports, you will find that the libraries uses different strings as the "WebSocket subprotocol". Confusingly, `subscriptions-transport-ws` uses the `graphql-ws` subprotocol and `graphql-ws` uses the `graphql-transport-ws` subprotocol! In these docs, when we say "`graphql-ws`" we are referring to the _library_ `graphql-ws`, not the subprotocol `graphql-ws`, which is the other project.
32+
2533
## Defining a subscription
2634

2735
You define a subscription on both the server side and the client side, just like you do for queries and mutations.
@@ -70,58 +78,54 @@ Whenever your GraphQL server _does_ push data to a subscribing client, that data
7078

7179
## Setting up the transport
7280

73-
Because subscriptions usually maintain a persistent connection, they shouldn't use the default HTTP transport that Apollo Client uses for queries and mutations. Instead, Apollo Client subscriptions most commonly communicate over WebSocket, via the community-maintained [`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws) library.
81+
Because subscriptions usually maintain a persistent connection, they shouldn't use the default HTTP transport that Apollo Client uses for queries and mutations. Instead, Apollo Client subscriptions most commonly communicate over WebSocket, via the [`graphql-ws`](https://www.npmjs.com/package/graphql-ws) library. (As mentioned [above](#choice-of-subscription-protocol), some servers use an older library called `subscriptions-transport-ws`; see [below](#the-older-subscriptions-transport-ws-library) for the changes necessary to use that library with Apollo Client.)
7482

7583
### 1. Install required libraries
7684

7785
[Apollo Link](../api/link/introduction/) is a library that helps you customize Apollo Client's network communication. You can use it to define a **link chain** that modifies your operations and routes them to the appropriate destination.
7886

79-
To execute subscriptions over WebSocket, you can add a `WebSocketLink` to your link chain. This link requires the `subscriptions-transport-ws` library. Install it like so:
87+
To execute subscriptions over WebSocket, you can add a `GraphQLWsLink` to your link chain. This link requires the `graphql-ws` library. Install it like so:
8088

8189
```bash
82-
npm install subscriptions-transport-ws
90+
npm install graphql-ws
8391
```
8492

85-
### 2. Initialize a `WebSocketLink`
93+
### 2. Initialize a `GraphQLWsLink`
8694

87-
Import and initialize a `WebSocketLink` object in the same project file where you initialize `ApolloClient`:
95+
Import and initialize a `GraphQLWsLink` object in the same project file where you initialize `ApolloClient`:
8896

8997
```js:title=index.js
90-
import { WebSocketLink } from '@apollo/client/link/ws';
98+
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
99+
import { createClient } from 'graphql-ws';
91100

92-
const wsLink = new WebSocketLink({
93-
uri: 'ws://localhost:4000/subscriptions',
94-
options: {
95-
reconnect: true
96-
}
97-
});
101+
const wsLink = new GraphQLWsLink(createClient({
102+
url: 'ws://localhost:4000/subscriptions',
103+
}));
98104
```
99105

100-
Replace the value of the `uri` option with your GraphQL server's subscription-specific WebSocket endpoint. If you're using Apollo Server, see [Setting a subscription endpoint](https://www.apollographql.com/docs/apollo-server/data/subscriptions/#setting-a-subscription-endpoint).
106+
Replace the value of the `url` option with your GraphQL server's subscription-specific WebSocket endpoint. If you're using Apollo Server, see [Setting a subscription endpoint](https://www.apollographql.com/docs/apollo-server/data/subscriptions/#setting-a-subscription-endpoint).
101107

102108
### 3. Split communication by operation (recommended)
103109

104-
Although Apollo Client _can_ use your `WebSocketLink` to execute all operation types, in most cases it should continue using HTTP for queries and mutations. This is because queries and mutations don't require a stateful or long-lasting connection, making HTTP more efficient and scalable if a WebSocket connection isn't already present.
110+
Although Apollo Client _can_ use your `GraphQLWsLink` to execute all operation types, in most cases it should continue using HTTP for queries and mutations. This is because queries and mutations don't require a stateful or long-lasting connection, making HTTP more efficient and scalable if a WebSocket connection isn't already present.
105111

106112
To support this, the `@apollo/client` library provides a `split` function that lets you use one of two different `Link`s, according to the result of a boolean check.
107113

108-
The following example expands on the previous one by initializing both a `WebSocketLink` _and_ an `HttpLink`. It then uses the `split` function to combine those two `Link`s into a _single_ `Link` that uses one or the other according to the type of operation being executed.
114+
The following example expands on the previous one by initializing both a `GraphQLWsLink` _and_ an `HttpLink`. It then uses the `split` function to combine those two `Link`s into a _single_ `Link` that uses one or the other according to the type of operation being executed.
109115

110116
```js:title=index.js
111117
import { split, HttpLink } from '@apollo/client';
112118
import { getMainDefinition } from '@apollo/client/utilities';
113-
import { WebSocketLink } from '@apollo/client/link/ws';
119+
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
120+
import { createClient } from 'graphql-ws';
114121

115122
const httpLink = new HttpLink({
116123
uri: 'http://localhost:4000/graphql'
117124
});
118125

119-
const wsLink = new WebSocketLink({
120-
uri: 'ws://localhost:4000/subscriptions',
121-
options: {
122-
reconnect: true
123-
}
124-
});
126+
const wsLink = new GraphQLWsLink(createClient({
127+
url: 'ws://localhost:4000/subscriptions',
128+
}));
125129

126130
// The split function takes three parameters:
127131
//
@@ -162,24 +166,21 @@ const client = new ApolloClient({
162166
163167
### 5. Authenticate over WebSocket (optional)
164168

165-
It is often necessary to authenticate a client before allowing it to receive subscription results. To do this, you can provide a `connectionParams` option to the `WebSocketLink` constructor, like so:
169+
It is often necessary to authenticate a client before allowing it to receive subscription results. To do this, you can provide a `connectionParams` option to the `GraphQLWsLink` constructor, like so:
166170

167-
```js{7-9}
168-
import { WebSocketLink } from '@apollo/client/link/ws';
171+
```js{6-8}
172+
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
173+
import { createClient } from 'graphql-ws';
169174
170-
const wsLink = new WebSocketLink({
171-
uri: 'ws://localhost:4000/subscriptions',
172-
options: {
173-
reconnect: true,
174-
connectionParams: {
175-
authToken: user.authToken,
176-
},
175+
const wsLink = new GraphQLWsLink(createClient({
176+
url: 'ws://localhost:4000/subscriptions',
177+
connectionParams: {
178+
authToken: user.authToken,
177179
},
178-
});
180+
}));
179181
```
180182

181-
Your `WebSocketLink` passes the `connectionParams` object to your server whenever it connects. If your server has a [SubscriptionsServer](https://www.apollographql.com/docs/graphql-subscriptions/authentication) object that's listening for WebSocket connections, it receives the `connectionParams` object and can use it to perform authentication, along with any other connection-related tasks.
182-
183+
Your `GraphQLWsLink` passes the `connectionParams` object to your server whenever it connects. Your server receives the `connectionParams` object and can use it to perform authentication, along with any other connection-related tasks.
183184

184185
## Executing a subscription
185186

@@ -312,3 +313,37 @@ The `useSubscription` Hook accepts the following options:
312313
After being called, the `useSubscription` Hook returns a result object with the following properties:
313314

314315
<SubscriptionResult />
316+
317+
## The older `subscriptions-transport-ws` library
318+
319+
If your server uses `subscriptions-transport-ws` instead of the newer `graphql-ws` library, you need to make a few changes to how you set up your link.
320+
321+
Instead of `npm install graphql-ws`, you `npm install subscriptions-transport-ws`.
322+
323+
Instead of `import { createClient } from 'graphql-ws'`, you `import { SubscriptionClient } from 'subscriptions-transport-ws'`.
324+
325+
Instead of `import { GraphQLWsLink } from '@apollo/client/link/subscriptions'`, you `import { WebSocketLink } from '@apollo/client/link/ws`.
326+
327+
The options passed to `new SubscriptionClient` are slightly different from those passed to `createClient`. The subscriptions URL is specified in an `uri` option instead of an `url` option. The `connectionParams` option is nested under an options object called `options` instead of being at the top level. (You may also pass the `new SubscriptionClient` constructor arguments directly to `new WebSocketLink`.) See [the `subscriptions-transport-ws` README](https://www.npmjs.com/package/subscriptions-transport-ws) for complete `SubscriptionClient` API docs.
328+
329+
Once you've created your `wsLink`, everything else in this document still applies: `useSubscription`, `subscribeToMore`, and split links work exactly the same way for both implementations.
330+
331+
The following is what typical `WebSocketLink` initialization looks like:
332+
333+
```js
334+
import { WebSocketLink } from "@apollo/client/link/ws";
335+
import { SubscriptionClient } from "subscriptions-transport-ws";
336+
337+
const wsLink = new WebSocketLink(
338+
new SubscriptionClient({
339+
uri: "ws://localhost:4000/subscriptions",
340+
options: {
341+
connectionParams: {
342+
authToken: user.authToken,
343+
},
344+
},
345+
}),
346+
});
347+
```
348+
349+
More details on `WebSocketLink`'s API can be found in [its API docs](../api/link/apollo-link-ws).

package-lock.json

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,14 @@
6464
},
6565
"peerDependencies": {
6666
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
67+
"graphql-ws": "^5.5.5",
6768
"react": "^16.8.0 || ^17.0.0",
6869
"subscriptions-transport-ws": "^0.9.0 || ^0.11.0"
6970
},
7071
"peerDependenciesMeta": {
72+
"graphql-ws": {
73+
"optional": true
74+
},
7175
"react": {
7276
"optional": true
7377
},
@@ -110,6 +114,7 @@
110114
"fetch-mock": "9.11.0",
111115
"glob": "7.2.0",
112116
"graphql": "16.0.1",
117+
"graphql-ws": "5.6.2",
113118
"jest": "27.5.1",
114119
"jest-fetch-mock": "3.0.3",
115120
"jest-junit": "13.0.0",

src/__tests__/__snapshots__/exports.ts.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ Array [
210210
]
211211
`;
212212

213+
exports[`exports of public entry points @apollo/client/link/subscriptions 1`] = `
214+
Array [
215+
"GraphQLWsLink",
216+
]
217+
`;
218+
213219
exports[`exports of public entry points @apollo/client/link/utils 1`] = `
214220
Array [
215221
"createOperation",

0 commit comments

Comments
 (0)