diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 90882a8a9..3cf70ad8e 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -1203,7 +1203,6 @@ packages: graphql-ws: ^5.5.5 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - subscriptions-transport-ws: ^0.9.0 || ^0.11.0 peerDependenciesMeta: graphql-ws: optional: true @@ -1211,8 +1210,6 @@ packages: optional: true react-dom: optional: true - subscriptions-transport-ws: - optional: true dependencies: '@graphql-typed-document-node/core': 3.2.0_graphql@16.8.1 '@wry/context': 0.7.1 @@ -1238,7 +1235,6 @@ packages: graphql-ws: ^5.5.5 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - subscriptions-transport-ws: ^0.9.0 || ^0.11.0 peerDependenciesMeta: graphql-ws: optional: true @@ -1246,8 +1242,6 @@ packages: optional: true react-dom: optional: true - subscriptions-transport-ws: - optional: true dependencies: '@graphql-typed-document-node/core': 3.2.0_graphql@16.8.1 '@wry/context': 0.7.1 diff --git a/packages/framework-integration-tests/integration/provider-specific/azure/deployment/deployment.integration.ts b/packages/framework-integration-tests/integration/provider-specific/azure/deployment/deployment.integration.ts index f35cd24e9..ad87a66a2 100644 --- a/packages/framework-integration-tests/integration/provider-specific/azure/deployment/deployment.integration.ts +++ b/packages/framework-integration-tests/integration/provider-specific/azure/deployment/deployment.integration.ts @@ -5,7 +5,7 @@ import { applicationName, checkAndGetCurrentEnv, getProviderTestHelper } from '. import { internet, random, commerce, finance } from 'faker' import { waitForIt } from '../../../helper/sleep' import { ApplicationTester } from '@boostercloud/application-tester' -import { gql } from '@apollo/client' +import { gql, ApolloClient, NormalizedCacheObject } from '@apollo/client' describe('After deployment', () => { describe('the ARM template', () => { @@ -34,7 +34,7 @@ describe('After deployment', () => { async () => { try { console.log('Performing mutation') - const client = applicationUnderTest.graphql.client(authToken) + const client: ApolloClient = applicationUnderTest.graphql.client(authToken) return await client.mutate({ variables: { sku: random.uuid(), diff --git a/website/docs/06_graphql.md b/website/docs/06_graphql.md index d50d576be..df6228ac8 100644 --- a/website/docs/06_graphql.md +++ b/website/docs/06_graphql.md @@ -920,37 +920,38 @@ One of the best clients to connect to a GraphQL API is the [Apollo](https://www. We recommend referring to the documentation of those clients to know how to use them. Here is an example of how to fully instantiate the Javascript client so that it works for queries, mutations and subscriptions: ```typescript -import { split, HttpLink } from '@apollo/client' -import { getMainDefinition } from '@apollo/client/utilities' -import { WebSocketLink } from '@apollo/client/link/ws' -import { ApolloClient, InMemoryCache } from '@apollo/client' -import { SubscriptionClient } from 'subscriptions-transport-ws' +import { split, HttpLink } from '@apollo/client'; +import { getMainDefinition } from '@apollo/client/utilities'; +import { WebSocketLink } from '@apollo/client/link/ws'; +import { ApolloClient, InMemoryCache } from '@apollo/client'; // Helper function that checks if a GraphQL operation is a subscription or not function isSubscriptionOperation({ query }) { - const definition = getMainDefinition(query) - return definition.kind === 'OperationDefinition' && definition.operation === 'subscription' + const definition = getMainDefinition(query); + return definition.kind === 'OperationDefinition' && definition.operation === 'subscription'; } // Create an HTTP link for sending queries and mutations const httpLink = new HttpLink({ uri: '', -}) +}); -// Create a SusbscriptionClient and a WebSocket link for sending subscriptions -const subscriptionClient = new SubscriptionClient('', { - reconnect: true, -}) -const wsLink = new WebSocketLink(subscriptionClient) +// Create a WebSocket link for sending subscriptions +const wsLink = new WebSocketLink({ + uri: '', + options: { + reconnect: true, + }, +}); // Combine both links so that depending on the operation, it uses one or another -const splitLink = split(isSubscriptionOperation, wsLink, httpLink) +const splitLink = split(isSubscriptionOperation, wsLink, httpLink); // Finally, create the client using the link created above const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache(), -}) +}); ``` Now, we can send queries, mutations and subscriptions using the `client` instance: @@ -1034,21 +1035,20 @@ You normally won't be sending tokens in such a low-level way. GraphQL clients ha We recommend going to the specific documentation of the specific Apollo client you are using to know how to send tokens. However, the basics of this guide remains the same. Here is an example of how you would configure the Javascript/Typescript Apollo client to send the authorization token. The example is exactly the same as the one shown in the [Using Apollo clients](#using-apollo-client) section, but with the changes needed to send the token. Notice that `` and `` are obtained from the [Authentication Rocket](https://github.com/boostercloud/rocket-auth-aws-infrastructure). ```typescript -import { split, HttpLink, ApolloLink } from '@apollo/client' -import { getMainDefinition } from '@apollo/client/utilities' -import { WebSocketLink } from '@apollo/client/link/ws' -import { ApolloClient, InMemoryCache } from '@apollo/client' -import { SubscriptionClient } from 'subscriptions-transport-ws' +import { split, HttpLink, ApolloLink } from '@apollo/client'; +import { getMainDefinition } from '@apollo/client/utilities'; +import { WebSocketLink } from '@apollo/client/link/ws'; +import { ApolloClient, InMemoryCache } from '@apollo/client'; function isSubscriptionOperation({ query }) { - const definition = getMainDefinition(query) - return definition.kind === 'OperationDefinition' && definition.operation === 'subscription' + const definition = getMainDefinition(query); + return definition.kind === 'OperationDefinition' && definition.operation === 'subscription'; } // CHANGED: We now use the AuthApiEndpoint obtained by the auth rocket const httpLink = new HttpLink({ uri: '', -}) +}); // CHANGED: We create an "authLink" that modifies the operation by adding the token to the headers const authLink = new ApolloLink((operation, forward) => { @@ -1056,30 +1056,33 @@ const authLink = new ApolloLink((operation, forward) => { headers: { Authorization: 'Bearer ', }, - }) - return forward(operation) -}) + }); + return forward(operation); +}); // <-- CHANGED: Concatenate the links so that the "httpLink" receives the operation with the headers set by the "authLink" -const httpLinkWithAuth = authLink.concat(httpLink) - -const subscriptionClient = new SubscriptionClient('', { - reconnect: true, - // CHANGED: added a "connectionParam" property with a function that returns the `Authorizaiton` header containing our token - connectionParams: () => { - return { - Authorization: 'Bearer ', - } +const httpLinkWithAuth = authLink.concat(httpLink); + +// CHANGED: Create a WebSocket link for sending subscriptions +const wsLink = new WebSocketLink({ + uri: '', + options: { + reconnect: true, + // CHANGED: added a "connectionParam" property with a function that returns the `Authorization` header containing our token + connectionParams: () => { + return { + Authorization: 'Bearer ', + }; + }, }, -}) -const wsLink = new WebSocketLink(subscriptionClient) +}); -const splitLink = split(isSubscriptionOperation, wsLink, httpLinkWithAuth) // Note that we now are using "httpLinkWithAuth" +const splitLink = split(isSubscriptionOperation, wsLink, httpLinkWithAuth); // Note that we now are using "httpLinkWithAuth" const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache(), -}) +}); ``` ### Refreshing tokens with Apollo clients @@ -1091,56 +1094,58 @@ There are several ways to do this. Here we show the simplest one for learning pu First, we modify the example shown in the section [Sending tokens with apollo clients](#sending-tokens-with-apollo-clients) so that the token is stored in a global variable and the Apollo links get the token from it. That variable will be updated when the user signs-in and the token is refreshed: ```typescript -import { split, HttpLink, ApolloLink } from '@apollo/client' -import { getMainDefinition } from '@apollo/client/utilities' -import { WebSocketLink } from '@apollo/client/link/ws' -import { ApolloClient, InMemoryCache } from '@apollo/client' -import { SubscriptionClient } from 'subscriptions-transport-ws' +import { split, HttpLink, ApolloLink } from '@apollo/client'; +import { getMainDefinition } from '@apollo/client/utilities'; +import { WebSocketLink } from '@apollo/client/link/ws'; +import { ApolloClient, InMemoryCache } from '@apollo/client'; -let authToken = undefined // <-- CHANGED: This variable will hold the token and will be updated everytime the token is refreshed +let authToken = undefined; // <-- No change here, this variable will hold the token and will be updated every time the token is refreshed. function isSubscriptionOperation({ query }) { - const definition = getMainDefinition(query) - return definition.kind === 'OperationDefinition' && definition.operation === 'subscription' + const definition = getMainDefinition(query); + return definition.kind === 'OperationDefinition' && definition.operation === 'subscription'; } const httpLink = new HttpLink({ uri: '', -}) +}); const authLink = new ApolloLink((operation, forward) => { if (authToken) { operation.setContext({ headers: { - Authorization: `Bearer ${authToken}`, // <-- CHANGED: We use the "authToken" global variable + Authorization: `Bearer ${authToken}`, // <-- No change here, continue using the "authToken" global variable. }, - }) + }); } - return forward(operation) -}) - -const httpLinkWithAuth = authLink.concat(httpLink) - -const subscriptionClient = new SubscriptionClient('', { - reconnect: true, - // CHANGED: added a "connectionParam" property with a function that returns the `Authorizaiton` header containing our token - connectionParams: () => { - if (authToken) { - return { - Authorization: `Bearer ${authToken}`, // <-- CHANGED: We use the "authToken" global variable + return forward(operation); +}); + +const httpLinkWithAuth = authLink.concat(httpLink); + +// CHANGED: Create a WebSocket link for sending subscriptions, and specify the connectionParams for Authorization header. +const wsLink = new WebSocketLink({ + uri: '', + options: { + reconnect: true, + connectionParams: () => { + if (authToken) { + return { + Authorization: `Bearer ${authToken}`, // <-- CHANGED: We use the "authToken" global variable. + }; } - } - return {} + return {}; + }, }, -}) -const wsLink = new WebSocketLink(subscriptionClient) +}); -const splitLink = split(isSubscriptionOperation, wsLink, httpLinkWithAuth) +const splitLink = split(isSubscriptionOperation, wsLink, httpLinkWithAuth); const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache(), -}) +}); + ``` Now, _when the user signs-in_ or _when the token is refreshed_, we need to do two things: @@ -1156,10 +1161,4 @@ Sockets are channels for two-way communication that doesn't follow the request-r For these reasons, in order to have an effective non-trivial communication through sockets, a sub-protocol is needed. It would be in charge of making both parts understand each other, share authentication tokens, matching response to the corresponding requests, etc. -The Booster WebSocket communication uses the "GraphQL over WebSocket" protocol as subprotocol. It is in charge of all the low level stuff needed to properly send subscription operations to read models and receive the corresponding data. - -You don't need to know anything about this to develop using Booster, neither in the backend side nor in the frontend side (as all the Apollo GraphQL clients uses this protocol), but it is good to know it is there to guarantee a proper communication. In case you are really curious, you can read about the protocol [here](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md). - -:::note -The WebSocket communication in Booster only supports this subprotocol, whose identifier is `graphql-ws`. For this reason, when you connect to the WebSocket provisioned by Booster, you must specify the `graphql-ws` subprotocol. If not, the connection won't succeed. -::: +You don't need to know anything about this to develop using Booster, neither in the backend side nor in the frontend side (as all the Apollo GraphQL clients use this protocol), but it's good to know it is there to guarantee a proper communication. If you are really curious, you can read about the protocol [here](https://github.com/enisdenjo/graphql-ws). \ No newline at end of file diff --git a/website/package-lock.json b/website/package-lock.json index 660aaae5c..edcc8d0c7 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -24,7 +24,6 @@ "react-dom": "^17.0.2", "react-markdown": "^8.0.5", "react-modal": "^3.16.1", - "subscriptions-transport-ws": "^0.11.0", "tailwindcss": "^3.2.4" }, "devDependencies": { @@ -33,7 +32,7 @@ "typescript": "^4.7.4" }, "engines": { - "node": ">=16.14" + "node": ">=18.0.0 <19.0.0" } }, "node_modules/@algolia/autocomplete-core": { @@ -216,8 +215,7 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", "graphql-ws": "^5.5.5", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "graphql-ws": { @@ -228,9 +226,6 @@ }, "react-dom": { "optional": true - }, - "subscriptions-transport-ws": { - "optional": true } } }, @@ -4206,11 +4201,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" - }, "node_modules/bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -7698,11 +7688,6 @@ "node": ">=0.10.0" } }, - "node_modules/iterall": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" - }, "node_modules/jest-util": { "version": "29.2.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.0.tgz", @@ -12245,35 +12230,6 @@ "postcss": "^8.2.15" } }, - "node_modules/subscriptions-transport-ws": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", - "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", - "deprecated": "The `subscriptions-transport-ws` package is no longer maintained. We recommend you use `graphql-ws` instead. For help migrating Apollo software to `graphql-ws`, see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws For general help using `graphql-ws`, see https://github.com/enisdenjo/graphql-ws/blob/master/README.md", - "dependencies": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependencies": { - "graphql": "^15.7.2 || ^16.0.0" - } - }, - "node_modules/subscriptions-transport-ws/node_modules/eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "node_modules/subscriptions-transport-ws/node_modules/symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -17057,11 +17013,6 @@ "@babel/helper-define-polyfill-provider": "^0.3.3" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" - }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -19551,11 +19502,6 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, - "iterall": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" - }, "jest-util": { "version": "29.2.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.0.tgz", @@ -22688,30 +22634,6 @@ "postcss-selector-parser": "^6.0.4" } }, - "subscriptions-transport-ws": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", - "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", - "requires": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - }, - "dependencies": { - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" - } - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/website/package.json b/website/package.json index 47d20bd3d..4017175c2 100644 --- a/website/package.json +++ b/website/package.json @@ -31,7 +31,6 @@ "react-dom": "^17.0.2", "react-markdown": "^8.0.5", "react-modal": "^3.16.1", - "subscriptions-transport-ws": "^0.11.0", "tailwindcss": "^3.2.4" }, "devDependencies": { diff --git a/website/src/services/apollo-service.ts b/website/src/services/apollo-service.ts index 4c8e200a9..0bcfd0d84 100644 --- a/website/src/services/apollo-service.ts +++ b/website/src/services/apollo-service.ts @@ -1,32 +1,32 @@ -import { ApolloClient, ApolloLink, HttpLink, InMemoryCache, NormalizedCacheObject, split } from '@apollo/client' -import { WebSocketLink } from '@apollo/client/link/ws' -import { getMainDefinition } from '@apollo/client/utilities' -import { SubscriptionClient } from 'subscriptions-transport-ws' +import { ApolloClient, ApolloLink, HttpLink, InMemoryCache, NormalizedCacheObject, split } from '@apollo/client'; +import { WebSocketLink } from '@apollo/client/link/ws'; +import { getMainDefinition } from '@apollo/client/utilities'; export class ApolloService { static initClient(httpUri: string, wsUri: string): ApolloClient { const httpLink = new HttpLink({ uri: httpUri, - }) + }); - const wsLink = new WebSocketLink( - new SubscriptionClient(wsUri, { + const wsLink = new WebSocketLink({ + uri: wsUri, + options: { reconnect: true, - }) - ) + }, + }); const splitLink = split( ({ query }) => { - const definition = getMainDefinition(query) - return definition.kind === 'OperationDefinition' && definition.operation === 'subscription' + const definition = getMainDefinition(query); + return definition.kind === 'OperationDefinition' && definition.operation === 'subscription'; }, wsLink, httpLink - ) + ); return new ApolloClient({ cache: new InMemoryCache(), link: ApolloLink.from([splitLink]), - }) + }); } }