diff --git a/.mocharc.yml b/.mocharc.yml index eefc53a3..4ab239f4 100644 --- a/.mocharc.yml +++ b/.mocharc.yml @@ -1,4 +1,3 @@ -throw-deprecation: true check-leaks: true require: - './resources/register.js' diff --git a/README.md b/README.md index ecb94123..10526c27 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ The `graphqlHTTP` function accepts the following options: - **`subscriptionEndpoint`**: An optional GraphQL string contains the WebSocket server url for subscription. + - **`websocketClient`**: An optional GraphQL string for websocket client used for subscription, `v0`: subscriptions-transport-ws, `v1`: graphql-ws. Defaults to `v0` if not provided + - **`rootValue`**: A value to pass as the `rootValue` to the `execute()` function from [`GraphQL.js/src/execute.js`](https://github.com/graphql/graphql-js/blob/main/src/execution/execute.js#L129). diff --git a/examples/index_subscription.ts b/examples/index_subscription.ts new file mode 100644 index 00000000..553a464e --- /dev/null +++ b/examples/index_subscription.ts @@ -0,0 +1,48 @@ +import { createServer } from 'http'; + +import express from 'express'; +import { execute, subscribe } from 'graphql'; +import ws from 'ws'; +import { useServer } from 'graphql-ws/lib/use/ws'; + +import { graphqlHTTP } from '../src'; + +import { schema, roots, rootValue } from './schema'; + +const PORT = 4000; +const subscriptionEndpoint = `ws://localhost:${PORT}/subscriptions`; + +const app = express(); +app.use( + '/graphql', + graphqlHTTP({ + schema, + rootValue, + graphiql: { + subscriptionEndpoint, + websocketClient: 'v1', + }, + }), +); + +const server = createServer(app); + +const wsServer = new ws.Server({ + server, + path: '/subscriptions', +}); + +server.listen(PORT, () => { + useServer( + { + schema, + roots, + execute, + subscribe, + }, + wsServer, + ); + console.info( + `Running a GraphQL API server with subscriptions at http://localhost:${PORT}/graphql`, + ); +}); diff --git a/examples/index_subscription_legacy.ts b/examples/index_subscription_legacy.ts new file mode 100644 index 00000000..2cd6d8ba --- /dev/null +++ b/examples/index_subscription_legacy.ts @@ -0,0 +1,53 @@ +import { createServer } from 'http'; + +import express from 'express'; +import { execute, subscribe } from 'graphql'; +import { SubscriptionServer } from 'subscriptions-transport-ws'; + +import { graphqlHTTP } from '../src'; + +import { schema, rootValue } from './schema'; + +const PORT = 4000; +const subscriptionEndpoint = `ws://localhost:${PORT}/subscriptions`; + +const app = express(); +app.use( + '/graphql', + graphqlHTTP({ + schema, + rootValue, + graphiql: { subscriptionEndpoint }, + }), +); + +const ws = createServer(app); + +ws.listen(PORT, () => { + console.log( + `Running a GraphQL API server with subscriptions at http://localhost:${PORT}/graphql`, + ); +}); + +const onConnect = (_: any, __: any) => { + console.log('connecting ....'); +}; + +const onDisconnect = (_: any) => { + console.log('disconnecting ...'); +}; + +SubscriptionServer.create( + { + schema, + rootValue, + execute, + subscribe, + onConnect, + onDisconnect, + }, + { + server: ws, + path: '/subscriptions', + }, +); diff --git a/examples/schema.ts b/examples/schema.ts new file mode 100644 index 00000000..98e55cad --- /dev/null +++ b/examples/schema.ts @@ -0,0 +1,37 @@ +import { buildSchema } from 'graphql'; + +function sleep(ms: number) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +export const schema = buildSchema(` +type Query { + hello: String +} +type Subscription { + countDown: Int +} +`); + +export const roots = { + Query: { + hello: () => 'Hello World!', + }, + subscription: { + /* eslint no-await-in-loop: "off" */ + + countDown: async function* fiveToOne() { + for (const number of [5, 4, 3, 2, 1]) { + await sleep(1000); // slow down a bit so user can see the count down on GraphiQL + yield { countDown: number }; + } + }, + }, +}; + +export const rootValue = { + hello: roots.Query.hello, + countDown: roots.subscription.countDown, +}; diff --git a/package-lock.json b/package-lock.json index 928abda2..565e242e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -318,6 +318,18 @@ } } }, + "@graphiql/toolkit": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.1.1.tgz", + "integrity": "sha512-cvsuaPkOA6/TZOdqEdvzqr7i+or2STTpSsteyDkrUXrwftRnH9ZfiUwnHPyf0AC2cKMwpP/Dny/UTS6CLC8ZNQ==", + "dev": true, + "requires": { + "@n1ru4l/push-pull-async-iterable-iterator": "^2.0.1", + "graphql-ws": "^4.1.0", + "meros": "^1.1.2", + "subscriptions-transport-ws": "^0.9.18" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -388,6 +400,12 @@ "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "dev": true }, + "@n1ru4l/push-pull-async-iterable-iterator": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-2.1.2.tgz", + "integrity": "sha512-KwZGeX2XK7Xj9ksWwei5923QnqIGoEuLlh3O46OW9vc8hQxjzmMTKCgJMVZ5ne5xaWFQYDT2dMpbUhq6hEOhxA==", + "dev": true + }, "@netflix/nerror": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@netflix/nerror/-/nerror-1.1.3.tgz", @@ -695,11 +713,12 @@ } }, "@types/ws": { - "version": "0.0.38", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-0.0.38.tgz", - "integrity": "sha1-QhBv/0tCLKlWc04p8Nc6bYkxlNM=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz", + "integrity": "sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg==", "dev": true, "requires": { + "@types/events": "*", "@types/node": "*" } }, @@ -991,6 +1010,12 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1275,19 +1300,19 @@ } }, "codemirror": { - "version": "5.58.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.58.2.tgz", - "integrity": "sha512-K/hOh24cCwRutd1Mk3uLtjWzNISOkm4fvXiMO7LucCrqbh6aJDdtqUziim3MZUI6wOY0rvY1SlL1Ork01uMy6w==", + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.60.0.tgz", + "integrity": "sha512-AEL7LhFOlxPlCL8IdTcJDblJm8yrAGib7I+DErJPdZd4l6imx8IMgKK3RblVgBQqz3TZJR4oknQ03bz+uNjBYA==", "dev": true }, "codemirror-graphql": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-0.12.3.tgz", - "integrity": "sha512-u0TooVA2MWGNV+Bio89RCTRW9P5FqegB1V9rnz9I0QKoGXX/c9z9/Fc+nj18p8jxkWK8ii8d7hkz7vsNsHxdkw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.0.1.tgz", + "integrity": "sha512-5ttMpv2kMn99Rmf2aZ5P6/hMd3y11cN8LP/x5MUeF0ipcalZA/GE/OxxXkhV0YJE/uW5QIcPyZDkvtSsGZa23A==", "dev": true, "requires": { - "graphql-language-service-interface": "^2.4.2", - "graphql-language-service-parser": "^1.6.4" + "graphql-language-service-interface": "^2.8.2", + "graphql-language-service-parser": "^1.9.0" } }, "color-convert": { @@ -2102,6 +2127,12 @@ } } }, + "dset": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.0.tgz", + "integrity": "sha512-7xTQ5DzyE59Nn+7ZgXDXjKAGSGmXZHqttMVVz1r4QNfmGpyj+cm2YtI3II0c/+4zS4a9yq2mBhgdeq2QnpcYlw==", + "dev": true + }, "dtrace-provider": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", @@ -2150,9 +2181,9 @@ } }, "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, "error-ex": { @@ -2532,9 +2563,9 @@ "dev": true }, "eventemitter3": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", - "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "dev": true }, "ewma": { @@ -3021,16 +3052,39 @@ "dev": true }, "graphiql": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-1.0.6.tgz", - "integrity": "sha512-PgKEfWkXxU4Lx92eSEcLF/2en5bjGplxNwwLCKEQ82xU7t8wfj4UL46Zwx40E9LcxJum6KRfGzMjcY+bNwHzpQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-1.4.1.tgz", + "integrity": "sha512-C7S36lTgCw2/C/Dt90eJSI9VdxQfohrUoDV1dt/WecS7dm5HcaQUIYFqvLQMZG1cSRJttRKwNwP1rYfs73v8SQ==", "dev": true, "requires": { + "@graphiql/toolkit": "^0.2.0", "codemirror": "^5.54.0", - "codemirror-graphql": "^0.12.3", + "codemirror-graphql": "^1.0.0", "copy-to-clipboard": "^3.2.0", + "dset": "^3.1.0", "entities": "^2.0.0", + "graphql-language-service": "^3.1.2", "markdown-it": "^10.0.0" + }, + "dependencies": { + "@graphiql/toolkit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.2.0.tgz", + "integrity": "sha512-T8fdGSh1bYqpQUurIBnNbXHMOFqV/btTdlcAw3+snItA619GgZfc471lYIT95/cywxbH2Ync/gqGgeSTeZhlTg==", + "dev": true, + "requires": { + "@n1ru4l/push-pull-async-iterable-iterator": "^2.0.1", + "graphql-ws": "^4.3.2", + "meros": "^1.1.4", + "subscriptions-transport-ws": "^0.9.18" + } + }, + "graphql-ws": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.4.0.tgz", + "integrity": "sha512-ZInnJEvEzgFUPUROhjaKH0wVUkUSz6mZY4ix/8QhTb6B4QlWtGtSMmVGG+uKPRi6YuMyoTwMnTcZTOSlC+gMAA==", + "dev": true + } } }, "graphiql-subscriptions-fetcher": { @@ -3043,6 +3097,21 @@ "subscriptions-transport-ws": "0.5.4" }, "dependencies": { + "@types/ws": { + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-0.0.38.tgz", + "integrity": "sha1-QhBv/0tCLKlWc04p8Nc6bYkxlNM=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=", + "dev": true + }, "graphql": { "version": "0.9.6", "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.9.6.tgz", @@ -3051,6 +3120,32 @@ "requires": { "iterall": "^1.0.0" } + }, + "subscriptions-transport-ws": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.5.4.tgz", + "integrity": "sha1-EJHlXnO/8NaRqGVwrCuX/2+zXPw=", + "dev": true, + "requires": { + "@types/ws": "0.0.38", + "backo2": "^1.0.2", + "eventemitter3": "^2.0.2", + "graphql-subscriptions": "^0.3.0", + "graphql-tag": "^1.2.4", + "lodash.isobject": "^3.0.2", + "lodash.isstring": "^4.0.1", + "ws": "^1.1.0" + } + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "dev": true, + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } } } }, @@ -3060,49 +3155,51 @@ "integrity": "sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA==", "dev": true }, + "graphql-language-service": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-3.1.3.tgz", + "integrity": "sha512-MTJT8QOpsJbG68wbkrmitlctvaajrQkJEN24AW+KzNxHWFEHnnqil6fFbVccHkRbG3Bk7D0f57fjtffSh37aEw==", + "dev": true, + "requires": { + "graphql-language-service-interface": "^2.8.2", + "graphql-language-service-types": "^1.8.0" + } + }, "graphql-language-service-interface": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.4.2.tgz", - "integrity": "sha512-iFLMz51cA2L5Tu7/mP19++bRGUuIe2J9ekQZrcJ6sMYStsF60x5eNu3JqheduYTLhQaSdKN55jX7RlLeIDUhQA==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.3.tgz", + "integrity": "sha512-Gh4Q3dlCT1MrZGO0eaz7v31gkp8fh+ig94YH/A+1Th2q+k3RsRqfSJm5tKZ8TJ4rSADZ/dj+hzOpWCGzLyCiHQ==", "dev": true, "requires": { - "graphql-language-service-parser": "^1.6.4", - "graphql-language-service-types": "^1.6.3", - "graphql-language-service-utils": "^2.4.3", + "graphql-language-service-parser": "^1.9.0", + "graphql-language-service-types": "^1.8.0", + "graphql-language-service-utils": "^2.5.1", "vscode-languageserver-types": "^3.15.1" } }, "graphql-language-service-parser": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.6.4.tgz", - "integrity": "sha512-Y365zUFfJ1GJ9NeRHb5Z/HBo6EnbuTi187Gkuldwd1YIDc0QcD7kqz6U5g043zd7BI/UZQth13Zd7pElvbb2zw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.1.tgz", + "integrity": "sha512-GySsDrYxzxu6r1vF282xXDR2KlfVL5aOW7pgc75fF3UFiuqGm/SeoIljNM0mLpRl5KSxo1HNOxhkWoFBoy/h2w==", "dev": true, "requires": { - "graphql-language-service-types": "^1.6.3", - "typescript": "^3.9.5" - }, - "dependencies": { - "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", - "dev": true - } + "graphql-language-service-types": "^1.8.0" } }, "graphql-language-service-types": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.6.3.tgz", - "integrity": "sha512-VDtBhdan1iSe7ad7+eBbsO5rrzWQpC6aV4SxSHEi8AtEQOFXpnL9Lq5jSaN8O02pGvAUr4wNUPu0oRU5g2XmVA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.1.tgz", + "integrity": "sha512-IpYS0mEHEmRsFlq+loWCpSYYYizAID7Alri6GoFN1QqUdux+8rp1Tkp2NGsGDpDmm3Dbz5ojmJWzNWQGpuwveA==", "dev": true }, "graphql-language-service-utils": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.4.3.tgz", - "integrity": "sha512-XSCMKsV4GuVSGdW8RJTpO/IJDMXgESDJLu67SAuXFXwfel84j1gWrsmBAUeu6Di6NUEoM9NOCEtJv3LbU+/8qw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.2.tgz", + "integrity": "sha512-hXGd4ARhyD7WTmTwuYmCYo6BcY8FtTp+1JHLaUG0Q63k0NpZTuFuRZ+N7TSP9mcRb7labeozs3DYgaqStsDe1A==", "dev": true, "requires": { - "graphql-language-service-types": "^1.6.3" + "graphql-language-service-types": "^1.8.0", + "nullthrows": "^1.0.0" } }, "graphql-subscriptions": { @@ -3120,6 +3217,12 @@ "integrity": "sha1-ers6j9nzQV0HFjMU7SNwYceFt1k=", "dev": true }, + "graphql-ws": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.1.2.tgz", + "integrity": "sha512-c/iOE4kGW6J5h9hmHWaYvgsjAQacioae3ZXvq3JDuVw8uXo3Tbmky71Fzn0+emSKRaRNL1jQuzYtRqFKge2PIw==", + "dev": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -3830,6 +3933,12 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "meros": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz", + "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -4154,6 +4263,12 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true + }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -5521,19 +5636,16 @@ "dev": true }, "subscriptions-transport-ws": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.5.4.tgz", - "integrity": "sha1-EJHlXnO/8NaRqGVwrCuX/2+zXPw=", + "version": "0.9.18", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz", + "integrity": "sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA==", "dev": true, "requires": { - "@types/ws": "0.0.38", "backo2": "^1.0.2", - "eventemitter3": "^2.0.2", - "graphql-subscriptions": "^0.3.0", - "graphql-tag": "^1.2.4", - "lodash.isobject": "^3.0.2", - "lodash.isstring": "^4.0.1", - "ws": "^1.1.0" + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0" } }, "superagent": { @@ -5614,6 +5726,12 @@ "has-flag": "^4.0.0" } }, + "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==", + "dev": true + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -5875,9 +5993,9 @@ } }, "vscode-languageserver-types": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", - "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", "dev": true }, "vscode-uri": { @@ -6038,13 +6156,12 @@ } }, "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", "dev": true, "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" + "async-limiter": "~1.0.0" } }, "xdg-basedir": { diff --git a/package.json b/package.json index ef48d894..06e9a1f3 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,9 @@ "check:spelling": "cspell '**/*'", "check:integrations": "mocha --full-trace integrationTests/*-test.js", "build:npm": "node resources/build-npm.js", - "start": "node -r ./resources/register.js examples/index.ts" + "start": "node -r ./resources/register.js examples/index.ts", + "start:subscription": "node -r ./resources/register.js examples/index_subscription.ts", + "start:subscription_legacy": "node -r ./resources/register.js examples/index_subscription_legacy.ts" }, "dependencies": { "accepts": "^1.3.7", @@ -57,6 +59,7 @@ "raw-body": "^2.4.1" }, "devDependencies": { + "@graphiql/toolkit": "^0.1.0", "@types/accepts": "1.3.5", "@types/body-parser": "1.19.0", "@types/chai": "4.2.14", @@ -70,6 +73,7 @@ "@types/restify": "8.4.2", "@types/sinon": "9.0.8", "@types/supertest": "2.0.10", + "@types/ws": "5.1.2", "@typescript-eslint/eslint-plugin": "4.8.1", "@typescript-eslint/parser": "4.8.1", "body-parser": "1.19.0", @@ -83,9 +87,10 @@ "eslint-plugin-istanbul": "0.1.2", "eslint-plugin-node": "11.1.0", "express": "4.17.1", - "graphiql": "1.0.6", + "graphiql": "^1.4.1", "graphiql-subscriptions-fetcher": "0.0.2", "graphql": "15.4.0", + "graphql-ws": "4.1.2", "mocha": "8.2.1", "multer": "1.4.2", "nyc": "15.1.0", @@ -95,11 +100,12 @@ "react-dom": "16.14.0", "restify": "8.5.1", "sinon": "9.2.1", - "subscriptions-transport-ws": "0.5.4", + "subscriptions-transport-ws": "0.9.18", "supertest": "6.0.1", "ts-node": "9.0.0", "typescript": "4.1.2", - "unfetch": "4.2.0" + "unfetch": "4.2.0", + "ws": "5.2.2" }, "peerDependencies": { "graphql": "^14.7.0 || ^15.3.0" diff --git a/src/__tests__/http-test.ts b/src/__tests__/http-test.ts index 6a2b3218..d449e09b 100644 --- a/src/__tests__/http-test.ts +++ b/src/__tests__/http-test.ts @@ -2015,6 +2015,34 @@ function runTests(server: Server) { // should contain the subscriptionEndpoint url expect(response.text).to.include('ws:\\/\\/localhost'); }); + + it('contains subscriptionEndpoint within GraphiQL with websocketClient option', async () => { + const app = server(); + + app.get( + urlString(), + graphqlHTTP({ + schema: TestSchema, + graphiql: { + subscriptionEndpoint: 'ws://localhost', + websocketClient: 'v1', + }, + }), + ); + + const response = await app + .request() + .get(urlString()) + .set('Accept', 'text/html'); + + expect(response.status).to.equal(200); + expect(response.type).to.equal('text/html'); + // should contain graphql-ws browser client + expect(response.text).to.include('graphql-transport-ws'); + + // should contain the subscriptionEndpoint url + expect(response.text).to.include('ws:\\/\\/localhost'); + }); }); describe('Custom validate function', () => { diff --git a/src/renderGraphiQL.ts b/src/renderGraphiQL.ts index df5b0a0c..f700e746 100644 --- a/src/renderGraphiQL.ts +++ b/src/renderGraphiQL.ts @@ -25,6 +25,13 @@ export interface GraphiQLOptions { * A websocket endpoint for subscription */ subscriptionEndpoint?: string; + + /** + * websocket client option for subscription, defaults to v0 + * v0: subscriptions-transport-ws + * v1: graphql-ws + */ + websocketClient?: string; } // Ensures string values are safe to be used within a + - - `; + + `; + } else { + subscriptionScripts = ` + + + + `; + } } return `