Initial Sample.

This commit is contained in:
2024-06-03 20:23:50 +05:30
parent ef2b65f673
commit 5269ec3c66
2575 changed files with 282312 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
import type { FastifyRequest } from 'fastify';
import type * as fastifyWebsocket from '@fastify/websocket';
import { ServerOptions } from '../../server.mjs';
import { ConnectionInitMessage } from '../../common.mjs';
/**
* The extra that will be put in the `Context`.
*
* @category Server/@fastify/websocket
*/
export interface Extra {
/**
* The underlying socket connection between the server and the client.
* The WebSocket socket is located under the `socket` parameter.
*/
readonly connection: fastifyWebsocket.SocketStream;
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*/
readonly request: FastifyRequest;
}
/**
* Make a handler to use on a [@fastify/websocket](https://github.com/fastify/fastify-websocket) route.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/@fastify/websocket
*/
export declare function makeHandler<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive?: number): fastifyWebsocket.WebsocketHandler;

View File

@@ -0,0 +1,36 @@
import type { FastifyRequest } from 'fastify';
import type * as fastifyWebsocket from '@fastify/websocket';
import { ServerOptions } from '../../server';
import { ConnectionInitMessage } from '../../common';
/**
* The extra that will be put in the `Context`.
*
* @category Server/@fastify/websocket
*/
export interface Extra {
/**
* The underlying socket connection between the server and the client.
* The WebSocket socket is located under the `socket` parameter.
*/
readonly connection: fastifyWebsocket.SocketStream;
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*/
readonly request: FastifyRequest;
}
/**
* Make a handler to use on a [@fastify/websocket](https://github.com/fastify/fastify-websocket) route.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/@fastify/websocket
*/
export declare function makeHandler<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive?: number): fastifyWebsocket.WebsocketHandler;

View File

@@ -0,0 +1,126 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeHandler = void 0;
const server_1 = require("../../server");
const common_1 = require("../../common");
const utils_1 = require("../../utils");
/**
* Make a handler to use on a [@fastify/websocket](https://github.com/fastify/fastify-websocket) route.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/@fastify/websocket
*/
function makeHandler(options,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive = 12000) {
const isProd = process.env.NODE_ENV === 'production';
const server = (0, server_1.makeServer)(options);
// we dont have access to the fastify-websocket server instance yet,
// register an error handler on first connection ONCE only
let handlingServerEmittedErrors = false;
return function handler(connection, request) {
const { socket } = connection;
// might be too late, but meh
this.websocketServer.options.handleProtocols = server_1.handleProtocols;
// handle server emitted errors only if not already handling
if (!handlingServerEmittedErrors) {
handlingServerEmittedErrors = true;
this.websocketServer.once('error', (err) => {
console.error('Internal error emitted on the WebSocket server. ' +
'Please check your implementation.', err);
// catch the first thrown error and re-throw it once all clients have been notified
let firstErr = null;
// report server errors by erroring out all clients with the same error
for (const client of this.websocketServer.clients) {
try {
client.close(common_1.CloseCode.InternalServerError, isProd
? 'Internal server error'
: (0, utils_1.limitCloseReason)(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
catch (err) {
firstErr = firstErr !== null && firstErr !== void 0 ? firstErr : err;
}
}
if (firstErr)
throw firstErr;
});
}
// used as listener on two streams, prevent superfluous calls on close
let emittedErrorHandled = false;
function handleEmittedError(err) {
if (emittedErrorHandled)
return;
emittedErrorHandled = true;
console.error('Internal error emitted on a WebSocket socket. ' +
'Please check your implementation.', err);
socket.close(common_1.CloseCode.InternalServerError, isProd
? 'Internal server error'
: (0, utils_1.limitCloseReason)(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
// fastify-websocket uses the WebSocket.createWebSocketStream,
// therefore errors get emitted on both the connection and the socket
connection.once('error', handleEmittedError);
socket.once('error', handleEmittedError);
// keep alive through ping-pong messages
let pongWait = null;
const pingInterval = keepAlive > 0 && isFinite(keepAlive)
? setInterval(() => {
// ping pong on open sockets only
if (socket.readyState === socket.OPEN) {
// terminate the connection after pong wait has passed because the client is idle
pongWait = setTimeout(() => {
socket.terminate();
}, keepAlive);
// listen for client's pong and stop socket termination
socket.once('pong', () => {
if (pongWait) {
clearTimeout(pongWait);
pongWait = null;
}
});
socket.ping();
}
}, keepAlive)
: null;
const closed = server.opened({
protocol: socket.protocol,
send: (data) => new Promise((resolve, reject) => {
if (socket.readyState !== socket.OPEN)
return resolve();
socket.send(data, (err) => (err ? reject(err) : resolve()));
}),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) => socket.on('message', async (event) => {
try {
await cb(String(event));
}
catch (err) {
console.error('Internal error occurred during message handling. ' +
'Please check your implementation.', err);
socket.close(common_1.CloseCode.InternalServerError, isProd
? 'Internal server error'
: (0, utils_1.limitCloseReason)(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
}),
}, { connection, request });
socket.once('close', (code, reason) => {
if (pongWait)
clearTimeout(pongWait);
if (pingInterval)
clearInterval(pingInterval);
if (!isProd &&
code === common_1.CloseCode.SubprotocolNotAcceptable &&
socket.protocol === common_1.DEPRECATED_GRAPHQL_WS_PROTOCOL)
console.warn(`Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` +
'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.');
closed(code, String(reason));
});
};
}
exports.makeHandler = makeHandler;

View File

@@ -0,0 +1,122 @@
import { handleProtocols, makeServer } from '../../server.mjs';
import { DEPRECATED_GRAPHQL_WS_PROTOCOL, CloseCode, } from '../../common.mjs';
import { limitCloseReason } from '../../utils.mjs';
/**
* Make a handler to use on a [@fastify/websocket](https://github.com/fastify/fastify-websocket) route.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/@fastify/websocket
*/
export function makeHandler(options,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive = 12000) {
const isProd = process.env.NODE_ENV === 'production';
const server = makeServer(options);
// we dont have access to the fastify-websocket server instance yet,
// register an error handler on first connection ONCE only
let handlingServerEmittedErrors = false;
return function handler(connection, request) {
const { socket } = connection;
// might be too late, but meh
this.websocketServer.options.handleProtocols = handleProtocols;
// handle server emitted errors only if not already handling
if (!handlingServerEmittedErrors) {
handlingServerEmittedErrors = true;
this.websocketServer.once('error', (err) => {
console.error('Internal error emitted on the WebSocket server. ' +
'Please check your implementation.', err);
// catch the first thrown error and re-throw it once all clients have been notified
let firstErr = null;
// report server errors by erroring out all clients with the same error
for (const client of this.websocketServer.clients) {
try {
client.close(CloseCode.InternalServerError, isProd
? 'Internal server error'
: limitCloseReason(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
catch (err) {
firstErr = firstErr !== null && firstErr !== void 0 ? firstErr : err;
}
}
if (firstErr)
throw firstErr;
});
}
// used as listener on two streams, prevent superfluous calls on close
let emittedErrorHandled = false;
function handleEmittedError(err) {
if (emittedErrorHandled)
return;
emittedErrorHandled = true;
console.error('Internal error emitted on a WebSocket socket. ' +
'Please check your implementation.', err);
socket.close(CloseCode.InternalServerError, isProd
? 'Internal server error'
: limitCloseReason(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
// fastify-websocket uses the WebSocket.createWebSocketStream,
// therefore errors get emitted on both the connection and the socket
connection.once('error', handleEmittedError);
socket.once('error', handleEmittedError);
// keep alive through ping-pong messages
let pongWait = null;
const pingInterval = keepAlive > 0 && isFinite(keepAlive)
? setInterval(() => {
// ping pong on open sockets only
if (socket.readyState === socket.OPEN) {
// terminate the connection after pong wait has passed because the client is idle
pongWait = setTimeout(() => {
socket.terminate();
}, keepAlive);
// listen for client's pong and stop socket termination
socket.once('pong', () => {
if (pongWait) {
clearTimeout(pongWait);
pongWait = null;
}
});
socket.ping();
}
}, keepAlive)
: null;
const closed = server.opened({
protocol: socket.protocol,
send: (data) => new Promise((resolve, reject) => {
if (socket.readyState !== socket.OPEN)
return resolve();
socket.send(data, (err) => (err ? reject(err) : resolve()));
}),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) => socket.on('message', async (event) => {
try {
await cb(String(event));
}
catch (err) {
console.error('Internal error occurred during message handling. ' +
'Please check your implementation.', err);
socket.close(CloseCode.InternalServerError, isProd
? 'Internal server error'
: limitCloseReason(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
}),
}, { connection, request });
socket.once('close', (code, reason) => {
if (pongWait)
clearTimeout(pongWait);
if (pingInterval)
clearInterval(pingInterval);
if (!isProd &&
code === CloseCode.SubprotocolNotAcceptable &&
socket.protocol === DEPRECATED_GRAPHQL_WS_PROTOCOL)
console.warn(`Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` +
'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.');
closed(code, String(reason));
});
};
}

View File

@@ -0,0 +1,60 @@
/// <reference types="bun-types" />
import type { WebSocketHandler, ServerWebSocket } from 'bun';
import { ConnectionInitMessage } from '../common.mjs';
import { ServerOptions } from '../server.mjs';
/**
* Convenience export for checking the WebSocket protocol on the request in `Bun.serve`.
*/
export { handleProtocols } from '../server.mjs';
/**
* The extra that will be put in the `Context`.
*
* @category Server/bun
*/
export interface Extra {
/**
* The actual socket connection between the server and the client.
*/
readonly socket: ServerWebSocket;
}
/**
* Use the server with [Bun](https://bun.sh/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* The WebSocket subprotocol is not available on the established socket and therefore
* needs to be checked during the request handling.
*
* Additionally, the keep-alive logic _seems_ to be handled by Bun seeing that
* they default [`sendPingsAutomatically` to `true`](https://github.com/oven-sh/bun/blob/6a163cf933542506354dc836bd92693bcae5939b/src/deps/uws.zig#L893).
*
* ```ts
* import { makeHandler, handleProtocols } from 'graphql-ws/lib/use/lib/bun';
* import { schema } from './my-schema/index.mjs';
*
* Bun.serve({
* fetch(req, server) {
* const [path, _search] = req.url.split('?');
* if (!path.endsWith('/graphql')) {
* return new Response('Not Found', { status: 404 });
* }
* if (req.headers.get('upgrade') != 'websocket') {
* return new Response('Upgrade Required', { status: 426 });
* }
* if (!handleProtocols(req.headers.get('sec-websocket-protocol') || '')) {
* return new Response('Bad Request', { status: 404 });
* }
* if (!server.upgrade(req)) {
* return new Response('Internal Server Error', { status: 500 });
* }
* return new Response();
* },
* websocket: makeHandler({ schema }),
* port: 4000,
* });
*
* console.log('Listening to port 4000');
* ```
*
* @category Server/bun
*/
export declare function makeHandler<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>): WebSocketHandler;

View File

@@ -0,0 +1,60 @@
/// <reference types="bun-types" />
import type { WebSocketHandler, ServerWebSocket } from 'bun';
import { ConnectionInitMessage } from '../common';
import { ServerOptions } from '../server';
/**
* Convenience export for checking the WebSocket protocol on the request in `Bun.serve`.
*/
export { handleProtocols } from '../server';
/**
* The extra that will be put in the `Context`.
*
* @category Server/bun
*/
export interface Extra {
/**
* The actual socket connection between the server and the client.
*/
readonly socket: ServerWebSocket;
}
/**
* Use the server with [Bun](https://bun.sh/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* The WebSocket subprotocol is not available on the established socket and therefore
* needs to be checked during the request handling.
*
* Additionally, the keep-alive logic _seems_ to be handled by Bun seeing that
* they default [`sendPingsAutomatically` to `true`](https://github.com/oven-sh/bun/blob/6a163cf933542506354dc836bd92693bcae5939b/src/deps/uws.zig#L893).
*
* ```ts
* import { makeHandler, handleProtocols } from 'graphql-ws/lib/use/lib/bun';
* import { schema } from './my-schema';
*
* Bun.serve({
* fetch(req, server) {
* const [path, _search] = req.url.split('?');
* if (!path.endsWith('/graphql')) {
* return new Response('Not Found', { status: 404 });
* }
* if (req.headers.get('upgrade') != 'websocket') {
* return new Response('Upgrade Required', { status: 426 });
* }
* if (!handleProtocols(req.headers.get('sec-websocket-protocol') || '')) {
* return new Response('Bad Request', { status: 404 });
* }
* if (!server.upgrade(req)) {
* return new Response('Internal Server Error', { status: 500 });
* }
* return new Response();
* },
* websocket: makeHandler({ schema }),
* port: 4000,
* });
*
* console.log('Listening to port 4000');
* ```
*
* @category Server/bun
*/
export declare function makeHandler<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>): WebSocketHandler;

View File

@@ -0,0 +1,97 @@
"use strict";
/// <reference types="bun-types" />
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeHandler = exports.handleProtocols = void 0;
const common_1 = require("../common");
const server_1 = require("../server");
/**
* Convenience export for checking the WebSocket protocol on the request in `Bun.serve`.
*/
var server_2 = require("../server");
Object.defineProperty(exports, "handleProtocols", { enumerable: true, get: function () { return server_2.handleProtocols; } });
/**
* Use the server with [Bun](https://bun.sh/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* The WebSocket subprotocol is not available on the established socket and therefore
* needs to be checked during the request handling.
*
* Additionally, the keep-alive logic _seems_ to be handled by Bun seeing that
* they default [`sendPingsAutomatically` to `true`](https://github.com/oven-sh/bun/blob/6a163cf933542506354dc836bd92693bcae5939b/src/deps/uws.zig#L893).
*
* ```ts
* import { makeHandler, handleProtocols } from 'graphql-ws/lib/use/lib/bun';
* import { schema } from './my-schema';
*
* Bun.serve({
* fetch(req, server) {
* const [path, _search] = req.url.split('?');
* if (!path.endsWith('/graphql')) {
* return new Response('Not Found', { status: 404 });
* }
* if (req.headers.get('upgrade') != 'websocket') {
* return new Response('Upgrade Required', { status: 426 });
* }
* if (!handleProtocols(req.headers.get('sec-websocket-protocol') || '')) {
* return new Response('Bad Request', { status: 404 });
* }
* if (!server.upgrade(req)) {
* return new Response('Internal Server Error', { status: 500 });
* }
* return new Response();
* },
* websocket: makeHandler({ schema }),
* port: 4000,
* });
*
* console.log('Listening to port 4000');
* ```
*
* @category Server/bun
*/
function makeHandler(options) {
const server = (0, server_1.makeServer)(options);
const clients = new WeakMap();
return {
open(ws) {
const client = {
handleMessage: () => {
throw new Error('Message received before handler was registered');
},
closed: () => {
throw new Error('Closed before handler was registered');
},
};
client.closed = server.opened({
// TODO: use protocol on socket once Bun exposes it
protocol: common_1.GRAPHQL_TRANSPORT_WS_PROTOCOL,
send: async (message) => {
// ws might have been destroyed in the meantime, send only if exists
if (clients.has(ws)) {
ws.sendText(message);
}
},
close: (code, reason) => {
if (clients.has(ws)) {
ws.close(code, reason);
}
},
onMessage: (cb) => (client.handleMessage = cb),
}, { socket: ws });
clients.set(ws, client);
},
message(ws, message) {
const client = clients.get(ws);
if (!client)
throw new Error('Message received for a missing client');
return client.handleMessage(String(message));
},
close(ws, code, message) {
const client = clients.get(ws);
if (!client)
throw new Error('Closing a missing client');
return client.closed(code, message);
},
};
}
exports.makeHandler = makeHandler;

View File

@@ -0,0 +1,92 @@
/// <reference types="bun-types" />
import { GRAPHQL_TRANSPORT_WS_PROTOCOL, } from '../common.mjs';
import { makeServer } from '../server.mjs';
/**
* Convenience export for checking the WebSocket protocol on the request in `Bun.serve`.
*/
export { handleProtocols } from '../server.mjs';
/**
* Use the server with [Bun](https://bun.sh/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* The WebSocket subprotocol is not available on the established socket and therefore
* needs to be checked during the request handling.
*
* Additionally, the keep-alive logic _seems_ to be handled by Bun seeing that
* they default [`sendPingsAutomatically` to `true`](https://github.com/oven-sh/bun/blob/6a163cf933542506354dc836bd92693bcae5939b/src/deps/uws.zig#L893).
*
* ```ts
* import { makeHandler, handleProtocols } from 'graphql-ws/lib/use/lib/bun';
* import { schema } from './my-schema/index.mjs';
*
* Bun.serve({
* fetch(req, server) {
* const [path, _search] = req.url.split('?');
* if (!path.endsWith('/graphql')) {
* return new Response('Not Found', { status: 404 });
* }
* if (req.headers.get('upgrade') != 'websocket') {
* return new Response('Upgrade Required', { status: 426 });
* }
* if (!handleProtocols(req.headers.get('sec-websocket-protocol') || '')) {
* return new Response('Bad Request', { status: 404 });
* }
* if (!server.upgrade(req)) {
* return new Response('Internal Server Error', { status: 500 });
* }
* return new Response();
* },
* websocket: makeHandler({ schema }),
* port: 4000,
* });
*
* console.log('Listening to port 4000');
* ```
*
* @category Server/bun
*/
export function makeHandler(options) {
const server = makeServer(options);
const clients = new WeakMap();
return {
open(ws) {
const client = {
handleMessage: () => {
throw new Error('Message received before handler was registered');
},
closed: () => {
throw new Error('Closed before handler was registered');
},
};
client.closed = server.opened({
// TODO: use protocol on socket once Bun exposes it
protocol: GRAPHQL_TRANSPORT_WS_PROTOCOL,
send: async (message) => {
// ws might have been destroyed in the meantime, send only if exists
if (clients.has(ws)) {
ws.sendText(message);
}
},
close: (code, reason) => {
if (clients.has(ws)) {
ws.close(code, reason);
}
},
onMessage: (cb) => (client.handleMessage = cb),
}, { socket: ws });
clients.set(ws, client);
},
message(ws, message) {
const client = clients.get(ws);
if (!client)
throw new Error('Message received for a missing client');
return client.handleMessage(String(message));
},
close(ws, code, message) {
const client = clients.get(ws);
if (!client)
throw new Error('Closing a missing client');
return client.closed(code, message);
},
};
}

View File

@@ -0,0 +1,56 @@
import { ServerOptions } from '../server.mjs';
import { ConnectionInitMessage } from '../common.mjs';
export { GRAPHQL_TRANSPORT_WS_PROTOCOL } from '../common.mjs';
/**
* The extra that will be put in the `Context`.
*
* @category Server/deno
*/
export interface Extra {
/**
* The actual socket connection between the server and the client.
*/
readonly socket: WebSocket;
}
/**
* Use the server with [Deno](https://deno.com/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs.
*
* The keep-alive is set in `Deno.upgradeWebSocket` during the upgrade.
*
* Additionally, the required WebSocket protocol is also defined during the upgrade,
* the correct example being:
*
* ```ts
* import { serve } from 'https://deno.land/std/http/mod.ts';
* import {
* makeHandler,
* GRAPHQL_TRANSPORT_WS_PROTOCOL,
* } from 'https://esm.sh/graphql-ws/lib/use/deno';
* import { schema } from './my-schema.ts/index.mjs';
*
* const handler = makeHandler({ schema });
*
* serve(
* (req: Request) => {
* const [path, _search] = req.url.split('?');
* if (!path.endsWith('/graphql')) {
* return new Response('Not Found', { status: 404 });
* }
* if (req.headers.get('upgrade') != 'websocket') {
* return new Response('Upgrade Required', { status: 426 });
* }
* const { socket, response } = Deno.upgradeWebSocket(req, {
* protocol: GRAPHQL_TRANSPORT_WS_PROTOCOL,
* idleTimeout: 12_000,
* });
* handler(socket);
* return response;
* },
* { port: 4000 },
* );
* ```
*
* @category Server/deno
*/
export declare function makeHandler<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>): (socket: WebSocket) => void;

View File

@@ -0,0 +1,56 @@
import { ServerOptions } from '../server';
import { ConnectionInitMessage } from '../common';
export { GRAPHQL_TRANSPORT_WS_PROTOCOL } from '../common';
/**
* The extra that will be put in the `Context`.
*
* @category Server/deno
*/
export interface Extra {
/**
* The actual socket connection between the server and the client.
*/
readonly socket: WebSocket;
}
/**
* Use the server with [Deno](https://deno.com/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs.
*
* The keep-alive is set in `Deno.upgradeWebSocket` during the upgrade.
*
* Additionally, the required WebSocket protocol is also defined during the upgrade,
* the correct example being:
*
* ```ts
* import { serve } from 'https://deno.land/std/http/mod.ts';
* import {
* makeHandler,
* GRAPHQL_TRANSPORT_WS_PROTOCOL,
* } from 'https://esm.sh/graphql-ws/lib/use/deno';
* import { schema } from './my-schema.ts';
*
* const handler = makeHandler({ schema });
*
* serve(
* (req: Request) => {
* const [path, _search] = req.url.split('?');
* if (!path.endsWith('/graphql')) {
* return new Response('Not Found', { status: 404 });
* }
* if (req.headers.get('upgrade') != 'websocket') {
* return new Response('Upgrade Required', { status: 426 });
* }
* const { socket, response } = Deno.upgradeWebSocket(req, {
* protocol: GRAPHQL_TRANSPORT_WS_PROTOCOL,
* idleTimeout: 12_000,
* });
* handler(socket);
* return response;
* },
* { port: 4000 },
* );
* ```
*
* @category Server/deno
*/
export declare function makeHandler<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>): (socket: WebSocket) => void;

View File

@@ -0,0 +1,88 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeHandler = exports.GRAPHQL_TRANSPORT_WS_PROTOCOL = void 0;
const server_1 = require("../server");
const common_1 = require("../common");
var common_2 = require("../common");
Object.defineProperty(exports, "GRAPHQL_TRANSPORT_WS_PROTOCOL", { enumerable: true, get: function () { return common_2.GRAPHQL_TRANSPORT_WS_PROTOCOL; } });
/**
* Use the server with [Deno](https://deno.com/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs.
*
* The keep-alive is set in `Deno.upgradeWebSocket` during the upgrade.
*
* Additionally, the required WebSocket protocol is also defined during the upgrade,
* the correct example being:
*
* ```ts
* import { serve } from 'https://deno.land/std/http/mod.ts';
* import {
* makeHandler,
* GRAPHQL_TRANSPORT_WS_PROTOCOL,
* } from 'https://esm.sh/graphql-ws/lib/use/deno';
* import { schema } from './my-schema.ts';
*
* const handler = makeHandler({ schema });
*
* serve(
* (req: Request) => {
* const [path, _search] = req.url.split('?');
* if (!path.endsWith('/graphql')) {
* return new Response('Not Found', { status: 404 });
* }
* if (req.headers.get('upgrade') != 'websocket') {
* return new Response('Upgrade Required', { status: 426 });
* }
* const { socket, response } = Deno.upgradeWebSocket(req, {
* protocol: GRAPHQL_TRANSPORT_WS_PROTOCOL,
* idleTimeout: 12_000,
* });
* handler(socket);
* return response;
* },
* { port: 4000 },
* );
* ```
*
* @category Server/deno
*/
function makeHandler(options) {
const server = (0, server_1.makeServer)(options);
return function handle(socket) {
socket.onerror = (err) => {
console.error('Internal error emitted on the WebSocket socket. ' +
'Please check your implementation.', err);
socket.close(common_1.CloseCode.InternalServerError, 'Internal server error');
};
let closed = () => {
// noop
};
socket.onopen = () => {
closed = server.opened({
protocol: socket.protocol,
send: (msg) => socket.send(msg),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) => {
socket.onmessage = async (event) => {
try {
await cb(String(event.data));
}
catch (err) {
console.error('Internal error occurred during message handling. ' +
'Please check your implementation.', err);
socket.close(common_1.CloseCode.InternalServerError, 'Internal server error');
}
};
},
}, { socket });
};
socket.onclose = (event) => {
if (event.code === common_1.CloseCode.SubprotocolNotAcceptable &&
socket.protocol === common_1.DEPRECATED_GRAPHQL_WS_PROTOCOL)
console.warn(`Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` +
'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.');
closed(event.code, event.reason);
};
};
}
exports.makeHandler = makeHandler;

View File

@@ -0,0 +1,83 @@
import { makeServer } from '../server.mjs';
import { DEPRECATED_GRAPHQL_WS_PROTOCOL, CloseCode, } from '../common.mjs';
export { GRAPHQL_TRANSPORT_WS_PROTOCOL } from '../common.mjs';
/**
* Use the server with [Deno](https://deno.com/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs.
*
* The keep-alive is set in `Deno.upgradeWebSocket` during the upgrade.
*
* Additionally, the required WebSocket protocol is also defined during the upgrade,
* the correct example being:
*
* ```ts
* import { serve } from 'https://deno.land/std/http/mod.ts';
* import {
* makeHandler,
* GRAPHQL_TRANSPORT_WS_PROTOCOL,
* } from 'https://esm.sh/graphql-ws/lib/use/deno';
* import { schema } from './my-schema.ts/index.mjs';
*
* const handler = makeHandler({ schema });
*
* serve(
* (req: Request) => {
* const [path, _search] = req.url.split('?');
* if (!path.endsWith('/graphql')) {
* return new Response('Not Found', { status: 404 });
* }
* if (req.headers.get('upgrade') != 'websocket') {
* return new Response('Upgrade Required', { status: 426 });
* }
* const { socket, response } = Deno.upgradeWebSocket(req, {
* protocol: GRAPHQL_TRANSPORT_WS_PROTOCOL,
* idleTimeout: 12_000,
* });
* handler(socket);
* return response;
* },
* { port: 4000 },
* );
* ```
*
* @category Server/deno
*/
export function makeHandler(options) {
const server = makeServer(options);
return function handle(socket) {
socket.onerror = (err) => {
console.error('Internal error emitted on the WebSocket socket. ' +
'Please check your implementation.', err);
socket.close(CloseCode.InternalServerError, 'Internal server error');
};
let closed = () => {
// noop
};
socket.onopen = () => {
closed = server.opened({
protocol: socket.protocol,
send: (msg) => socket.send(msg),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) => {
socket.onmessage = async (event) => {
try {
await cb(String(event.data));
}
catch (err) {
console.error('Internal error occurred during message handling. ' +
'Please check your implementation.', err);
socket.close(CloseCode.InternalServerError, 'Internal server error');
}
};
},
}, { socket });
};
socket.onclose = (event) => {
if (event.code === CloseCode.SubprotocolNotAcceptable &&
socket.protocol === DEPRECATED_GRAPHQL_WS_PROTOCOL)
console.warn(`Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` +
'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.');
closed(event.code, event.reason);
};
};
}

View File

@@ -0,0 +1,40 @@
import type { FastifyRequest } from 'fastify';
import type * as fastifyWebsocket from 'fastify-websocket';
import { ServerOptions } from '../server.mjs';
import { ConnectionInitMessage } from '../common.mjs';
/**
* The extra that will be put in the `Context`.
*
* @deprecated Use `@fastify/websocket` instead.
*
* @category Server/fastify-websocket
*/
export interface Extra {
/**
* The underlying socket connection between the server and the client.
* The WebSocket socket is located under the `socket` parameter.
*/
readonly connection: fastifyWebsocket.SocketStream;
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*/
readonly request: FastifyRequest;
}
/**
* Make a handler to use on a [fastify-websocket](https://github.com/fastify/fastify-websocket) route.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @deprecated Use `@fastify/websocket` instead.
*
* @category Server/fastify-websocket
*/
export declare function makeHandler<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive?: number): fastifyWebsocket.WebsocketHandler;

View File

@@ -0,0 +1,40 @@
import type { FastifyRequest } from 'fastify';
import type * as fastifyWebsocket from 'fastify-websocket';
import { ServerOptions } from '../server';
import { ConnectionInitMessage } from '../common';
/**
* The extra that will be put in the `Context`.
*
* @deprecated Use `@fastify/websocket` instead.
*
* @category Server/fastify-websocket
*/
export interface Extra {
/**
* The underlying socket connection between the server and the client.
* The WebSocket socket is located under the `socket` parameter.
*/
readonly connection: fastifyWebsocket.SocketStream;
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*/
readonly request: FastifyRequest;
}
/**
* Make a handler to use on a [fastify-websocket](https://github.com/fastify/fastify-websocket) route.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @deprecated Use `@fastify/websocket` instead.
*
* @category Server/fastify-websocket
*/
export declare function makeHandler<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive?: number): fastifyWebsocket.WebsocketHandler;

View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeHandler = void 0;
const websocket_1 = require("./@fastify/websocket");
/**
* Make a handler to use on a [fastify-websocket](https://github.com/fastify/fastify-websocket) route.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @deprecated Use `@fastify/websocket` instead.
*
* @category Server/fastify-websocket
*/
function makeHandler(options,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive = 12000) {
// new handler can be reused, the semantics stayed the same
return (0, websocket_1.makeHandler)(options, keepAlive);
}
exports.makeHandler = makeHandler;

View File

@@ -0,0 +1,21 @@
import { makeHandler as makeHandlerCurrent } from './@fastify/websocket.mjs';
/**
* Make a handler to use on a [fastify-websocket](https://github.com/fastify/fastify-websocket) route.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @deprecated Use `@fastify/websocket` instead.
*
* @category Server/fastify-websocket
*/
export function makeHandler(options,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive = 12000) {
// new handler can be reused, the semantics stayed the same
return makeHandlerCurrent(options, keepAlive);
}

View File

@@ -0,0 +1,67 @@
/// <reference types="node" />
import type * as uWS from 'uWebSockets.js';
import type http from 'http';
import { ServerOptions } from '../server.mjs';
import { ConnectionInitMessage } from '../common.mjs';
/**
* The extra that will be put in the `Context`.
*
* @category Server/uWebSockets
*/
export interface Extra extends UpgradeData {
/**
* The actual socket connection between the server and the client
* with the upgrade data.
*/
readonly socket: uWS.WebSocket<unknown> & UpgradeData;
}
/**
* Data acquired during the HTTP upgrade callback from uWS.
*
* @category Server/uWebSockets
*/
export interface UpgradeData {
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*
* uWS's request is stack allocated and cannot be accessed
* from outside of the internal upgrade; therefore, the persisted
* request holds the relevant values extracted from the uWS's request
* while it is accessible.
*/
readonly persistedRequest: PersistedRequest;
}
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*
* uWS's request is stack allocated and cannot be accessed
* from outside of the internal upgrade; therefore, the persisted
* request holds relevant values extracted from the uWS's request
* while it is accessible.
*
* @category Server/uWebSockets
*/
export interface PersistedRequest {
method: string;
url: string;
/** The raw query string (after the `?` sign) or empty string. */
query: string;
headers: http.IncomingHttpHeaders;
}
/**
* Make the behaviour for using a [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js) WebSocket server.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/uWebSockets
*/
export declare function makeBehavior<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>, behavior?: uWS.WebSocketBehavior<unknown>,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive?: number): uWS.WebSocketBehavior<unknown>;

View File

@@ -0,0 +1,67 @@
/// <reference types="node" />
import type * as uWS from 'uWebSockets.js';
import type http from 'http';
import { ServerOptions } from '../server';
import { ConnectionInitMessage } from '../common';
/**
* The extra that will be put in the `Context`.
*
* @category Server/uWebSockets
*/
export interface Extra extends UpgradeData {
/**
* The actual socket connection between the server and the client
* with the upgrade data.
*/
readonly socket: uWS.WebSocket<unknown> & UpgradeData;
}
/**
* Data acquired during the HTTP upgrade callback from uWS.
*
* @category Server/uWebSockets
*/
export interface UpgradeData {
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*
* uWS's request is stack allocated and cannot be accessed
* from outside of the internal upgrade; therefore, the persisted
* request holds the relevant values extracted from the uWS's request
* while it is accessible.
*/
readonly persistedRequest: PersistedRequest;
}
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*
* uWS's request is stack allocated and cannot be accessed
* from outside of the internal upgrade; therefore, the persisted
* request holds relevant values extracted from the uWS's request
* while it is accessible.
*
* @category Server/uWebSockets
*/
export interface PersistedRequest {
method: string;
url: string;
/** The raw query string (after the `?` sign) or empty string. */
query: string;
headers: http.IncomingHttpHeaders;
}
/**
* Make the behaviour for using a [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js) WebSocket server.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/uWebSockets
*/
export declare function makeBehavior<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>, behavior?: uWS.WebSocketBehavior<unknown>,
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive?: number): uWS.WebSocketBehavior<unknown>;

View File

@@ -0,0 +1,141 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeBehavior = void 0;
const server_1 = require("../server");
const common_1 = require("../common");
const utils_1 = require("../utils");
/**
* Make the behaviour for using a [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js) WebSocket server.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/uWebSockets
*/
function makeBehavior(options, behavior = {},
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive = 12000) {
const isProd = process.env.NODE_ENV === 'production';
const server = (0, server_1.makeServer)(options);
const clients = new Map();
let onDrain = () => {
// gets called when backpressure drains
};
return Object.assign(Object.assign({}, behavior), { pong(...args) {
var _a;
(_a = behavior.pong) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const [socket] = args;
const client = clients.get(socket);
if (!client)
throw new Error('Pong received for a missing client');
if (client.pongWaitTimeout) {
clearTimeout(client.pongWaitTimeout);
client.pongWaitTimeout = null;
}
},
upgrade(...args) {
var _a;
(_a = behavior.upgrade) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const [res, req, context] = args;
const headers = {};
req.forEach((key, value) => {
headers[key] = value;
});
res.upgrade({
persistedRequest: {
method: req.getMethod(),
url: req.getUrl(),
query: req.getQuery(),
headers,
},
}, req.getHeader('sec-websocket-key'), (0, server_1.handleProtocols)(req.getHeader('sec-websocket-protocol')) ||
new Uint8Array(), req.getHeader('sec-websocket-extensions'), context);
},
open(...args) {
var _a;
(_a = behavior.open) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const socket = args[0];
const persistedRequest = socket.persistedRequest;
// prepare client object
const client = {
pingInterval: null,
pongWaitTimeout: null,
handleMessage: () => {
throw new Error('Message received before handler was registered');
},
closed: () => {
throw new Error('Closed before handler was registered');
},
};
client.closed = server.opened({
protocol: (0, server_1.handleProtocols)(persistedRequest.headers['sec-websocket-protocol'] || '') || '',
send: async (message) => {
// the socket might have been destroyed in the meantime
if (!clients.has(socket))
return;
if (!socket.send(message))
// if backpressure is built up wait for drain
await new Promise((resolve) => (onDrain = resolve));
},
close: (code, reason) => {
// end socket in next tick making sure the client is registered
setImmediate(() => {
// the socket might have been destroyed before issuing a close
if (clients.has(socket))
socket.end(code, reason);
});
},
onMessage: (cb) => (client.handleMessage = cb),
}, { socket, persistedRequest });
if (keepAlive > 0 && isFinite(keepAlive)) {
client.pingInterval = setInterval(() => {
// terminate the connection after pong wait has passed because the client is idle
client.pongWaitTimeout = setTimeout(() => socket.close(), keepAlive);
socket.ping();
}, keepAlive);
}
clients.set(socket, client);
},
drain(...args) {
var _a;
(_a = behavior.drain) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
onDrain();
},
async message(...args) {
var _a;
(_a = behavior.message) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const [socket, message] = args;
const client = clients.get(socket);
if (!client)
throw new Error('Message received for a missing client');
try {
await client.handleMessage(Buffer.from(message).toString());
}
catch (err) {
console.error('Internal error occurred during message handling. ' +
'Please check your implementation.', err);
socket.end(common_1.CloseCode.InternalServerError, isProd
? 'Internal server error'
: (0, utils_1.limitCloseReason)(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
},
close(...args) {
var _a;
(_a = behavior.close) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const [socket, code, message] = args;
const client = clients.get(socket);
if (!client)
throw new Error('Closing a missing client');
if (client.pongWaitTimeout)
clearTimeout(client.pongWaitTimeout);
if (client.pingInterval)
clearTimeout(client.pingInterval);
client.closed(code, Buffer.from(message).toString());
clients.delete(socket);
} });
}
exports.makeBehavior = makeBehavior;

View File

@@ -0,0 +1,137 @@
import { handleProtocols, makeServer } from '../server.mjs';
import { CloseCode } from '../common.mjs';
import { limitCloseReason } from '../utils.mjs';
/**
* Make the behaviour for using a [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js) WebSocket server.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/uWebSockets
*/
export function makeBehavior(options, behavior = {},
/**
* The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive = 12000) {
const isProd = process.env.NODE_ENV === 'production';
const server = makeServer(options);
const clients = new Map();
let onDrain = () => {
// gets called when backpressure drains
};
return Object.assign(Object.assign({}, behavior), { pong(...args) {
var _a;
(_a = behavior.pong) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const [socket] = args;
const client = clients.get(socket);
if (!client)
throw new Error('Pong received for a missing client');
if (client.pongWaitTimeout) {
clearTimeout(client.pongWaitTimeout);
client.pongWaitTimeout = null;
}
},
upgrade(...args) {
var _a;
(_a = behavior.upgrade) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const [res, req, context] = args;
const headers = {};
req.forEach((key, value) => {
headers[key] = value;
});
res.upgrade({
persistedRequest: {
method: req.getMethod(),
url: req.getUrl(),
query: req.getQuery(),
headers,
},
}, req.getHeader('sec-websocket-key'), handleProtocols(req.getHeader('sec-websocket-protocol')) ||
new Uint8Array(), req.getHeader('sec-websocket-extensions'), context);
},
open(...args) {
var _a;
(_a = behavior.open) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const socket = args[0];
const persistedRequest = socket.persistedRequest;
// prepare client object
const client = {
pingInterval: null,
pongWaitTimeout: null,
handleMessage: () => {
throw new Error('Message received before handler was registered');
},
closed: () => {
throw new Error('Closed before handler was registered');
},
};
client.closed = server.opened({
protocol: handleProtocols(persistedRequest.headers['sec-websocket-protocol'] || '') || '',
send: async (message) => {
// the socket might have been destroyed in the meantime
if (!clients.has(socket))
return;
if (!socket.send(message))
// if backpressure is built up wait for drain
await new Promise((resolve) => (onDrain = resolve));
},
close: (code, reason) => {
// end socket in next tick making sure the client is registered
setImmediate(() => {
// the socket might have been destroyed before issuing a close
if (clients.has(socket))
socket.end(code, reason);
});
},
onMessage: (cb) => (client.handleMessage = cb),
}, { socket, persistedRequest });
if (keepAlive > 0 && isFinite(keepAlive)) {
client.pingInterval = setInterval(() => {
// terminate the connection after pong wait has passed because the client is idle
client.pongWaitTimeout = setTimeout(() => socket.close(), keepAlive);
socket.ping();
}, keepAlive);
}
clients.set(socket, client);
},
drain(...args) {
var _a;
(_a = behavior.drain) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
onDrain();
},
async message(...args) {
var _a;
(_a = behavior.message) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const [socket, message] = args;
const client = clients.get(socket);
if (!client)
throw new Error('Message received for a missing client');
try {
await client.handleMessage(Buffer.from(message).toString());
}
catch (err) {
console.error('Internal error occurred during message handling. ' +
'Please check your implementation.', err);
socket.end(CloseCode.InternalServerError, isProd
? 'Internal server error'
: limitCloseReason(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
},
close(...args) {
var _a;
(_a = behavior.close) === null || _a === void 0 ? void 0 : _a.call(behavior, ...args);
const [socket, code, message] = args;
const client = clients.get(socket);
if (!client)
throw new Error('Closing a missing client');
if (client.pongWaitTimeout)
clearTimeout(client.pongWaitTimeout);
if (client.pingInterval)
clearTimeout(client.pingInterval);
client.closed(code, Buffer.from(message).toString());
clients.delete(socket);
} });
}

View File

@@ -0,0 +1,38 @@
import type * as http from 'http';
import type * as ws from 'ws';
import { ServerOptions } from '../server.mjs';
import { ConnectionInitMessage, Disposable } from '../common.mjs';
type WebSocket = typeof ws.prototype;
type WebSocketServer = ws.Server;
/**
* The extra that will be put in the `Context`.
*
* @category Server/ws
*/
export interface Extra {
/**
* The actual socket connection between the server and the client.
*/
readonly socket: WebSocket;
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*/
readonly request: http.IncomingMessage;
}
/**
* Use the server on a [ws](https://github.com/websockets/ws) ws server.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/ws
*/
export declare function useServer<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>, ws: WebSocketServer,
/**
* The timeout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive?: number): Disposable;
export {};

View File

@@ -0,0 +1,38 @@
import type * as http from 'http';
import type * as ws from 'ws';
import { ServerOptions } from '../server';
import { ConnectionInitMessage, Disposable } from '../common';
type WebSocket = typeof ws.prototype;
type WebSocketServer = ws.Server;
/**
* The extra that will be put in the `Context`.
*
* @category Server/ws
*/
export interface Extra {
/**
* The actual socket connection between the server and the client.
*/
readonly socket: WebSocket;
/**
* The initial HTTP upgrade request before the actual
* socket and connection is established.
*/
readonly request: http.IncomingMessage;
}
/**
* Use the server on a [ws](https://github.com/websockets/ws) ws server.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/ws
*/
export declare function useServer<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>>(options: ServerOptions<P, Extra & Partial<E>>, ws: WebSocketServer,
/**
* The timeout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive?: number): Disposable;
export {};

View File

@@ -0,0 +1,119 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useServer = void 0;
const server_1 = require("../server");
const common_1 = require("../common");
const utils_1 = require("../utils");
/**
* Use the server on a [ws](https://github.com/websockets/ws) ws server.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/ws
*/
function useServer(options, ws,
/**
* The timeout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive = 12000) {
const isProd = process.env.NODE_ENV === 'production';
const server = (0, server_1.makeServer)(options);
ws.options.handleProtocols = server_1.handleProtocols;
ws.once('error', (err) => {
console.error('Internal error emitted on the WebSocket server. ' +
'Please check your implementation.', err);
// catch the first thrown error and re-throw it once all clients have been notified
let firstErr = null;
// report server errors by erroring out all clients with the same error
for (const client of ws.clients) {
try {
client.close(common_1.CloseCode.InternalServerError, isProd
? 'Internal server error'
: (0, utils_1.limitCloseReason)(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
catch (err) {
firstErr = firstErr !== null && firstErr !== void 0 ? firstErr : err;
}
}
if (firstErr)
throw firstErr;
});
ws.on('connection', (socket, request) => {
socket.once('error', (err) => {
console.error('Internal error emitted on a WebSocket socket. ' +
'Please check your implementation.', err);
socket.close(common_1.CloseCode.InternalServerError, isProd
? 'Internal server error'
: (0, utils_1.limitCloseReason)(err instanceof Error ? err.message : String(err), 'Internal server error'));
});
// keep alive through ping-pong messages
let pongWait = null;
const pingInterval = keepAlive > 0 && isFinite(keepAlive)
? setInterval(() => {
// ping pong on open sockets only
if (socket.readyState === socket.OPEN) {
// terminate the connection after pong wait has passed because the client is idle
pongWait = setTimeout(() => {
socket.terminate();
}, keepAlive);
// listen for client's pong and stop socket termination
socket.once('pong', () => {
if (pongWait) {
clearTimeout(pongWait);
pongWait = null;
}
});
socket.ping();
}
}, keepAlive)
: null;
const closed = server.opened({
protocol: socket.protocol,
send: (data) => new Promise((resolve, reject) => {
if (socket.readyState !== socket.OPEN)
return resolve();
socket.send(data, (err) => (err ? reject(err) : resolve()));
}),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) => socket.on('message', async (event) => {
try {
await cb(String(event));
}
catch (err) {
console.error('Internal error occurred during message handling. ' +
'Please check your implementation.', err);
socket.close(common_1.CloseCode.InternalServerError, isProd
? 'Internal server error'
: (0, utils_1.limitCloseReason)(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
}),
}, { socket, request });
socket.once('close', (code, reason) => {
if (pongWait)
clearTimeout(pongWait);
if (pingInterval)
clearInterval(pingInterval);
if (!isProd &&
code === common_1.CloseCode.SubprotocolNotAcceptable &&
socket.protocol === common_1.DEPRECATED_GRAPHQL_WS_PROTOCOL)
console.warn(`Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` +
'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.');
closed(code, String(reason));
});
});
return {
dispose: async () => {
for (const client of ws.clients) {
client.close(1001, 'Going away');
}
ws.removeAllListeners();
await new Promise((resolve, reject) => {
ws.close((err) => (err ? reject(err) : resolve()));
});
},
};
}
exports.useServer = useServer;

View File

@@ -0,0 +1,115 @@
import { handleProtocols, makeServer } from '../server.mjs';
import { DEPRECATED_GRAPHQL_WS_PROTOCOL, CloseCode, } from '../common.mjs';
import { limitCloseReason } from '../utils.mjs';
/**
* Use the server on a [ws](https://github.com/websockets/ws) ws server.
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* @category Server/ws
*/
export function useServer(options, ws,
/**
* The timeout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss))
* to check that the link between the clients and the server is operating and to prevent the link
* from being broken due to idling.
*
* @default 12_000 // 12 seconds
*/
keepAlive = 12000) {
const isProd = process.env.NODE_ENV === 'production';
const server = makeServer(options);
ws.options.handleProtocols = handleProtocols;
ws.once('error', (err) => {
console.error('Internal error emitted on the WebSocket server. ' +
'Please check your implementation.', err);
// catch the first thrown error and re-throw it once all clients have been notified
let firstErr = null;
// report server errors by erroring out all clients with the same error
for (const client of ws.clients) {
try {
client.close(CloseCode.InternalServerError, isProd
? 'Internal server error'
: limitCloseReason(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
catch (err) {
firstErr = firstErr !== null && firstErr !== void 0 ? firstErr : err;
}
}
if (firstErr)
throw firstErr;
});
ws.on('connection', (socket, request) => {
socket.once('error', (err) => {
console.error('Internal error emitted on a WebSocket socket. ' +
'Please check your implementation.', err);
socket.close(CloseCode.InternalServerError, isProd
? 'Internal server error'
: limitCloseReason(err instanceof Error ? err.message : String(err), 'Internal server error'));
});
// keep alive through ping-pong messages
let pongWait = null;
const pingInterval = keepAlive > 0 && isFinite(keepAlive)
? setInterval(() => {
// ping pong on open sockets only
if (socket.readyState === socket.OPEN) {
// terminate the connection after pong wait has passed because the client is idle
pongWait = setTimeout(() => {
socket.terminate();
}, keepAlive);
// listen for client's pong and stop socket termination
socket.once('pong', () => {
if (pongWait) {
clearTimeout(pongWait);
pongWait = null;
}
});
socket.ping();
}
}, keepAlive)
: null;
const closed = server.opened({
protocol: socket.protocol,
send: (data) => new Promise((resolve, reject) => {
if (socket.readyState !== socket.OPEN)
return resolve();
socket.send(data, (err) => (err ? reject(err) : resolve()));
}),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) => socket.on('message', async (event) => {
try {
await cb(String(event));
}
catch (err) {
console.error('Internal error occurred during message handling. ' +
'Please check your implementation.', err);
socket.close(CloseCode.InternalServerError, isProd
? 'Internal server error'
: limitCloseReason(err instanceof Error ? err.message : String(err), 'Internal server error'));
}
}),
}, { socket, request });
socket.once('close', (code, reason) => {
if (pongWait)
clearTimeout(pongWait);
if (pingInterval)
clearInterval(pingInterval);
if (!isProd &&
code === CloseCode.SubprotocolNotAcceptable &&
socket.protocol === DEPRECATED_GRAPHQL_WS_PROTOCOL)
console.warn(`Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` +
'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.');
closed(code, String(reason));
});
});
return {
dispose: async () => {
for (const client of ws.clients) {
client.close(1001, 'Going away');
}
ws.removeAllListeners();
await new Promise((resolve, reject) => {
ws.close((err) => (err ? reject(err) : resolve()));
});
},
};
}