Giter VIP home page Giter VIP logo

Comments (10)

enisdenjo avatar enisdenjo commented on May 16, 2024 5

I spoke too soon... PR coming soon!

from graphql-ws.

enisdenjo avatar enisdenjo commented on May 16, 2024 2

Hey @MartijnHols and @brettjashford, I've added 2 new callbacks: onDisconnect and onClose. Both are used for tracking socket closures; however, they are a bit different:

onDisconnect

Called exclusively after the connection has been acknowledged (the client successfully went through the connection initialisation phase). Meaning, you can chain the onConnect + onDisconnect to track actual, compatible, clients. onConnect will always be called before onDisconnect. Is called before onClose.

onClose

Called when the socket closes at any point in time. It does not care if the connection has been acknowledged or not. The onConnect callback must not necessarily be called before it. Is called after onDisconnect.

from graphql-ws.

enisdenjo avatar enisdenjo commented on May 16, 2024 2

If the user disconnects abruptly without graphql-ws acknowledging the connection, only onClose will be called. So, yeah, I'd suggest using onClose in your case.

from graphql-ws.

acro5piano avatar acro5piano commented on May 16, 2024 1

@enisdenjo Thanks for your help!

from graphql-ws.

brettjashford avatar brettjashford commented on May 16, 2024

i have yet to switch from subscriptions-transport-ws, but an onDisconnect server callback would be helpful. currently use this to send stats to datadog on the number of connected clients

from graphql-ws.

enisdenjo avatar enisdenjo commented on May 16, 2024

The thing is, the actual "server" is just a connection controller which can be hooked up to any WebSocket implementation. Its meant to be ultra minimal and its sole purpose is marshalling the messages and having high level control over the channel. Because of this, it does not have a concept of a "disconnect" - it simply does not care. So, ultimately, there is no place for a onDisconnect in the controller.

However, you can indeed achieve this by providing a custom implementation over ws using the raw import { makeServer } from 'graphql-ws' or by simply hooking up on the onclose event in the ws.Server instance. You can check how I implement ws here:

graphql-ws/src/use/ws.ts

Lines 25 to 126 in 5483fac

/**
* 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
*/
export function useServer(
options: ServerOptions<Extra>,
ws: WebSocketServer,
/**
* 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 * 1000 // 12 seconds
*/
keepAlive = 12 * 1000,
): Disposable {
const isProd = process.env.NODE_ENV === 'production';
const server = makeServer<Extra>(options);
ws.on('error', (err) => {
// catch the first thrown error and re-throw it once all clients have been notified
let firstErr: Error | null = null;
// report server errors by erroring out all clients with the same error
for (const client of ws.clients) {
try {
client.close(1011, isProd ? 'Internal Error' : err.message);
} catch (err) {
firstErr = firstErr ?? err;
}
}
if (firstErr) {
throw firstErr;
}
});
ws.on('connection', (socket, request) => {
// keep alive through ping-pong messages
let pongWait: NodeJS.Timeout | null = 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) => {
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(event.toString());
} catch (err) {
socket.close(1011, isProd ? 'Internal Error' : err.message);
}
}),
},
{ socket, request },
);
socket.once('close', () => {
if (pongWait) clearTimeout(pongWait);
if (pingInterval) clearInterval(pingInterval);
closed();
});
});
return {
dispose: async () => {
for (const client of ws.clients) {
client.close(1001, 'Going away');
}
ws.removeAllListeners();
await new Promise<void>((resolve, reject) => {
ws.close((err) => (err ? reject(err) : resolve()));
});
},
};
}

If you need additional help, I'll write up an usage example for you!

from graphql-ws.

enisdenjo avatar enisdenjo commented on May 16, 2024

🎉 This issue has been resolved in version 4.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

from graphql-ws.

acro5piano avatar acro5piano commented on May 16, 2024

Hi @enisdenjo thank you for the amazing library!

I would like to implement the same feature of what @MartijnHols said.

update the user's online status when a user disconnects

The code should be like this, right?

import { useServer } from 'graphql-ws/lib/use/ws';
import { db } from './my-db-service'

useServer({
  context: (ctx) => ({
    userId: ctx.connectionParams.userId // psuedo code, no auth logic
  }),
  onDisconnect: (ctx) => {
    db.update({ status: 'offline' }).where({ userId: ctx.connectionParams.userId })
  }
}, ws )

from graphql-ws.

enisdenjo avatar enisdenjo commented on May 16, 2024

Hey there @acro5piano! I'd recommend reading #91 (comment) to understand the difference between onClose and onDisconnect.

In essence, onDisconnect is called only if the user successfully went through the connection phase (WebSocket established + ConnectionAck message dispatched). On the other hand, onClose is called regardless if the user successfully connected or not - so, every time the socket closes. The call order when the WebSocket closes is:

  1. if acknowledged then onDisconnect
  2. onClose

from graphql-ws.

acro5piano avatar acro5piano commented on May 16, 2024

@enisdenjo
Thank you for your quick response! So onClose seems better because it is possible that user disconnects without sending signals, e.g.) internet disconnection.

from graphql-ws.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.