From 5c0f46f568e606969cd3e3d571005ae4fee324b3 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 17 May 2021 11:13:11 -0400 Subject: [PATCH] WIP --- package.json | 5 +- src/GraphqlSocketSubscriptionServer.ts | 110 +++++++++++++++++++++++++ yarn.lock | 17 ++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/GraphqlSocketSubscriptionServer.ts diff --git a/package.json b/package.json index 4c0bebb2..55d39ecb 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,11 @@ ] }, "dependencies": { + "@types/ws": "^7.4.1", "express": "^4.17.1", - "redis": "^3.0.0" + "graphql-ws": "^4.3.2", + "redis": "^3.0.0", + "ws": "^7.4.4" }, "peerDependencies": { "graphql": ">=0.12.3", diff --git a/src/GraphqlSocketSubscriptionServer.ts b/src/GraphqlSocketSubscriptionServer.ts new file mode 100644 index 00000000..e9ac8477 --- /dev/null +++ b/src/GraphqlSocketSubscriptionServer.ts @@ -0,0 +1,110 @@ +import type { IncomingMessage } from 'http'; +import { promisify } from 'util'; + +import express from 'express'; +import type { GraphQLSchema } from 'graphql'; +import { useServer } from 'graphql-ws/lib/use/ws'; +import ws from 'ws'; + +import AuthorizedSocketConnection from './AuthorizedSocketConnection'; +import type { CreateValidationRules } from './AuthorizedSocketConnection'; +import type { CredentialsManager } from './CredentialsManager'; +import type { CreateLogger, Logger } from './Logger'; +import type { Subscriber } from './Subscriber'; + +export type SubscriptionServerConfig = { + path: string; + schema: GraphQLSchema; + subscriber: Subscriber; + createCredentialsManager: (request: any) => CredentialsManager; + hasPermission: (data: any, credentials: TCredentials) => boolean; + createContext?: ( + request: any, + credentials: TCredentials | null | undefined, + ) => TContext; + maxSubscriptionsPerConnection?: number; + createValidationRules?: CreateValidationRules; + createLogger?: CreateLogger; +}; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const defaultCreateLogger = () => () => {}; + +export default class SubscriptionServer { + config: SubscriptionServerConfig; + + log: Logger; + + server: ws.Server | null = null; + + constructor(config: SubscriptionServerConfig) { + this.config = config; + + const createLogger: CreateLogger = + config.createLogger || defaultCreateLogger; + this.log = createLogger('@4c/SubscriptionServer::Server'); + } + + attach(httpServer: any) { + this.server = new ws.Server({ + server: httpServer, + path: this.config.path, + }); + + const { createContext } = this.config; + + useServer( + // from the previous step + { + schema: this.config.schema, + context: (ctx, msg, args) => { + + }, + onConnect() + + // credentialsManager: this.config.createCredentialsManager(request), + // hasPermission: this.config.hasPermission, + createContext: + createContext && + ((credentials: TCredentials | null | undefined) => + createContext(request, credentials)), + maxSubscriptionsPerConnection: this.config + .maxSubscriptionsPerConnection, + createValidationRules: this.config.createValidationRules, + createLogger: this.config.createLogger || defaultCreateLogger, + }, + wsServer, + ); + + this.server.on('connection', this.handleConnection); + } + + handleConnection = (socket: ws, req: IncomingMessage) => { + this.log('debug', 'new socket connection'); + + const request = Object.create((express as any).request); + Object.assign(request, req); + + const { createContext } = this.config; + + // eslint-disable-next-line no-new + new AuthorizedSocketConnection(socket, { + schema: this.config.schema, + subscriber: this.config.subscriber, + credentialsManager: this.config.createCredentialsManager(request), + hasPermission: this.config.hasPermission, + createContext: + createContext && + ((credentials: TCredentials | null | undefined) => + createContext(request, credentials)), + maxSubscriptionsPerConnection: this.config.maxSubscriptionsPerConnection, + createValidationRules: this.config.createValidationRules, + createLogger: this.config.createLogger || defaultCreateLogger, + }); + }; + + async close() { + // @ts-ignore + await promisify((...args) => this.io.close(...args))(); + } +} diff --git a/yarn.lock b/yarn.lock index a7911313..325c1c20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1995,6 +1995,13 @@ dependencies: "@types/node" "*" +"@types/ws@^7.4.1": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.1.tgz#49eacb15a0534663d53a36fbf5b4d98f5ae9a73a" + integrity sha512-ISCK1iFnR+jYv7+jLNX0wDqesZ/5RAeY3wUx6QaphmocphU61h+b+PHjS18TF4WIPTu/MMzxIq2PHr32o2TS5Q== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -5830,6 +5837,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.5 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graphql-ws@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-4.3.2.tgz#c58b03acc3bd5d4a92a6e9f729d29ba5e90d46a3" + integrity sha512-jsW6eOlko7fJek1iaSGQFj97AWuhexL9A3PuxYtyke/VlMdbSFzmDR4PlPPCTBBskRg6tNRb5RTbBVSd2T60JQ== + graphql@^15.4.0: version "15.4.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.4.0.tgz#e459dea1150da5a106486ba7276518b5295a4347" @@ -12525,6 +12537,11 @@ ws@^7.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.0.tgz#a5dd76a24197940d4a8bb9e0e152bb4503764da7" integrity sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ== +ws@^7.4.4: + version "7.4.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" + integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw== + xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"