Skip to content

Commit

Permalink
feat!: add websocket server option
Browse files Browse the repository at this point in the history
BREAKING CHANGE: the subscription server export is now an abstract class
  • Loading branch information
jquense committed May 18, 2021
1 parent 5c0f46f commit ed71098
Show file tree
Hide file tree
Showing 18 changed files with 1,640 additions and 210 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
# GraphQL Subscription Server

A subscription server for GraphQL subscriptions. Supports streaming over plain web sockets
or Socket.IO, and integrates with Redis or any other Pub/Sub service.

## Setup

### Socket.IO

```js
import http from 'http';
import {
SocketIOSubscriptionServer, // or WebSocketSubscriptionServer
JwtCredentialManager,
RedisSubscriber,
} from '@4c/graphql-subscription-server';

const server = http.createServer();

const subscriptionServer = new SocketIOSubscriptionServer({
schema,
path: '/socket.io/graphql',
subscriber: new RedisSubscriber(),
hasPermission: (message, credentials) => {
authorize(message, credentials);
},
createCredentialsManager: (req) => new JwtCredentialManager(),
createLogger: () => console.debug,
});

subscriptionServer.attach(server);

server.listen(4000, () => {
console.log('server running');
});
```
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"tdd": "jest --watch",
"test": "yarn lint && yarn typecheck && jest",
"testonly": "jest",
"typecheck": "tsc --noEmit && tsc -p test --noEmit"
"typecheck": "tsc --noEmit && tsc -p test --noEmit",
"update-schema": "NODE_ENV=test babel-node ./update-schema.js"
},
"husky": {
"hooks": {
Expand Down Expand Up @@ -51,7 +52,7 @@
"express": "^4.17.1",
"graphql-ws": "^4.3.2",
"redis": "^3.0.0",
"ws": "^7.4.4"
"ws": "^7.4.5"
},
"peerDependencies": {
"graphql": ">=0.12.3",
Expand All @@ -71,6 +72,7 @@
"@4c/tsconfig": "^0.3.1",
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/node": "^7.14.2",
"@babel/preset-typescript": "^7.12.7",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.19",
Expand All @@ -88,13 +90,16 @@
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.3.0",
"graphql": "^15.4.0",
"graphql-relay": "^0.6.0",
"graphql-relay-subscription": "^0.3.1",
"husky": "^4.3.6",
"jest": "^26.6.3",
"lint-staged": "^10.5.3",
"prettier": "^2.2.1",
"redis-mock": "^0.55.1",
"semantic-release": "^17.3.0",
"socket.io": "^3.0.4",
"socket.io": "^4.1.2",
"socket.io-client": "^4.1.2",
"travis-deploy-once": "^5.0.11",
"typescript": "^4.1.3",
"utility-types": "^3.10.0"
Expand Down
17 changes: 8 additions & 9 deletions src/AuthorizedSocketConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
validate,
} from 'graphql';
import { ExecutionResult } from 'graphql/execution/execute';
import io from 'socket.io';

import * as AsyncUtils from './AsyncUtils';
import { CredentialsManager } from './CredentialsManager';
import { CreateLogger, Logger } from './Logger';
import { Subscriber } from './Subscriber';
import SubscriptionContext from './SubscriptionContext';
import { WebSocket } from './types';

export type CreateValidationRules = ({
query,
Expand Down Expand Up @@ -62,7 +62,7 @@ const acknowledge = (cb?: () => void) => {
* - Rudimentary connection constraints (max connections)
*/
export default class AuthorizedSocketConnection<TContext, TCredentials> {
socket: io.Socket;
socket: WebSocket;

config: AuthorizedSocketOptions<TContext, TCredentials>;

Expand All @@ -74,7 +74,7 @@ export default class AuthorizedSocketConnection<TContext, TCredentials> {
>;

constructor(
socket: io.Socket,
socket: WebSocket,
config: AuthorizedSocketOptions<TContext, TCredentials>,
) {
this.socket = socket;
Expand All @@ -83,12 +83,11 @@ export default class AuthorizedSocketConnection<TContext, TCredentials> {
this.log = config.createLogger('@4c/SubscriptionServer::AuthorizedSocket');
this.subscriptionContexts = new Map();

this.socket
.on('authenticate', this.handleAuthenticate)
.on('subscribe', this.handleSubscribe)
.on('unsubscribe', this.handleUnsubscribe)
.on('connect', this.handleConnect)
.on('disconnect', this.handleDisconnect);
this.socket.on('authenticate', this.handleAuthenticate);
this.socket.on('subscribe', this.handleSubscribe);
this.socket.on('unsubscribe', this.handleUnsubscribe);
this.socket.on('connect', this.handleConnect);
this.socket.on('disconnect', this.handleDisconnect);
}

emitError(error: { code: string; data?: any }) {
Expand Down
110 changes: 0 additions & 110 deletions src/GraphqlSocketSubscriptionServer.ts

This file was deleted.

67 changes: 67 additions & 0 deletions src/SocketIOSubscriptionServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { promisify } from 'util';

import express from 'express';
import type io from 'socket.io';

import SubscriptionServer, {
SubscriptionServerConfig,
} from './SubscriptionServer';

export interface SocketIOSubscriptionServerConfig<TContext, TCredentials>
extends SubscriptionServerConfig<TContext, TCredentials> {
socketIoServer?: io.Server;
}

export default class SocketIOSubscriptionServer<
TContext,
TCredentials
> extends SubscriptionServer<TContext, TCredentials> {
io: io.Server;

constructor({
socketIoServer,
...config
}: SocketIOSubscriptionServerConfig<TContext, TCredentials>) {
super(config);

this.io = socketIoServer!;
if (!this.io) {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const IoServer = require('socket.io').Server;
this.io = new IoServer({
serveClient: false,
path: this.config.path,
transports: ['websocket'],
allowEIO3: true,
});
}

this.io.on('connection', (socket: io.Socket) => {
const request = Object.create((express as any).request);
Object.assign(request, socket.request);
this.opened(
{
id: socket.id,
protocol: 'socket-io',
on: socket.on.bind(socket),
emit(event: string, data: any) {
socket.emit(event, data);
},
close() {
socket.disconnect();
},
},
request,
);
});
}

attach(httpServer: any) {
this.io.attach(httpServer);
}

async close() {
// @ts-ignore
await promisify((...args) => this.io.close(...args))();
}
}
Loading

0 comments on commit ed71098

Please sign in to comment.