9

I need to handle events "user is now online" and "user is now offline" on GraphQL Apollo Node.js server. What's the best way to do it?

My investigation: I pretty sure that I don't need to implement any heartbeat logic, because subscriptions are working on WebSockets. But I didn't find any info in their docs how to handle WebSockets events like "connecting" and "disconnecting" from the subscription... Actually I can handle those events from the outside of actual subscription:

SubscriptionServer.create({
    execute,
    subscribe,
    schema,
    onConnect = (...args) => {
        console.log('User connected')
    },
    onDisconnect = (...args) => {
        console.log('User disconnected')
    }
}, {
    server: ws,
    path: '/subscriptions'
})

But can't determine which user is connected via this socket.

My implementation: for now I made it work like that:

  1. We have express middleware for all the calls, it is pushing user object from jsonwebtoken to req object. Here I can trigger "user is now online" logic.

  2. I've created separate subscription, client subscribes on it on login and unsubscribes on logout. Since there is no unsubscribe handler, I manage to determine that filter function gets called on user disconnect without payload, so I did this approach:

    userOnlineSubscription: {
        subscribe: withFilter(
            () => pubSub.asyncIterator('userOnlineSubscription'),
                async (payload, variables) => {
                    if (!payload) {
                        // set user offline
                }
                return false
            }
        )
    }
    

As for me, the solution above is ugly. Can someone recommend the better approach?

gre_gor
  • 6,669
  • 9
  • 47
  • 52
Andrew Gura
  • 382
  • 2
  • 11

1 Answers1

2

I used this approach

onConnect (connectionParams, webSocket) {
  const userPromise = new Promise((resolve, reject) => {
    if (connectionParams.jwt) {
      jsonwebtoken.verify(
        connectionParams.jwt,
        JWT_SECRET,
        (err, decoded) => {
          if (err) {
            reject(new Error('Invalid Token'))
          }

          resolve(
            User.findOne({
              where: { id: decoded.id }
            })
          )
        }
      )
    } else {
      reject(new Error('No Token'))
    }
  })

  return userPromise.then(user => {
    if (user) {
      return { user: Promise.resolve(user) }
    }

    return Promise.reject(new Error('No User'))
  })
}
Medet Tleukabiluly
  • 11,662
  • 3
  • 34
  • 69
  • Unfortunately with this approach "jsonwebtoken.verify" will be called twice: in this handler and inside the graphQL subscriptions. But I couldn't find better solution, so it must be the accepted answer since it works fine. – Andrew Gura Mar 15 '18 at 11:34